facebook/hermes

Intl.NumberFormat signDisplay option displays odd behaviour

Brawl345 opened this issue · 1 comments

Description

The signDisplay option for Intl.NumberFormat is bugged and has an odd behaviour. E.g. the option exceptZero outputs "+8,537,71+" but it should output +8.537,71 €. Don't know if this only affects Android.

Steps to reproduce

  1. Create a new React Native application
  2. Insert this code somewhere:
          <Text>{
              new Intl.NumberFormat('de-DE', {
                  style: 'currency',
                  currency: 'EUR',
                  maximumFractionDigits: 2,
                  minimumFractionDigits: 2,
                  roundingMode: 'floor',
                  signDisplay: 'exceptZero',
              }).format(8537.71)
          }</Text>
  1. Start the app on Android

React Native Version

0.74.4

Affected Platforms

Runtime - Android

Output of npx react-native info

System:
  OS: macOS 14.5
  CPU: (10) arm64 Apple M1 Max
  Memory: 307.52 MB / 32.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 20.14.0
    path: /etc/profiles/per-user/user/bin/node
  Yarn:
    version: 3.6.4
    path: /etc/profiles/per-user/user/bin/yarn
  npm:
    version: 10.7.0
    path: /etc/profiles/per-user/user/bin/npm
  Watchman:
    version: 2024.03.11.00
    path: /etc/profiles/per-user/user/bin/watchman
Managers:
  CocoaPods: Not Found
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 23.5
      - iOS 17.5
      - macOS 14.5
      - tvOS 17.5
      - visionOS 1.2
      - watchOS 10.5
  Android SDK:
    API Levels:
      - "30"
      - "31"
      - "32"
      - "33"
      - "33"
      - "34"
    Build Tools:
      - 30.0.1
      - 30.0.3
      - 31.0.0
      - 33.0.1
      - 34.0.0
    System Images:
      - android-22 | Google APIs ARM 64 v8a
      - android-31 | Google APIs ARM 64 v8a
      - android-33 | Wear OS 4 ARM 64 v8a
      - android-33 | Google APIs ARM 64 v8a
      - android-33 | Google Play ARM 64 v8a
      - android-34 | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2024.1 AI-241.18034.62.2411.12071903
  Xcode:
    version: 15.4/15F31d
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.12
    path: /usr/bin/javac
  Ruby:
    version: 2.6.10
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.2.0
    wanted: 18.2.0
  react-native:
    installed: 0.74.4
    wanted: 0.74.4
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false
iOS:
  hermesEnabled: Not found
  newArchEnabled: false

Stacktrace or Logs

n/a

Reproducer

https://github.com/Brawl345/rn-signDisplay-bug-reproducer

Screenshots and Videos

Screenshot_1722504869

I was trying to fix this issue.
After debugging I found out that current logic which handles signDisplay is breaking in these cases:

  • The old logic replaces positivePrefix with just the plus sign if negativePrefix is not empty. This means any existing currency symbol or other content in positivePrefix would be lost.
    Example:
    Given positivePrefix = ¥ and negativePrefix = -, the old logic would replace positivePrefix with +, resulting in + instead of +¥.
    Also In case JP locale it is not respecting already appended currencySymbol.

  • It adds a Prefix and Suffix both in this case:

new Intl.NumberFormat('de-DE', {
                  style: 'currency',
                  currency: 'EUR',
                  signDisplay: 'exceptZero',
              }).format(8537.71)`

Result: +8,537,71+
Expected: +8.537,71 €

So in order to fix this we surely need to revisit the logic written here:

public PlatformNumberFormatterICU setSignDisplay(

Solution:

          if (decimalFormat.getPositivePrefix().isEmpty()) {
            decimalFormat.setPositivePrefix(new String(new char[]{symbols.getPlusSign()}));
          } else {
            // Preserve the existing prefix, which may include the currency symbol, and prepend the plus sign
            decimalFormat.setPositivePrefix(new String(new char[]{symbols.getPlusSign()}) + decimalFormat.getPositivePrefix());
          }

I tested this in above failing cases.
Let me know your thoughts on this, or any edge case which you thought will break this.