diff --git a/Cargo.lock b/Cargo.lock index a657c8758e0..6084ceb6c46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1910,6 +1910,7 @@ dependencies = [ "icu_casemapping", "icu_collator", "icu_collections", + "icu_compactdecimal", "icu_datagen", "icu_datetime", "icu_decimal", diff --git a/experimental/compactdecimal/Cargo.toml b/experimental/compactdecimal/Cargo.toml index 9f413e13056..f28456573c8 100644 --- a/experimental/compactdecimal/Cargo.toml +++ b/experimental/compactdecimal/Cargo.toml @@ -34,7 +34,7 @@ fixed_decimal = { version = "0.5", path = "../../utils/fixed_decimal" } databake = { version = "0.1.0", path = "../../utils/databake", optional = true, features = ["derive"]} [dev-dependencies] -icu_testdata = { path = "../../provider/testdata", default-features = false, features = ["icu_plurals", "icu_decimal"] } +icu_testdata = { path = "../../provider/testdata", default-features = false, features = ["icu_plurals", "icu_decimal", "icu_compactdecimal"] } icu_locid = { version = "1.0.0", path = "../../components/locid" } [features] diff --git a/experimental/compactdecimal/src/compactdecimal.rs b/experimental/compactdecimal/src/compactdecimal.rs new file mode 100644 index 00000000000..5330031d83d --- /dev/null +++ b/experimental/compactdecimal/src/compactdecimal.rs @@ -0,0 +1,414 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use alloc::borrow::Cow; +use core::convert::TryFrom; +use fixed_decimal::{CompactDecimal, FixedDecimal}; +use icu_decimal::{ + options::{FixedDecimalFormatterOptions, GroupingStrategy}, + FixedDecimalFormatter, +}; +use icu_plurals::PluralRules; +use icu_provider::{DataLocale, DataPayload, DataProvider, DataRequest}; +use zerovec::maps::ZeroMap2dCursor; + +use crate::{ + format::FormattedCompactDecimal, + provider::{ + Count, ErasedCompactDecimalFormatDataV1Marker, LongCompactDecimalFormatDataV1Marker, + PatternULE, ShortCompactDecimalFormatDataV1Marker, + }, + CompactDecimalError, +}; + +/// A formatter that renders locale-sensitive compact numbers. +/// +/// # Examples +/// +/// ``` +/// use icu_compactdecimal::CompactDecimalFormatter; +/// use icu_locid::locale; +/// use writeable::assert_writeable_eq; +/// +/// let short_french = CompactDecimalFormatter::try_new_short_unstable( +/// &icu_testdata::unstable(), +/// &locale!("fr").into(), +/// ).unwrap(); +/// +/// let [long_french, long_japanese, long_bangla] = [locale!("fr"), locale!("ja"), locale!("bn")] +/// .map(|locale| { +/// CompactDecimalFormatter::try_new_long_unstable( +/// &icu_testdata::unstable(), +/// &locale.into(), +/// ) +/// .unwrap() +/// }); +/// +/// /// Supports short and long notations: +/// # // The following line contains U+00A0 NO-BREAK SPACE. +/// assert_writeable_eq!(short_french.format_i64(35_357_670), "35 M"); +/// assert_writeable_eq!(long_french.format_i64(35_357_670), "35 millions"); +/// /// The powers of ten used are locale-dependent: +/// assert_writeable_eq!(long_japanese.format_i64(3535_7670), "3536万"); +/// /// So are the digits: +/// assert_writeable_eq!(long_bangla.format_i64(3_53_57_670), "৩.৫ কোটি"); +/// +/// /// The output does not always contain digits: +/// assert_writeable_eq!(long_french.format_i64(1000), "mille"); +/// ``` +pub struct CompactDecimalFormatter { + pub(crate) plural_rules: PluralRules, + pub(crate) fixed_decimal_format: FixedDecimalFormatter, + pub(crate) compact_data: DataPayload, +} + +impl CompactDecimalFormatter { + /// Constructor that takes a selected locale, reference to a + /// [data provider] and a list of preferences, then collects all data + /// necessary to format numbers in short compact decimal notation for + /// the given locale. + /// + /// [📚 Help choosing a constructor](icu_provider::constructors) + ///
+ /// ⚠️ The bounds on this function may change over time, including in SemVer minor releases. + ///
+ /// + /// # Examples + /// + /// ``` + /// use icu_compactdecimal::CompactDecimalFormatter; + /// use icu_locid::locale; + /// + /// CompactDecimalFormatter::try_new_short_unstable( + /// &icu_testdata::unstable(), + /// &locale!("sv").into()); + /// ``` + /// + /// [data provider]: icu_provider + pub fn try_new_short_unstable( + data_provider: &D, + locale: &DataLocale, + ) -> Result + where + D: DataProvider + + DataProvider + + DataProvider + + ?Sized, + { + let mut options = FixedDecimalFormatterOptions::default(); + options.grouping_strategy = GroupingStrategy::Min2; + Ok(Self { + fixed_decimal_format: FixedDecimalFormatter::try_new_unstable( + data_provider, + locale, + options, + )?, + plural_rules: PluralRules::try_new_cardinal_unstable(data_provider, locale)?, + compact_data: DataProvider::::load( + data_provider, + DataRequest { + locale, + metadata: Default::default(), + }, + )? + .take_payload()? + .cast(), + }) + } + + icu_provider::gen_any_buffer_constructors!( + locale: include, + options: skip, + error: CompactDecimalError, + functions: [ + Self::try_new_short_unstable, + try_new_short_with_any_provider, + try_new_short_with_buffer_provider + ] + ); + + /// Constructor that takes a selected locale, reference to a + /// [data provider] and a list of preferences, then collects all data + /// necessary to format numbers in short compact decimal notation for + /// the given locale. + /// + /// [📚 Help choosing a constructor](icu_provider::constructors) + ///
+ /// ⚠️ The bounds on this function may change over time, including in SemVer minor releases. + ///
+ /// + /// # Examples + /// + /// ``` + /// use icu_compactdecimal::CompactDecimalFormatter; + /// use icu_locid::locale; + /// + /// CompactDecimalFormatter::try_new_long_unstable( + /// &icu_testdata::unstable(), + /// &locale!("sv").into()); + /// ``` + /// + /// [data provider]: icu_provider + pub fn try_new_long_unstable( + data_provider: &D, + locale: &DataLocale, + ) -> Result + where + D: DataProvider + + DataProvider + + DataProvider + + ?Sized, + { + let mut options = FixedDecimalFormatterOptions::default(); + options.grouping_strategy = GroupingStrategy::Min2; + Ok(Self { + fixed_decimal_format: FixedDecimalFormatter::try_new_unstable( + data_provider, + locale, + options, + )?, + plural_rules: PluralRules::try_new_cardinal_unstable(data_provider, locale)?, + compact_data: DataProvider::::load( + data_provider, + DataRequest { + locale, + metadata: Default::default(), + }, + )? + .take_payload()? + .cast(), + }) + } + + icu_provider::gen_any_buffer_constructors!( + locale: include, + options: skip, + error: CompactDecimalError, + functions: [ + Self::try_new_long_unstable, + try_new_long_with_any_provider, + try_new_long_with_buffer_provider + ] + ); + + /// Formats an integer 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. + /// ``` + /// # use icu_compactdecimal::CompactDecimalFormatter; + /// # use icu_locid::locale; + /// # use writeable::assert_writeable_eq; + /// # + /// # let short_english = CompactDecimalFormatter::try_new_short_unstable( + /// # &icu_testdata::unstable(), + /// # &locale!("en").into() + /// # ).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"); + /// assert_writeable_eq!(short_english.format_i64(2207), "2.2K"); + /// assert_writeable_eq!(short_english.format_i64(15_127), "15K"); + /// 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; + /// # use writeable::assert_writeable_eq; + /// # + /// # let short_english = CompactDecimalFormatter::try_new_short_unstable( + /// # &icu_testdata::unstable(), + /// # &locale!("en").into(), + /// # ).unwrap(); + /// assert_writeable_eq!(short_english.format_i64(999_499), "999K"); + /// assert_writeable_eq!(short_english.format_i64(999_500), "1M"); + /// assert_writeable_eq!(short_english.format_i64(1650), "1.6K"); + /// assert_writeable_eq!(short_english.format_i64(1750), "1.8K"); + /// assert_writeable_eq!(short_english.format_i64(1950), "2K"); + /// assert_writeable_eq!(short_english.format_i64(-1_172_700), "-1.2M"); + /// ``` + pub fn format_i64(&self, value: i64) -> FormattedCompactDecimal<'_> { + let unrounded = FixedDecimal::from(value); + let log10_type = unrounded.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)); + // If we have just one digit before the decimal point… + if significand.nonzero_magnitude_start() == 0 { + // …round to one fractional digit… + significand.half_even(-1); + } else { + // …otherwise, we have at least 2 digits before the decimal point, + // so round to eliminate the fractional part. + significand.half_even(0); + } + let rounded_magnitude = significand.nonzero_magnitude_start() + i16::from(exponent); + if rounded_magnitude > log10_type { + // We got bumped up a magnitude by rounding. + // This means that `significand` is a power of 10. + let old_exponent = exponent; + // NOTE(egg): We could inline `plural_map_and_exponent_for_magnitude` + // to avoid iterating twice (we only need to look at the next key), + // but this obscures the logic and the map is tiny. + (plural_map, exponent) = self.plural_map_and_exponent_for_magnitude(rounded_magnitude); + significand = + significand.multiplied_pow10(i16::from(old_exponent) - i16::from(exponent)); + // There is no need to perform any rounding: `significand`, being + // a power of 10, is as round as it gets, and since `exponent` can + // only have become larger, it is already the correct rounding of + // `unrounded` to the precision we want to show. + } + significand.trim_end(); + FormattedCompactDecimal { + formatter: self, + plural_map, + value: Cow::Owned(CompactDecimal::from_significand_and_exponent( + significand, + exponent, + )), + } + } + + /// Formats a [`CompactDecimal`] object according to locale data. + /// + /// This is an advanced API; prefer using [`Self::format_i64()`] in simple + /// cases. + /// + /// Since the caller specifies the exact digits that are displayed, this + /// allows for arbitrarily complex rounding rules. + /// However, contrary to [`FixedDecimalFormatter::format()`], this operation + /// can fail, because the given [`CompactDecimal`] can be inconsistent with + /// the locale data; for instance, if the locale uses lakhs and crores and + /// millions are requested, or vice versa, this function returns an error. + /// + /// The given [`CompactDecimal`] should be constructed using + /// [`Self::compact_exponent_for_magnitude()`] on the same + /// [`CompactDecimalFormatter`] object. + /// Specifically, `formatter.format_compact_decimal(n)` requires that `n.exponent()` + /// be equal to `formatter.compact_exponent_for_magnitude(n.significand().nonzero_magnitude_start() + n.exponent())`. + /// + /// # Examples + /// ``` + /// # use icu_compactdecimal::CompactDecimalFormatter; + /// # use icu_locid::locale; + /// # use writeable::assert_writeable_eq; + /// # use std::str::FromStr; + /// use fixed_decimal::CompactDecimal; + /// + /// # let short_french = CompactDecimalFormatter::try_new_short_unstable( + /// # &icu_testdata::unstable(), + /// # &locale!("fr").into(), + /// # ).unwrap(); + /// # let [long_french, long_bangla] = [locale!("fr"), locale!("bn")] + /// # .map(|locale| { + /// # CompactDecimalFormatter::try_new_long_unstable( + /// # &icu_testdata::unstable(), + /// # &locale.into(), + /// # ) + /// # .unwrap() + /// # }); + /// # + /// let about_a_million = CompactDecimal::from_str("1.20c6").unwrap(); + /// let three_million = CompactDecimal::from_str("+3c6").unwrap(); + /// let ten_lakhs = CompactDecimal::from_str("10c5").unwrap(); + /// # // The following line contains U+00A0 NO-BREAK SPACE. + /// assert_writeable_eq!(short_french.format_compact_decimal(&about_a_million).unwrap(), "1,20 M"); + /// assert_writeable_eq!(long_french.format_compact_decimal(&about_a_million).unwrap(), "1,20 million"); + /// + /// # // The following line contains U+00A0 NO-BREAK SPACE. + /// assert_writeable_eq!(short_french.format_compact_decimal(&three_million).unwrap(), "+3 M"); + /// assert_writeable_eq!(long_french.format_compact_decimal(&three_million).unwrap(), "+3 millions"); + /// + /// assert_writeable_eq!(long_bangla.format_compact_decimal(&ten_lakhs).unwrap(), "১০ লাখ"); + /// + /// assert_eq!( + /// long_bangla.format_compact_decimal(&about_a_million).err().unwrap().to_string(), + /// "Expected compact exponent 5 for 10^6, got 6", + /// ); + /// assert_eq!( + /// long_french.format_compact_decimal(&ten_lakhs).err().unwrap().to_string(), + /// "Expected compact exponent 6 for 10^6, got 5", + /// ); + /// + /// /// Some patterns omit the digits; in those cases, the output does not + /// /// contain the sequence of digits specified by the CompactDecimal. + /// let a_thousand = CompactDecimal::from_str("1c3").unwrap(); + /// assert_writeable_eq!(long_french.format_compact_decimal(&a_thousand).unwrap(), "mille"); + /// ``` + pub fn format_compact_decimal<'l>( + &'l self, + value: &'l CompactDecimal, + ) -> Result, CompactDecimalError> { + let log10_type = value.significand().nonzero_magnitude_start() + value.exponent(); + + let (plural_map, expected_exponent) = + self.plural_map_and_exponent_for_magnitude(log10_type); + if value.exponent() != i16::from(expected_exponent) { + return Err(CompactDecimalError::Exponent { + actual: value.exponent(), + expected: i16::from(expected_exponent), + log10_type, + }); + } + + Ok(FormattedCompactDecimal { + formatter: self, + plural_map, + value: Cow::Borrowed(value), + }) + } + + /// Returns the compact decimal exponent that should be used for a number of + /// the given magnitude when using this formatter. + /// + /// # Examples + /// ``` + /// use icu_compactdecimal::CompactDecimalFormatter; + /// use icu_locid::locale; + /// + /// let [long_french, long_japanese, long_bangla] = [locale!("fr"), locale!("ja"), locale!("bn")] + /// .map(|locale| { + /// CompactDecimalFormatter::try_new_long_unstable( + /// &icu_testdata::unstable(), + /// &locale.into(), + /// ) + /// .unwrap() + /// }); + /// /// French uses millions. + /// assert_eq!(long_french.compact_exponent_for_magnitude(6), 6); + /// /// Bangla uses lakhs. + /// assert_eq!(long_bangla.compact_exponent_for_magnitude(6), 5); + /// /// Japanese uses myriads. + /// assert_eq!(long_japanese.compact_exponent_for_magnitude(6), 4); + /// ``` + pub fn compact_exponent_for_magnitude(&self, magnitude: i16) -> u8 { + let (_, exponent) = self.plural_map_and_exponent_for_magnitude(magnitude); + exponent + } + + fn plural_map_and_exponent_for_magnitude( + &self, + magnitude: i16, + ) -> (Option>, u8) { + let plural_map = self + .compact_data + .get() + .patterns + .iter0() + .filter(|cursor| i16::from(*cursor.key0()) <= magnitude) + .last(); + let exponent = plural_map + .as_ref() + .and_then(|map| { + map.get1(&Count::Other) + .and_then(|pattern| u8::try_from(pattern.exponent).ok()) + }) + .unwrap_or(0); + (plural_map, exponent) + } +} diff --git a/experimental/compactdecimal/src/error.rs b/experimental/compactdecimal/src/error.rs index 3510f472d1f..f280b8f5b80 100644 --- a/experimental/compactdecimal/src/error.rs +++ b/experimental/compactdecimal/src/error.rs @@ -20,6 +20,18 @@ pub enum CompactDecimalError { /// An error originating from [`FixedDecimalFormatter`](icu_decimal::FixedDecimalFormatter). #[displaydoc("Error loading FixedDecimalFormatter: {0}")] Decimal(DecimalError), + /// An error due to a [`CompactDecimal`](fixed_decimal::CompactDecimal) with an + /// exponent inconsistent with the compact decimal data for the locale, e.g., + /// when formatting 1c5 in English (US). + #[displaydoc("Expected compact exponent {expected} for 10^{log10_type}, got {actual}")] + Exponent { + /// The compact decimal exponent passed to the formatter. + actual: i16, + /// The appropriate compact decimal exponent for a number of the given magnitude. + expected: i16, + /// The magnitude of the number being formatted. + log10_type: i16, + }, } impl From for CompactDecimalError { diff --git a/experimental/compactdecimal/src/format.rs b/experimental/compactdecimal/src/format.rs new file mode 100644 index 00000000000..cdba4d178f2 --- /dev/null +++ b/experimental/compactdecimal/src/format.rs @@ -0,0 +1,74 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use alloc::borrow::Cow; +use fixed_decimal::{CompactDecimal, FixedDecimal}; +use writeable::Writeable; +use zerovec::maps::ZeroMap2dCursor; + +use crate::compactdecimal::CompactDecimalFormatter; +use crate::provider::{Count, PatternULE}; + +/// An intermediate structure returned by [`CompactDecimalFormatter`](crate::CompactDecimalFormatter). +/// Use [`Writeable`][Writeable] to render the formatted decimal to a string or buffer. +pub struct FormattedCompactDecimal<'l> { + pub(crate) formatter: &'l CompactDecimalFormatter, + pub(crate) value: Cow<'l, CompactDecimal>, + pub(crate) plural_map: Option>, +} + +impl<'l> Writeable for FormattedCompactDecimal<'l> { + fn write_to(&self, sink: &mut W) -> core::result::Result<(), core::fmt::Error> + where + W: core::fmt::Write + ?Sized, + { + if self.value.exponent() == 0 { + self.formatter + .fixed_decimal_format + .format(self.value.significand()) + .write_to(sink) + } else { + let plural_map = self.plural_map.as_ref().ok_or(core::fmt::Error)?; + let chosen_pattern = (|| { + if self.value.significand() == &FixedDecimal::from(1) { + if let Some(pattern) = plural_map.get1(&Count::Explicit1) { + return Some(pattern); + } + } + let plural_category = self + .formatter + .plural_rules + .category_for(self.value.significand()); + plural_map + .get1(&plural_category.into()) + .or_else(|| plural_map.get1(&Count::Other)) + })() + .ok_or(core::fmt::Error)?; + match chosen_pattern.index { + u8::MAX => sink.write_str(&chosen_pattern.literal_text), + _ => { + let i = usize::from(chosen_pattern.index); + sink.write_str( + chosen_pattern + .literal_text + .get(..i) + .ok_or(core::fmt::Error)?, + )?; + self.formatter + .fixed_decimal_format + .format(self.value.significand()) + .write_to(sink)?; + sink.write_str( + chosen_pattern + .literal_text + .get(i..) + .ok_or(core::fmt::Error)?, + ) + } + } + } + } +} + +writeable::impl_display_with_writeable!(FormattedCompactDecimal<'_>); diff --git a/experimental/compactdecimal/src/lib.rs b/experimental/compactdecimal/src/lib.rs index 1715bd10669..f9c370c144d 100644 --- a/experimental/compactdecimal/src/lib.rs +++ b/experimental/compactdecimal/src/lib.rs @@ -25,14 +25,17 @@ clippy::panic, clippy::exhaustive_structs, clippy::exhaustive_enums, - missing_debug_implementations, + // TODO(#2266): enable missing_debug_implementations, ) )] #![warn(missing_docs)] extern crate alloc; +mod compactdecimal; mod error; +mod format; pub mod provider; +pub use compactdecimal::CompactDecimalFormatter; pub use error::CompactDecimalError; diff --git a/experimental/compactdecimal/src/provider.rs b/experimental/compactdecimal/src/provider.rs index dea38ff2a8b..fa4bcfd8bf8 100644 --- a/experimental/compactdecimal/src/provider.rs +++ b/experimental/compactdecimal/src/provider.rs @@ -12,7 +12,8 @@ //! Read more about data providers: [`icu_provider`] use alloc::borrow::Cow; -use icu_provider::{yoke, zerofrom}; +use icu_plurals::PluralCategory; +use icu_provider::{yoke, zerofrom, DataMarker}; use zerovec::ZeroMap2d; /// Relative time format V1 data struct. @@ -89,6 +90,20 @@ pub enum Count { // algorithm does not allow such a thing to arise. } +impl From for Count { + fn from(other: PluralCategory) -> Self { + use PluralCategory::*; + match other { + Zero => Count::Zero, + One => Count::One, + Two => Count::Two, + Few => Count::Few, + Many => Count::Many, + Other => Count::Other, + } + } +} + /// A compact decimal pattern, representing some literal text with an optional /// placeholder, and the power of 10 expressed by the text. #[derive( @@ -125,3 +140,8 @@ pub struct Pattern<'data> { /// " M" for the pattern "000 M" pub literal_text: Cow<'data, str>, } +pub(crate) struct ErasedCompactDecimalFormatDataV1Marker; + +impl DataMarker for ErasedCompactDecimalFormatDataV1Marker { + type Yokeable = CompactDecimalPatternDataV1<'static>; +} diff --git a/ffi/diplomat/ffi_coverage/tests/missing_apis.txt b/ffi/diplomat/ffi_coverage/tests/missing_apis.txt index f2d9eacb4af..9af7de9be7f 100644 --- a/ffi/diplomat/ffi_coverage/tests/missing_apis.txt +++ b/ffi/diplomat/ffi_coverage/tests/missing_apis.txt @@ -1,7 +1,9 @@ fixed_decimal::CompactDecimal#Struct fixed_decimal::CompactDecimal::exponent#FnInStruct +fixed_decimal::CompactDecimal::from_significand_and_exponent#FnInStruct fixed_decimal::CompactDecimal::from_str#FnInStruct fixed_decimal::CompactDecimal::into_significand#FnInStruct +fixed_decimal::CompactDecimal::significand#FnInStruct fixed_decimal::CompactDecimal::write_to#FnInStruct fixed_decimal::FixedInteger#Struct fixed_decimal::FixedInteger::from_str#FnInStruct diff --git a/provider/datagen/src/transform/cldr/decimal/compact_decimal_pattern.rs b/provider/datagen/src/transform/cldr/decimal/compact_decimal_pattern.rs index b1ad0934088..022615b5271 100644 --- a/provider/datagen/src/transform/cldr/decimal/compact_decimal_pattern.rs +++ b/provider/datagen/src/transform/cldr/decimal/compact_decimal_pattern.rs @@ -322,6 +322,22 @@ impl TryFrom<&DecimalFormat> for CompactDecimalPatternDataV1<'static> { ); } } + if !patterns + .iter() + .tuple_windows() + .all(|((_, low), (_, high))| { + low.get(&Count::Other).map(|p| p.exponent) + <= high.get(&Count::Other).map(|p| p.exponent) + }) + { + Err(format!( + "Compact decimal exponents should be nondecreasing: {:?}", + patterns + .iter() + .map(|(_, plural_map)| plural_map.get(&Count::Other).map(|p| p.exponent)) + .collect::>(), + ))?; + } // Deduplicate sequences of types that have the same plural map (up to =1), keeping the lowest type. // The pattern 0 for type 1 is implicit. let deduplicated_patterns = patterns diff --git a/provider/testdata/Cargo.toml b/provider/testdata/Cargo.toml index 584fdc83ce0..81e9693aec0 100644 --- a/provider/testdata/Cargo.toml +++ b/provider/testdata/Cargo.toml @@ -325,6 +325,7 @@ zerovec = { version = "0.9", path = "../../utils/zerovec" } icu_calendar = { version = "1.0.0", path = "../../components/calendar", default-features = false, optional = true } icu_casemapping = { version = "0.7.0", path = "../../experimental/casemapping", default-features = false, optional = true } icu_collator = { version = "1.0.0", path = "../../components/collator", default-features = false, optional = true } +icu_compactdecimal = { version = "0.1.0", path = "../../experimental/compactdecimal", default-features = false, optional = true } icu_datetime = { version = "1.0.0", path = "../../components/datetime", default-features = false, optional = true } icu_decimal = { version = "1.0.0", path = "../../components/decimal", default-features = false, optional = true } icu_displaynames = { version = "0.7.0", path = "../../experimental/displaynames", default-features = false, optional = true } diff --git a/utils/fixed_decimal/src/compact.rs b/utils/fixed_decimal/src/compact.rs index d20f8e41fcc..06aa884131f 100644 --- a/utils/fixed_decimal/src/compact.rs +++ b/utils/fixed_decimal/src/compact.rs @@ -26,6 +26,26 @@ pub struct CompactDecimal { } impl CompactDecimal { + /// Constructs a [`CompactDecimal`] from its significand and exponent. + pub fn from_significand_and_exponent(significand: FixedDecimal, exponent: u8) -> Self { + Self { + significand, + exponent: exponent.into(), + } + } + + /// Returns a reference to the significand of `self`. + /// ``` + /// # use fixed_decimal::CompactDecimal; + /// # use fixed_decimal::FixedDecimal; + /// # use std::str::FromStr; + /// # + /// assert_eq!(CompactDecimal::from_str("+1.20c6").unwrap().significand(), &FixedDecimal::from_str("+1.20").unwrap()); + /// ``` + pub fn significand(&self) -> &FixedDecimal { + &self.significand + } + /// Returns the significand of `self`. /// ``` /// # use fixed_decimal::CompactDecimal;