From 1da1922358f8e60ce37b8eef618f4dfc51203b7f Mon Sep 17 00:00:00 2001 From: Luis Date: Sun, 19 Nov 2023 17:18:27 +0100 Subject: [PATCH] Moving some logic to created NumberItlHelper and documenting. --- Jint.Tests/Runtime/NumberTests.cs | 2 +- Jint/Native/Number/NumberIntlHelper.cs | 42 ++++++++++++++++++++++++++ Jint/Native/Number/NumberPrototype.cs | 28 +---------------- 3 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 Jint/Native/Number/NumberIntlHelper.cs diff --git a/Jint.Tests/Runtime/NumberTests.cs b/Jint.Tests/Runtime/NumberTests.cs index 0c433a755b..047bd5bda1 100644 --- a/Jint.Tests/Runtime/NumberTests.cs +++ b/Jint.Tests/Runtime/NumberTests.cs @@ -64,7 +64,7 @@ public void ParseFloat(string input, double result) Assert.Equal(result, value); } - // Results from node -v v18.13.0. + // Results from node -v v18.18.0. [Theory] // Thousand separators. [InlineData("1000000", "en-US", "1,000,000")] diff --git a/Jint/Native/Number/NumberIntlHelper.cs b/Jint/Native/Number/NumberIntlHelper.cs new file mode 100644 index 0000000000..4e17d82cab --- /dev/null +++ b/Jint/Native/Number/NumberIntlHelper.cs @@ -0,0 +1,42 @@ +// Ideally, internacionalization formats implemented through the ECMAScript standards would follow this: +// https://tc39.es/ecma402/#sec-initializedatetimeformat +// https://tc39.es/ecma402/#sec-canonicalizelocalelist +// Along with the implementations of whatever is subsequenlty called. + +// As this is not in place (See TODOS in NumberFormatConstructor and DateTimeFormatConstructor) we can arrange +// values that will match the JS behavior using the host logic. This bypasses the ECMAScript standards but can +// do the job for the most common use cases and cultures meanwhile. + +namespace Jint.Native.Number +{ + internal class NumberIntlHelper + { + // Obtined empirically. For all cultures tested, we get a maximum of 3 decimal digits. + private const int JS_MAX_DECIMAL_DIGIT_COUNT = 3; + + /// + /// Checks the powers of 10 of number to count the number of decimal digits. + /// Returns a clamped JS_MAX_DECIMAL_DIGIT_COUNT count. + /// JavaScript will use the shortest representation that accurately represents the value + /// and clamp the decimal digits to JS_MAX_DECIMAL_DIGIT_COUNT. + /// C# fills the digits with zeros up to the culture's numberFormat.NumberDecimalDigits + /// and does not provide the same max (numberFormat.NumberDecimalDigits != JS_MAX_DECIMAL_DIGIT_COUNT). + /// This function matches the JS behaviour for the decimal digits returned, this is the actual decimal + /// digits for a number (with no zeros fill) clamped to JS_MAX_DECIMAL_DIGIT_COUNT. + /// + public static int GetDecimalDigitCount(double number) + { + for (int i = 0; i < JS_MAX_DECIMAL_DIGIT_COUNT; i++) + { + var powOf10 = number * System.Math.Pow(10, i); + bool isInteger = powOf10 == ((int) powOf10); + if (isInteger) + { + return i; + } + } + + return JS_MAX_DECIMAL_DIGIT_COUNT; + } + } +} diff --git a/Jint/Native/Number/NumberPrototype.cs b/Jint/Native/Number/NumberPrototype.cs index 050107f1e5..dc2a96e8e5 100644 --- a/Jint/Native/Number/NumberPrototype.cs +++ b/Jint/Native/Number/NumberPrototype.cs @@ -1,7 +1,6 @@ using System.Diagnostics; using System.Globalization; using System.Text; -using System; using Jint.Collections; using Jint.Native.Number.Dtoa; using Jint.Native.Object; @@ -47,31 +46,6 @@ protected override void Initialize() SetProperties(properties); } - /// - /// Checks the powers of 10 of to count the number of decimal digits. - /// Returns a clamped . - /// JavaScript will use the shortest representation that accurately represents the value. - /// c# fills the digits with zeros up to the culture's NumberDecimalDigits. - /// Here we get the decimal digit count digits to match JS behavior and avoid extra zeros. - /// - - private int GetDecimalDigitCount(double number, int maxDecimalDigitCount) - { - var counter = 0; - for (int i = 0; i < maxDecimalDigitCount; i++) - { - var powOf10 = number * System.Math.Pow(10, i); - bool isInteger = powOf10 == ((int) powOf10); - if (isInteger) - { - break; - } - counter++; - } - - return counter; - } - private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments) { if (!thisObject.IsNumber() && ReferenceEquals(thisObject.TryCast(), null)) @@ -113,7 +87,7 @@ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments) try { numberFormat = (NumberFormatInfo) CultureInfo.GetCultureInfo(cultureArg).NumberFormat.Clone(); - int decDigitCount = GetDecimalDigitCount(m, numberFormat.NumberDecimalDigits); + int decDigitCount = NumberIntlHelper.GetDecimalDigitCount(m); numberFormat.NumberDecimalDigits = decDigitCount; } catch (CultureNotFoundException)