Skip to content

feat: Implement currency precision fallback#8169

Open
younies wants to merge 23 commits into
unicode-org:mainfrom
younies:younies_currency_precision
Open

feat: Implement currency precision fallback#8169
younies wants to merge 23 commits into
unicode-org:mainfrom
younies:younies_currency_precision

Conversation

@younies

@younies younies commented Jul 2, 2026

Copy link
Copy Markdown
Member

This PR implements currency precision fallback, integrates pattern precision into CurrencyEssentials, and adds support for per-currency fallback (i.e., resolving precision based on the specific currency, such as 0 decimal places for JPY or 2 decimal places for USD, before falling back to a global default).

It is stacked on top of #8150.

Changelog

  1. Add Pattern Precision into CurrencyEssentials:

    • Added fractions_vec, standard_index, and standard_next_to_alpha fields to CurrencyEssentials to store locale-specific pattern precision.
    • Updated the datagen provider for CurrencyEssentialsV1 to extract this precision from CLDR patterns at datagen time.
    • This optimizes Short and Narrow formatters by reducing the number of loaded data markers from 2 to 1.
  2. Implement Currency Precision Fallback:

    • Updated CurrencyFormatter and CompactCurrencyFormatter to use CurrencyEssentials for locale-specific precision.
    • Implemented a 3-step fallback for resolving currency precision:
      1. Currency-specific override: Look up the currency in the global CurrencyFractionsV1 map (e.g., JPY = 0).
      2. Locale-specific pattern precision: If not found, use the precision of the selected pattern from CurrencyEssentials (only for Short/Narrow formats).
      3. Global default: Fall back to the global default in CurrencyFractionsV1 (2 decimals).
    • For Long format, since it doesn't use CurrencyEssentials, it bypasses step 2 and falls back directly to the global default (or currency override).
  3. Support Per-Currency Fallback (ISO Code Fallback):

    • Updated CurrencyFormatter to make CurrencyExtendedDataV1 (display names) optional.
    • If the display name data for a currency is missing (e.g., for an unsupported or fake currency like XYZ), it now gracefully falls back to using the currency's ISO code as the display name instead of failing construction.
  4. Tests and Data:

    • Added tests to verify the Long format fallback to ISO code (test_long_fallback_to_iso).
    • Updated existing tests to expect the correct rounded values (since precision is now active).
    • Regenerated baked data.

TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29

younies added 8 commits July 2, 2026 11:11
Implement the ValueRepresentation trait and Decimal marker struct.
Migrate CurrencyFormatter to CurrencyFormatter<Decimal> with
try_new_short and try_new_narrow constructors, removing the old
non-generic try_new constructors. Update tests accordingly.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
- Update try_new_narrow and try_new_narrow_unstable to use robust fallback loading, ensuring consistency with short constructors and supporting numbering system overrides.
- Add explanatory comments and TODOs for the manual constructors explaining the cross-crate dependency constraint.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
…sentation

- Add TODO(unicode-org#8146) to format_fixed_decimal to track the discussion about whether FixedDecimal is the correct input type for currency formatting.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
- Remove test_en_us_aud to keep the test suite DRY, addressing reviewer feedback. CAD and USD tests already provide full coverage for the dollar currency prefix and narrow/short resolution logic.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
- Rename locale variable to prefs of type CurrencyFormatterPreferences across all test cases in format.rs for naming consistency and to address reviewer feedback.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
- Add test cases to test_numbering_system_override to verify that try_new_narrow correctly respects numbering system overrides (like ar-EG-u-nu-latn), ensuring consistency with try_new_short. - Rename short test variables for clarity.

TAG=agy
CONV=0f8e0e91-077a-4704-b63f-0c02f37cbc41
@younies younies changed the base branch from currency-long-decimal to main July 2, 2026 14:04
@younies younies changed the base branch from main to currency-long-decimal July 2, 2026 14:04
@younies younies force-pushed the currency-long-decimal branch from e845d8a to 82a26af Compare July 2, 2026 14:07
@younies younies changed the title Younies currency precision feat: Implement currency precision fallback Jul 2, 2026
pattern.interpolate((
self.decimal_formatter
.format_unsigned(icu_decimal::Cow::Borrowed(&value.absolute)),
.format_unsigned(icu_decimal::Cow::Owned(value.absolute)),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Owned ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need Cow::Owned here because value is a local variable.

In format_fixed_decimal, we apply precision which clones and modifies the input, creating a new local FixedDecimal (value). If we used Cow::Borrowed(&value.absolute), the returned FormattedDecimal would borrow from this local variable, which would result in a lifetime error since the local variable is dropped when the function returns.

By using Cow::Owned(value.absolute), we move the owned data into the Cow so it can be safely returned. Since UnsignedDecimal uses inline storage for typical digit counts, this Cow::Owned usually does not trigger heap allocations.

An alternative to avoid Cow::Owned would be to define a custom FormattedCurrency struct that owns the modified FixedDecimal and implements Writeable, but that would add more complexity for now.

Comment on lines +397 to +399
})
.allow_identifier_not_found()?
.map(|res| res.payload);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we allow this ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We allow IdentifierNotFound here to support fallback to the ISO code when the localized display name for a currency is missing.

According to UTS #35 (LDML Part 3: Numbers) Section 3.11.1:

If no displayName element is found at any level in the fallback chain, the system uses the currency code itself (e.g., "ZWD") as the display name.

If we didn't use allow_identifier_not_found()?, the formatter construction would fail completely for unsupported currencies. By allowing it, we can still format the number and gracefully fall back to the ISO code (e.g., 12,345.67 XYZ).

younies added 14 commits July 3, 2026 12:50
Revert manual precision additions to CurrencyEssentials and
CurrencyExtendedData, and instead load and use the existing
CurrencyFractionsV1 singleton to resolve currency precision.

Also apply a fix to check_pattern in essentials provider to allow
mismatches in grouping separators (Indian vs Western).

TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
Introduce CurrencyPatternFractionsV1 marker to store precision extracted
from CLDR currency patterns at datagen time.

Update CurrencyFormatter and CompactCurrencyFormatter to load this data
and implement a 3-step fallback for resolving currency precision:
1. Global currency-specific overrides (from CurrencyFractionsV1).
2. Locale-specific pattern precision (from CurrencyPatternFractionsV1).
3. Global default (2 decimals).

TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
Update CurrencyFormatter to make CurrencyExtendedDataV1 (display names)
optional. If the extended data for a currency is missing (e.g., for a
fake or unsupported currency), we now fall back to using its ISO code
as the display name, instead of failing construction.

Add test_long_fallback_to_iso to verify this behavior.

TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
Following the decision that Long formatting does not need locale-specific
pattern precision fallback (as it has no pattern-defined precision and
should fall back to the global default), we merge the pattern precision
data (fractions_vec, standard_index, standard_next_to_alpha) directly
into CurrencyEssentials.

This optimizes Short and Narrow formatting by reducing the number of
loaded data markers from 2 to 1, while Long formatting remains optimized
by only loading CurrencyFractionsV1 (global overrides).

Updates:
- Removed CurrencyPatternFractionsV1 marker.
- Added fractions_vec, standard_index, standard_next_to_alpha to CurrencyEssentials.
- Updated datagen to populate these fields in CurrencyEssentialsV1.
- Updated CurrencyFormatter and CompactCurrencyFormatter to use CurrencyEssentials for locale-specific precision.
- Regenerated baked data.

TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
- Pass CurrencyCode by value in CompactCurrencyFormatter::resolve_fraction_info.
- Collapse nested if statements using Option::filter in resolve_fraction_info (both formatters).
- Remove unused load_with_fallback from compact_formatter.rs.
- Remove unused ZeroVec import from fractions.rs.
- Remove unnecessary qualifications in essentials.rs (provider).
- Remove needless borrow of locale in essentials.rs (provider).

TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
This aligns the function name with its return type (FractionInfo) and makes
it more descriptive of what it actually does.

TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
Added comments in formatter.rs referencing UTS unicode-org#35 Section 3.11.1
explaining that we fall back to the currency code itself if the
displayName is not found.

TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
TAG=agy
CONV=4e806c61-d22c-4821-8a7f-6c8e0ebb5a29
@younies younies changed the base branch from currency-long-decimal to main July 3, 2026 12:53
@younies younies force-pushed the younies_currency_precision branch from 375131d to 99b5d32 Compare July 3, 2026 12:54
)
}

fn resolve_fraction_info(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment to this function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added doc comments to both `compact_formatter.rs` and `formatter.rs`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant