Skip to content

Commit f1a18f9

Browse files
Polish compact decimal API (unicode-org#7759)
unicode-org#3647 ## Changelog: N/A
1 parent f44174f commit f1a18f9

File tree

7 files changed

+89
-60
lines changed

7 files changed

+89
-60
lines changed

components/decimal/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ criterion = { workspace = true }
5151

5252
[features]
5353
default = ["compiled_data"]
54-
serde = ["dep:serde", "icu_provider/serde", "zerovec/serde", "icu_pattern?/serde", "icu_plurals?/serde"]
54+
serde = ["alloc", "dep:serde", "icu_provider/serde", "zerovec/serde", "icu_pattern?/serde", "icu_plurals?/serde"]
5555
datagen = ["serde", "dep:databake", "zerovec/databake", "icu_provider/export", "alloc", "icu_plurals?/datagen"]
5656
compiled_data = ["dep:icu_decimal_data", "dep:icu_locale", "icu_locale?/compiled_data", "icu_provider/baked", "icu_plurals?/compiled_data"]
5757
ryu = ["fixed_decimal/ryu"]
5858
alloc = ["serde?/alloc", "zerovec/alloc"]
59-
unstable = ["alloc", "dep:icu_pattern", "dep:icu_plurals"]
59+
unstable = ["dep:icu_pattern", "dep:icu_plurals"]
6060

6161
[[bench]]
6262
name = "fixed_decimal_format"

components/decimal/src/compact_formatter.rs

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44

55
use core::fmt::Display;
66

7+
use crate::input::Decimal;
8+
use crate::Cow;
79
use crate::{
8-
error::ExponentError,
10+
error::CompactExponentError,
911
options::CompactDecimalFormatterOptions,
1012
preferences::{CompactDecimalFormatterPreferences, DecimalFormatterPreferences},
1113
provider::*,
1214
DecimalFormatter,
1315
};
14-
use alloc::borrow::Cow;
15-
use fixed_decimal::{Decimal, UnsignedDecimal};
16+
#[cfg(feature = "alloc")]
17+
use alloc::string::String;
18+
use fixed_decimal::UnsignedDecimal;
1619
use icu_pattern::{Pattern, PatternBackend, SinglePlaceholder};
1720
use icu_plurals::PluralRules;
1821
use icu_provider::DataError;
@@ -72,9 +75,7 @@ pub struct CompactDecimalFormatter {
7275
}
7376

7477
impl CompactDecimalFormatter {
75-
/// Constructor that takes a selected locale and a list of preferences,
76-
/// then collects all compiled data necessary to format numbers in short compact
77-
/// decimal notation for the given locale.
78+
/// Creates a new short [`CompactDecimalFormatter`] from compiled data and an options bag.
7879
///
7980
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
8081
///
@@ -85,11 +86,14 @@ impl CompactDecimalFormatter {
8586
/// ```
8687
/// use icu::decimal::CompactDecimalFormatter;
8788
/// use icu::locale::locale;
89+
/// use writeable::assert_writeable_eq;
8890
///
89-
/// CompactDecimalFormatter::try_new_short(
91+
/// let formatter = CompactDecimalFormatter::try_new_short(
9092
/// locale!("sv").into(),
9193
/// Default::default(),
92-
/// );
94+
/// ).unwrap();
95+
///
96+
/// assert_writeable_eq!(formatter.format(&1234.into()), "1,2 tn");
9397
/// ```
9498
#[cfg(feature = "compiled_data")]
9599
pub fn try_new_short(
@@ -98,10 +102,7 @@ impl CompactDecimalFormatter {
98102
) -> Result<Self, DataError> {
99103
let locale = DecimalCompactShortV1::make_locale(prefs.locale_preferences);
100104
Ok(Self {
101-
decimal_formatter: DecimalFormatter::try_new(
102-
(&prefs).into(),
103-
options.decimal_formatter_options,
104-
)?,
105+
decimal_formatter: DecimalFormatter::try_new((&prefs).into(), options.into())?,
105106
plural_rules: PluralRules::try_new_cardinal((&prefs).into())?,
106107
compact_data: load_with_fallback::<DecimalCompactShortV1>(
107108
&Baked,
@@ -143,7 +144,7 @@ impl CompactDecimalFormatter {
143144
decimal_formatter: DecimalFormatter::try_new_unstable(
144145
provider,
145146
(&prefs).into(),
146-
options.decimal_formatter_options,
147+
options.into(),
147148
)?,
148149
plural_rules: PluralRules::try_new_cardinal_unstable(provider, (&prefs).into())?,
149150
compact_data: load_with_fallback::<DecimalCompactShortV1>(
@@ -158,9 +159,7 @@ impl CompactDecimalFormatter {
158159
})
159160
}
160161

161-
/// Constructor that takes a selected locale and a list of preferences,
162-
/// then collects all compiled data necessary to format numbers in short compact
163-
/// decimal notation for the given locale.
162+
/// Creates a new long [`CompactDecimalFormatter`] from compiled data and an options bag.
164163
///
165164
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
166165
///
@@ -171,11 +170,14 @@ impl CompactDecimalFormatter {
171170
/// ```
172171
/// use icu::decimal::CompactDecimalFormatter;
173172
/// use icu::locale::locale;
173+
/// use writeable::assert_writeable_eq;
174174
///
175-
/// CompactDecimalFormatter::try_new_long(
175+
/// let formatter = CompactDecimalFormatter::try_new_long(
176176
/// locale!("sv").into(),
177177
/// Default::default(),
178-
/// );
178+
/// ).unwrap();
179+
///
180+
/// assert_writeable_eq!(formatter.format(&1234.into()), "1,2 tusen");
179181
/// ```
180182
#[cfg(feature = "compiled_data")]
181183
pub fn try_new_long(
@@ -184,10 +186,7 @@ impl CompactDecimalFormatter {
184186
) -> Result<Self, DataError> {
185187
let locale = DecimalCompactLongV1::make_locale(prefs.locale_preferences);
186188
Ok(Self {
187-
decimal_formatter: DecimalFormatter::try_new(
188-
(&prefs).into(),
189-
options.decimal_formatter_options,
190-
)?,
189+
decimal_formatter: DecimalFormatter::try_new((&prefs).into(), options.into())?,
191190
plural_rules: PluralRules::try_new_cardinal((&prefs).into())?,
192191
compact_data: load_with_fallback::<DecimalCompactLongV1>(
193192
&Baked,
@@ -229,7 +228,7 @@ impl CompactDecimalFormatter {
229228
decimal_formatter: DecimalFormatter::try_new_unstable(
230229
provider,
231230
(&prefs).into(),
232-
options.decimal_formatter_options,
231+
options.into(),
233232
)?,
234233
plural_rules: PluralRules::try_new_cardinal_unstable(provider, (&prefs).into())?,
235234
compact_data: load_with_fallback::<DecimalCompactLongV1>(
@@ -249,14 +248,11 @@ impl CompactDecimalFormatter {
249248
/// The result may have a fractional digit only if it is compact and its
250249
/// significand is less than 10. Trailing fractional 0s are omitted.
251250
///
252-
/// Because the Decimal is mutated before formatting, this function
253-
/// takes ownership of it.
254-
///
255251
/// # Examples
256252
///
257253
/// ```
258254
/// use icu::decimal::CompactDecimalFormatter;
259-
/// use icu::decimal::input::{Decimal, FloatPrecision, SignDisplay};
255+
/// use icu::decimal::input::{Decimal, SignDisplay};
260256
/// use icu::locale::locale;
261257
/// use writeable::assert_writeable_eq;
262258
///
@@ -303,14 +299,6 @@ impl CompactDecimalFormatter {
303299
/// ),
304300
/// "+2.5K"
305301
/// );
306-
///
307-
/// // Floating point inputs should use FloatPrecision::RoundTrip
308-
/// assert_writeable_eq!(
309-
/// short_english.format(
310-
/// &Decimal::try_from_f64(999_499.99, FloatPrecision::RoundTrip).unwrap()
311-
/// ),
312-
/// "999K"
313-
/// );
314302
/// ```
315303
///
316304
/// The result is the nearest such compact number, with halfway cases-
@@ -354,6 +342,26 @@ impl CompactDecimalFormatter {
354342
/// "0.22"
355343
/// );
356344
/// ```
345+
///
346+
/// Floating point inputs should use [`FloatPrecision::RoundTrip`](fixed_decimal::FloatPrecision::RoundTrip).
347+
///
348+
/// ```
349+
/// # use icu::decimal::input::{Decimal, FloatPrecision};
350+
/// # use icu::decimal::CompactDecimalFormatter;
351+
/// # use icu::locale::locale;
352+
/// # use writeable::assert_writeable_eq;
353+
/// #
354+
/// # let short_english = CompactDecimalFormatter::try_new_short(
355+
/// # locale!("en").into(),
356+
/// # Default::default(),
357+
/// # ).unwrap();
358+
/// assert_writeable_eq!(
359+
/// short_english.format(
360+
/// &Decimal::try_from_f64(999_499.99, FloatPrecision::RoundTrip).unwrap()
361+
/// ),
362+
/// "999K"
363+
/// );
364+
/// ```
357365
pub fn format(&self, value: &Decimal) -> impl Writeable + Display + '_ {
358366
let (compact_pattern, significand) = self
359367
.compact_data
@@ -370,6 +378,15 @@ impl CompactDecimalFormatter {
370378
)
371379
}
372380

381+
/// Formats a [`Decimal`], returning a [`String`].
382+
///
383+
/// ✨ *Enabled with the `alloc` Cargo feature.*
384+
#[cfg(feature = "alloc")]
385+
pub fn format_to_string(&self, value: &Decimal) -> String {
386+
use writeable::Writeable;
387+
self.format(value).write_to_string().into_owned()
388+
}
389+
373390
/// Formats a [`Decimal`] with a given exponent according to locale data.
374391
///
375392
/// This is an advanced API; prefer using [`Self::format()`] in simple
@@ -395,7 +412,7 @@ impl CompactDecimalFormatter {
395412
/// # use icu::locale::locale;
396413
/// # use writeable::assert_writeable_eq;
397414
/// # use std::str::FromStr;
398-
///
415+
/// #
399416
/// # let short_french = CompactDecimalFormatter::try_new_short(
400417
/// # locale!("fr").into(),
401418
/// # Default::default(),
@@ -470,7 +487,7 @@ impl CompactDecimalFormatter {
470487
&'l self,
471488
significand: &'l Decimal,
472489
exponent: u8,
473-
) -> Result<impl Writeable + Display + 'l, ExponentError> {
490+
) -> Result<impl Writeable + Display + 'l, CompactExponentError> {
474491
let log10_type = significand.absolute.nonzero_magnitude_start() + i16::from(exponent);
475492

476493
let (pattern, expected_exponent) = self
@@ -491,10 +508,10 @@ impl CompactDecimalFormatter {
491508
.unwrap_or((Pattern::<SinglePlaceholder>::PASS_THROUGH, 0));
492509

493510
if exponent != expected_exponent {
494-
return Err(ExponentError {
511+
return Err(CompactExponentError {
495512
actual: exponent,
496513
expected: expected_exponent,
497-
log10_type,
514+
magnitude: log10_type,
498515
});
499516
}
500517

components/decimal/src/decimal_formatter.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ use core::fmt::Write;
77
use writeable::Part;
88

99
use crate::grouper;
10+
use crate::input::Decimal;
1011
use crate::options::*;
1112
use crate::parts;
1213
use crate::provider::*;
1314
use crate::size_test_macro::size_test;
1415
use crate::DecimalFormatterPreferences;
1516
#[cfg(feature = "alloc")]
1617
use alloc::string::String;
17-
use fixed_decimal::{Decimal, Sign, UnsignedDecimal};
18+
use fixed_decimal::{Sign, UnsignedDecimal};
1819
use icu_provider::prelude::*;
1920
use writeable::PartsWrite;
2021
use writeable::Writeable;

components/decimal/src/error.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
77
use displaydoc::Display;
88

9-
/// An error due to a [`CompactDecimal`](fixed_decimal::CompactDecimal) with an
10-
/// exponent inconsistent with the compact decimal data for the locale, e.g.,
11-
/// when formatting 1c5 in English (US).
9+
/// An error due to incorrect arguments to
10+
/// [`CompactDecimalFormatter::format_with_exponent`](crate::CompactDecimalFormatter::format_with_exponent).
1211
///
1312
/// <div class="stab unstable">
1413
/// 🚧 This code is considered unstable; it may change at any time, in breaking or non-breaking ways,
@@ -19,14 +18,14 @@ use displaydoc::Display;
1918
///
2019
/// ✨ *Enabled with the `unstable` Cargo feature.*
2120
#[derive(Display, Copy, Clone, Debug)]
22-
#[displaydoc("Expected compact exponent {expected} for 10^{log10_type}, got {actual}")]
23-
pub struct ExponentError {
21+
#[displaydoc("Expected compact exponent {expected} for 10^{magnitude}, got {actual}")]
22+
pub struct CompactExponentError {
2423
/// The compact decimal exponent passed to the formatter.
2524
pub(crate) actual: u8,
26-
/// The appropriate compact decimal exponent for a number of the given magnitude.
25+
/// The appropriate exponent for a number of the given magnitude.
2726
pub(crate) expected: u8,
2827
/// The magnitude of the number being formatted.
29-
pub(crate) log10_type: i16,
28+
pub(crate) magnitude: i16,
3029
}
3130

32-
impl core::error::Error for ExponentError {}
31+
impl core::error::Error for CompactExponentError {}

components/decimal/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,18 @@ pub use alloc::borrow::Cow;
9898
#[doc(hidden)] // TODO(#3647): should be private
9999
pub enum Cow<'a, T> {
100100
Borrowed(&'a T),
101+
Owned(T),
101102
}
102103

103104
#[cfg(not(feature = "alloc"))]
104105
impl<'a, T> core::ops::Deref for Cow<'a, T> {
105106
type Target = T;
106107

107108
fn deref(&self) -> &Self::Target {
108-
let Self::Borrowed(r) = self;
109-
r
109+
match self {
110+
Self::Borrowed(r) => r,
111+
Self::Owned(ref r) => r,
112+
}
110113
}
111114
}
112115

components/decimal/src/options.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,24 +82,33 @@ pub enum GroupingStrategy {
8282
#[derive(Debug, Eq, PartialEq, Clone)]
8383
#[non_exhaustive]
8484
pub struct CompactDecimalFormatterOptions {
85-
/// Options to configure the inner [`crate::DecimalFormatter`].
86-
pub decimal_formatter_options: DecimalFormatterOptions,
85+
/// When to render grouping separators.
86+
///
87+
/// Default is [`GroupingStrategy::Min2`]
88+
pub grouping_strategy: Option<GroupingStrategy>,
8789
}
8890

8991
#[cfg(feature = "unstable")]
9092
impl Default for CompactDecimalFormatterOptions {
9193
fn default() -> Self {
92-
Self {
93-
decimal_formatter_options: GroupingStrategy::Min2.into(),
94-
}
94+
GroupingStrategy::Min2.into()
9595
}
9696
}
9797

9898
#[cfg(feature = "unstable")]
9999
impl From<DecimalFormatterOptions> for CompactDecimalFormatterOptions {
100100
fn from(decimal_formatter_options: DecimalFormatterOptions) -> Self {
101101
Self {
102-
decimal_formatter_options,
102+
grouping_strategy: decimal_formatter_options.grouping_strategy,
103+
}
104+
}
105+
}
106+
107+
#[cfg(feature = "unstable")]
108+
impl From<CompactDecimalFormatterOptions> for DecimalFormatterOptions {
109+
fn from(decimal_formatter_options: CompactDecimalFormatterOptions) -> Self {
110+
Self {
111+
grouping_strategy: decimal_formatter_options.grouping_strategy,
103112
}
104113
}
105114
}
@@ -108,7 +117,7 @@ impl From<DecimalFormatterOptions> for CompactDecimalFormatterOptions {
108117
impl From<GroupingStrategy> for CompactDecimalFormatterOptions {
109118
fn from(grouping_strategy: GroupingStrategy) -> Self {
110119
Self {
111-
decimal_formatter_options: grouping_strategy.into(),
120+
grouping_strategy: Some(grouping_strategy),
112121
}
113122
}
114123
}

tools/make/diplomat-coverage/src/allowlist.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ lazy_static::lazy_static! {
296296
// =========================
297297

298298
"icu::decimal::CompactDecimalFormatter",
299-
"icu::decimal::error::ExponentError",
299+
"icu::decimal::error::CompactExponentError",
300300
"icu::decimal::options::CompactDecimalFormatterOptions",
301301

302302
"icu::experimental",

0 commit comments

Comments
 (0)