-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add more functions to CompactDecimalFormatter #3699
Changes from 6 commits
60e900d
2df9c58
90a8970
a633485
3789d14
b431bf5
b352c49
b050ed4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -278,15 +278,19 @@ impl CompactDecimalFormatter { | |
/// The result may have a fractional digit only if it is compact and its | ||
/// significand is less than 10. Trailing fractional 0s are omitted, and | ||
/// a sign is shown only for negative values. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// # use icu_compactdecimal::CompactDecimalFormatter; | ||
/// # use icu_locid::locale; | ||
/// # use writeable::assert_writeable_eq; | ||
/// # | ||
/// # let short_english = CompactDecimalFormatter::try_new_short( | ||
/// # &locale!("en").into(), | ||
/// # Default::default(), | ||
/// # ).unwrap(); | ||
/// use icu_compactdecimal::CompactDecimalFormatter; | ||
/// use icu_locid::locale; | ||
/// use writeable::assert_writeable_eq; | ||
/// | ||
/// let short_english = CompactDecimalFormatter::try_new_short( | ||
/// &locale!("en").into(), | ||
/// Default::default(), | ||
/// ).unwrap(); | ||
/// | ||
/// assert_writeable_eq!(short_english.format_i64(0), "0"); | ||
/// assert_writeable_eq!(short_english.format_i64(2), "2"); | ||
/// assert_writeable_eq!(short_english.format_i64(843), "843"); | ||
|
@@ -295,8 +299,10 @@ impl CompactDecimalFormatter { | |
/// assert_writeable_eq!(short_english.format_i64(3_010_349), "3M"); | ||
/// assert_writeable_eq!(short_english.format_i64(-13_132), "-13K"); | ||
/// ``` | ||
/// | ||
/// The result is the nearest such compact number, with halfway cases- | ||
/// rounded towards the number with an even least significant digit. | ||
/// | ||
/// ``` | ||
/// # use icu_compactdecimal::CompactDecimalFormatter; | ||
/// # use icu_locid::locale; | ||
|
@@ -315,9 +321,154 @@ impl CompactDecimalFormatter { | |
/// ``` | ||
pub fn format_i64(&self, value: i64) -> FormattedCompactDecimal<'_> { | ||
let unrounded = FixedDecimal::from(value); | ||
let log10_type = unrounded.nonzero_magnitude_start(); | ||
self.format_fixed_decimal(unrounded) | ||
} | ||
|
||
/// Formats a floating-point number in compact decimal notation using the default | ||
/// precision settings. | ||
/// | ||
/// The result may have a fractional digit only if it is compact and its | ||
/// significand is less than 10. Trailing fractional 0s are omitted, and | ||
/// a sign is shown only for negative values. | ||
/// | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should document that the ryu feature is needed |
||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use icu_compactdecimal::CompactDecimalFormatter; | ||
/// use icu_locid::locale; | ||
/// use writeable::assert_writeable_eq; | ||
/// | ||
/// let short_english = CompactDecimalFormatter::try_new_short( | ||
/// &locale!("en").into(), | ||
/// Default::default(), | ||
/// ).unwrap(); | ||
/// | ||
/// assert_writeable_eq!(short_english.format_f64(0.0).unwrap(), "0"); | ||
/// assert_writeable_eq!(short_english.format_f64(2.0).unwrap(), "2"); | ||
/// assert_writeable_eq!(short_english.format_f64(843.0).unwrap(), "843"); | ||
/// assert_writeable_eq!(short_english.format_f64(2207.0).unwrap(), "2.2K"); | ||
/// assert_writeable_eq!(short_english.format_f64(15_127.0).unwrap(), "15K"); | ||
/// assert_writeable_eq!(short_english.format_f64(3_010_349.0).unwrap(), "3M"); | ||
/// assert_writeable_eq!(short_english.format_f64(-13_132.0).unwrap(), "-13K"); | ||
/// ``` | ||
/// | ||
/// The result is the nearest such compact number, with halfway cases- | ||
/// rounded towards the number with an even least significant digit. | ||
/// | ||
/// ``` | ||
/// # use icu_compactdecimal::CompactDecimalFormatter; | ||
/// # use icu_locid::locale; | ||
/// # use writeable::assert_writeable_eq; | ||
/// # | ||
/// # let short_english = CompactDecimalFormatter::try_new_short( | ||
/// # &locale!("en").into(), | ||
/// # Default::default(), | ||
/// # ).unwrap(); | ||
/// assert_writeable_eq!(short_english.format_f64(999_499.99).unwrap(), "999K"); | ||
/// assert_writeable_eq!(short_english.format_f64(999_500.00).unwrap(), "1M"); | ||
/// assert_writeable_eq!(short_english.format_f64(1650.0).unwrap(), "1.6K"); | ||
/// assert_writeable_eq!(short_english.format_f64(1750.0).unwrap(), "1.8K"); | ||
/// assert_writeable_eq!(short_english.format_f64(1950.0).unwrap(), "2K"); | ||
/// assert_writeable_eq!(short_english.format_f64(-1_172_700.0).unwrap(), "-1.2M"); | ||
/// ``` | ||
#[cfg(feature = "ryu")] | ||
pub fn format_f64( | ||
&self, | ||
value: f64, | ||
) -> Result<FormattedCompactDecimal<'_>, CompactDecimalError> { | ||
use fixed_decimal::FloatPrecision::Floating; | ||
// NOTE: This first gets the shortest representation of the f64, which | ||
// manifests as double rounding. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t think this is actually a problem given that you do not give the user control over the rounding mode, and that the second rounding is only in the compact case: the two roundings are nearest ties to even, and the ties for the second rounding are representable unless you have really stupidly large numbers (being integers), so the first rounding will not produce a tie for the second. The double rounding issue would manifest if you could request things such as « nearest, ties to even, exactly one sig. dec. »: then |
||
let partly_rounded = FixedDecimal::try_from_f64(value, Floating)?; | ||
Ok(self.format_fixed_decimal(partly_rounded)) | ||
} | ||
|
||
/// Formats a [`FixedDecimal`] by automatically scaling and rounding it. | ||
/// | ||
/// The result may have a fractional digit only if it is compact and its | ||
/// significand is less than 10. Trailing fractional 0s are omitted. | ||
/// | ||
/// Because the FixedDecimal is mutated before formatting, this function | ||
/// takes ownership of it. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use fixed_decimal::FixedDecimal; | ||
/// use icu_compactdecimal::CompactDecimalFormatter; | ||
/// use icu_locid::locale; | ||
/// use writeable::assert_writeable_eq; | ||
/// | ||
/// let short_english = CompactDecimalFormatter::try_new_short( | ||
/// &locale!("en").into(), | ||
/// Default::default(), | ||
/// ).unwrap(); | ||
/// | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal(FixedDecimal::from(0)), | ||
/// "0"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal(FixedDecimal::from(2)), | ||
/// "2"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal(FixedDecimal::from(843)), | ||
/// "843"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal(FixedDecimal::from(2207)), | ||
/// "2.2K"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal(FixedDecimal::from(15127)), | ||
/// "15K"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal(FixedDecimal::from(3010349)), | ||
/// "3M"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal(FixedDecimal::from(-13132)), | ||
/// "-13K"); | ||
/// | ||
/// // The sign display on the FixedDecimal is respected: | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal(FixedDecimal::from(2500) | ||
/// .with_sign_display(fixed_decimal::SignDisplay::ExceptZero)), | ||
/// "+2.5K"); | ||
/// ``` | ||
/// | ||
/// The result is the nearest such compact number, with halfway cases- | ||
/// rounded towards the number with an even least significant digit. | ||
/// | ||
/// ``` | ||
/// # use fixed_decimal::FixedDecimal; | ||
/// # use icu_compactdecimal::CompactDecimalFormatter; | ||
/// # use icu_locid::locale; | ||
/// # use writeable::assert_writeable_eq; | ||
/// # | ||
/// # let short_english = CompactDecimalFormatter::try_new_short( | ||
/// # &locale!("en").into(), | ||
/// # Default::default(), | ||
/// # ).unwrap(); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal("999499.99".parse().unwrap()), | ||
/// "999K"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal("999500.00".parse().unwrap()), | ||
/// "1M"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal("1650".parse().unwrap()), | ||
/// "1.6K"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal("1750".parse().unwrap()), | ||
/// "1.8K"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal("1950".parse().unwrap()), | ||
/// "2K"); | ||
/// assert_writeable_eq!( | ||
/// short_english.format_fixed_decimal("-1172700".parse().unwrap()), | ||
/// "-1.2M"); | ||
/// ``` | ||
pub fn format_fixed_decimal(&self, value: FixedDecimal) -> FormattedCompactDecimal<'_> { | ||
let log10_type = value.nonzero_magnitude_start(); | ||
let (mut plural_map, mut exponent) = self.plural_map_and_exponent_for_magnitude(log10_type); | ||
let mut significand = unrounded.multiplied_pow10(-i16::from(exponent)); | ||
let mut significand = value.multiplied_pow10(-i16::from(exponent)); | ||
// If we have just one digit before the decimal point… | ||
if significand.nonzero_magnitude_start() == 0 { | ||
// …round to one fractional digit… | ||
|
@@ -373,6 +524,7 @@ impl CompactDecimalFormatter { | |
/// be equal to `formatter.compact_exponent_for_magnitude(n.significand().nonzero_magnitude_start() + n.exponent())`. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// # use icu_compactdecimal::CompactDecimalFormatter; | ||
/// # use icu_locid::locale; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly that if you want something fancier, e.g., at least three digits (like YT subscriber counts), or directed rounding (like everything at YT) you then need to round yourself?
(That works for me.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you be okay if we landed this PR and then in the future we added such options to
CompactDecimalFormatterOptions
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, though the rounding direction discussion would then come back to haunt us :-)
As I wrote, I am also fine with just not having those options, and seeing the sort of thing that YouTube does as an advanced do-your-own-rounding scenario; since we have format_compact_decimal and CompactDecimal, it is much easier for the caller to have their own arbitrarily fancy rounding logic than it was for me with ICU a few years ago.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow-up filed: #3929