summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp60
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/NumberFormatFunction.cpp4
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp4
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js70
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js78
5 files changed, 171 insertions, 45 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp
index d12071745f..1bc18c5b00 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormat.cpp
@@ -5,7 +5,9 @@
*/
#include <AK/Utf8View.h>
+#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibJS/Runtime/Array.h>
+#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Intl/NumberFormat.h>
#include <LibJS/Runtime/Intl/NumberFormatFunction.h>
@@ -251,21 +253,28 @@ static ALWAYS_INLINE int log10floor(Value number)
{
if (number.is_number())
return static_cast<int>(floor(log10(number.as_double())));
- VERIFY_NOT_REACHED();
+
+ // FIXME: Can we do this without string conversion?
+ auto as_string = number.as_bigint().big_integer().to_base(10);
+ return as_string.length() - 1;
}
-static Value multiply(GlobalObject&, Value lhs, i64 rhs)
+static Value multiply(GlobalObject& global_object, Value lhs, i64 rhs)
{
if (lhs.is_number())
return Value(lhs.as_double() * rhs);
- VERIFY_NOT_REACHED();
+
+ auto rhs_bigint = Crypto::SignedBigInteger::create_from(rhs);
+ return js_bigint(global_object.vm(), lhs.as_bigint().big_integer().multiplied_by(rhs_bigint));
}
-static Value divide(GlobalObject&, Value lhs, i64 rhs)
+static Value divide(GlobalObject& global_object, Value lhs, i64 rhs)
{
if (lhs.is_number())
return Value(lhs.as_double() / rhs);
- VERIFY_NOT_REACHED();
+
+ auto rhs_bigint = Crypto::SignedBigInteger::create_from(rhs);
+ return js_bigint(global_object.vm(), lhs.as_bigint().big_integer().divided_by(rhs_bigint).quotient);
}
static ALWAYS_INLINE Value multiply_by_power(GlobalObject& global_object, Value number, i64 exponent)
@@ -286,42 +295,42 @@ static ALWAYS_INLINE Value rounded(Value number)
{
if (number.is_number())
return Value(round(number.as_double()));
- VERIFY_NOT_REACHED();
+ return number;
}
static ALWAYS_INLINE bool is_zero(Value number)
{
if (number.is_number())
return number.as_double() == 0.0;
- VERIFY_NOT_REACHED();
+ return number.as_bigint().big_integer() == Crypto::SignedBigInteger::create_from(0);
}
static ALWAYS_INLINE bool is_greater_than(Value number, i64 rhs)
{
if (number.is_number())
return number.as_double() > rhs;
- VERIFY_NOT_REACHED();
+ return number.as_bigint().big_integer() > Crypto::SignedBigInteger::create_from(rhs);
}
static ALWAYS_INLINE bool is_greater_than_or_equal(Value number, i64 rhs)
{
if (number.is_number())
return number.as_double() >= rhs;
- VERIFY_NOT_REACHED();
+ return number.as_bigint().big_integer() >= Crypto::SignedBigInteger::create_from(rhs);
}
static ALWAYS_INLINE bool is_less_than(Value number, i64 rhs)
{
if (number.is_number())
return number.as_double() < rhs;
- VERIFY_NOT_REACHED();
+ return number.as_bigint().big_integer() < Crypto::SignedBigInteger::create_from(rhs);
}
static ALWAYS_INLINE String number_to_string(Value number)
{
if (number.is_number())
return number.to_string_without_side_effects();
- VERIFY_NOT_REACHED();
+ return number.as_bigint().big_integer().to_base(10);
}
// 15.1.1 SetNumberFormatDigitOptions ( intlObj, options, mnfdDefault, mxfdDefault, notation ), https://tc39.es/ecma402/#sec-setnfdigitoptions
@@ -833,7 +842,7 @@ Vector<PatternPartition> partition_notation_sub_pattern(GlobalObject& global_obj
result.append({ "nan"sv, move(formatted_string) });
}
// 3. Else if x is a non-finite Number, then
- else if (!number.is_finite_number()) {
+ else if (number.is_number() && !number.is_finite_number()) {
// a. Append a new Record { [[Type]]: "infinity", [[Value]]: n } as the last element of result.
result.append({ "infinity"sv, move(formatted_string) });
}
@@ -1089,7 +1098,6 @@ RawFormatResult to_raw_precision(GlobalObject& global_object, Value number, int
RawFormatResult result {};
// 1. Set x to ā„(x).
- // FIXME: Support BigInt number formatting.
// 2. Let p be maxPrecision.
int precision = max_precision;
@@ -1116,7 +1124,23 @@ RawFormatResult to_raw_precision(GlobalObject& global_object, Value number, int
// a. Let e and n be integers such that 10^(pā€“1) ā‰¤ n < 10^p and for which n Ɨ 10^(eā€“p+1) ā€“ x is as close to zero as possible.
// If there are two such sets of e and n, pick the e and n for which n Ɨ 10^(eā€“p+1) is larger.
exponent = log10floor(number);
- auto n = rounded(divide_by_power(global_object, number, exponent - precision + 1));
+ Value n;
+
+ if (number.is_number()) {
+ n = rounded(divide_by_power(global_object, number, exponent - precision + 1));
+ } else {
+ // NOTE: In order to round the BigInt to the proper precision, this computation is initially off by a
+ // factor of 10. This lets us inspect the ones digit and then round up if needed.
+ n = divide_by_power(global_object, number, exponent - precision);
+
+ // FIXME: Can we do this without string conversion?
+ auto digits = n.as_bigint().big_integer().to_base(10);
+ auto digit = digits.substring_view(digits.length() - 1);
+
+ n = divide(global_object, n, 10);
+ if (digit.to_uint().value() >= 5)
+ n = js_bigint(global_object.vm(), n.as_bigint().big_integer().plus(Crypto::SignedBigInteger::create_from(1)));
+ }
// b. Let m be the String consisting of the digits of the decimal representation of n (in order, with no leading zeroes).
result.formatted_string = number_to_string(n);
@@ -1180,7 +1204,6 @@ RawFormatResult to_raw_fixed(GlobalObject& global_object, Value number, int min_
RawFormatResult result {};
// 1. Set x to ā„(x).
- // FIXME: Support BigInt number formatting.
// 2. Let f be maxFraction.
int fraction = max_fraction;
@@ -1318,7 +1341,10 @@ Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& nu
auto as_number = [&]() {
if (number.is_number())
return number.as_double();
- VERIFY_NOT_REACHED();
+
+ // FIXME: This should be okay for now as our naive Unicode::select_pattern_with_plurality implementation
+ // checks against just a few specific small values. But revisit this if precision becomes a concern.
+ return number.as_bigint().big_integer().to_double();
};
// 1. Let localeData be %NumberFormat%.[[LocaleData]].
@@ -1400,7 +1426,7 @@ Optional<Variant<StringView, String>> get_number_format_pattern(NumberFormat& nu
StringView pattern;
- bool is_positive_zero = number.is_positive_zero();
+ bool is_positive_zero = number.is_positive_zero() || (number.is_bigint() && is_zero(number));
bool is_negative_zero = number.is_negative_zero();
bool is_nan = number.is_nan();
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatFunction.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatFunction.cpp
index 1a9dc04829..842c9472ee 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatFunction.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatFunction.cpp
@@ -44,10 +44,6 @@ ThrowCompletionOr<Value> NumberFormatFunction::call()
// 4. Let x be ? ToNumeric(value).
value = TRY(value.to_numeric(global_object));
- // FIXME: Support BigInt number formatting.
- if (value.is_bigint())
- return vm.throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "BigInt number formatting");
-
// 5. Return ? FormatNumeric(nf, x).
// Note: Our implementation of FormatNumeric does not throw.
auto formatted = format_numeric(global_object, m_number_format, value);
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp
index be9e1de020..3880891147 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/NumberFormatPrototype.cpp
@@ -70,10 +70,6 @@ JS_DEFINE_NATIVE_FUNCTION(NumberFormatPrototype::format_to_parts)
// 3. Let x be ? ToNumeric(value).
value = TRY(value.to_numeric(global_object));
- // FIXME: Support BigInt number formatting.
- if (value.is_bigint())
- return vm.throw_completion<InternalError>(global_object, ErrorType::NotImplemented, "BigInt number formatting");
-
// 4. Return ? FormatNumericToParts(nf, x).
// Note: Our implementation of FormatNumericToParts does not throw.
return format_numeric_to_parts(global_object, *number_format, value);
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js
index e14df84869..c95735680a 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.format.js
@@ -14,16 +14,6 @@ describe("errors", () => {
Intl.NumberFormat().format(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
});
-
- // FIXME: Remove this and add BigInt tests when BigInt number formatting is supported.
- test("bigint", () => {
- expect(() => {
- Intl.NumberFormat().format(1n);
- }).toThrowWithMessage(
- InternalError,
- "BigInt number formatting is not implemented in LibJS"
- );
- });
});
describe("special values", () => {
@@ -1019,3 +1009,63 @@ describe("style=unit", () => {
expect(ja.format(123)).toBe("123km/h");
});
});
+
+describe("bigint", () => {
+ test("default", () => {
+ const en = new Intl.NumberFormat("en");
+ expect(en.format(1n)).toBe("1");
+ expect(en.format(12n)).toBe("12");
+ expect(en.format(123n)).toBe("123");
+ expect(en.format(123456789123456789123456789123456789n)).toBe(
+ "123,456,789,123,456,789,123,456,789,123,456,789"
+ );
+
+ const ar = new Intl.NumberFormat("ar");
+ expect(ar.format(1n)).toBe("\u0661");
+ expect(ar.format(12n)).toBe("\u0661\u0662");
+ expect(ar.format(123n)).toBe("\u0661\u0662\u0663");
+ expect(ar.format(123456789123456789123456789123456789n)).toBe(
+ "\u0661\u0662\u0663\u066c\u0664\u0665\u0666\u066c\u0667\u0668\u0669\u066c\u0661\u0662\u0663\u066c\u0664\u0665\u0666\u066c\u0667\u0668\u0669\u066c\u0661\u0662\u0663\u066c\u0664\u0665\u0666\u066c\u0667\u0668\u0669\u066c\u0661\u0662\u0663\u066c\u0664\u0665\u0666\u066c\u0667\u0668\u0669"
+ );
+ });
+
+ test("integer digits", () => {
+ const en = new Intl.NumberFormat("en", { minimumIntegerDigits: 2 });
+ expect(en.format(1n)).toBe("01");
+ expect(en.format(12n)).toBe("12");
+ expect(en.format(123n)).toBe("123");
+
+ const ar = new Intl.NumberFormat("ar", { minimumIntegerDigits: 2 });
+ expect(ar.format(1n)).toBe("\u0660\u0661");
+ expect(ar.format(12n)).toBe("\u0661\u0662");
+ expect(ar.format(123n)).toBe("\u0661\u0662\u0663");
+ });
+
+ test("significant digits", () => {
+ const en = new Intl.NumberFormat("en", {
+ minimumSignificantDigits: 4,
+ maximumSignificantDigits: 6,
+ });
+ expect(en.format(1n)).toBe("1.000");
+ expect(en.format(12n)).toBe("12.00");
+ expect(en.format(123n)).toBe("123.0");
+ expect(en.format(1234n)).toBe("1,234");
+ expect(en.format(12345n)).toBe("12,345");
+ expect(en.format(123456n)).toBe("123,456");
+ expect(en.format(1234567n)).toBe("1,234,570");
+ expect(en.format(1234561n)).toBe("1,234,560");
+
+ const ar = new Intl.NumberFormat("ar", {
+ minimumSignificantDigits: 4,
+ maximumSignificantDigits: 6,
+ });
+ expect(ar.format(1n)).toBe("\u0661\u066b\u0660\u0660\u0660");
+ expect(ar.format(12n)).toBe("\u0661\u0662\u066b\u0660\u0660");
+ expect(ar.format(123n)).toBe("\u0661\u0662\u0663\u066b\u0660");
+ expect(ar.format(1234n)).toBe("\u0661\u066c\u0662\u0663\u0664");
+ expect(ar.format(12345n)).toBe("\u0661\u0662\u066c\u0663\u0664\u0665");
+ expect(ar.format(123456n)).toBe("\u0661\u0662\u0663\u066c\u0664\u0665\u0666");
+ expect(ar.format(1234567n)).toBe("\u0661\u066c\u0662\u0663\u0664\u066c\u0665\u0667\u0660");
+ expect(ar.format(1234561n)).toBe("\u0661\u066c\u0662\u0663\u0664\u066c\u0665\u0666\u0660");
+ });
+});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js
index 5a144e1ebc..ceddabddd4 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/NumberFormat/NumberFormat.prototype.formatToParts.js
@@ -10,16 +10,6 @@ describe("errors", () => {
Intl.NumberFormat().formatToParts(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
});
-
- // FIXME: Remove this and add BigInt tests when BigInt number formatting is supported.
- test("bigint", () => {
- expect(() => {
- Intl.NumberFormat().formatToParts(1n);
- }).toThrowWithMessage(
- InternalError,
- "BigInt number formatting is not implemented in LibJS"
- );
- });
});
describe("special values", () => {
@@ -1310,3 +1300,71 @@ describe("style=unit", () => {
]);
});
});
+
+describe("bigint", () => {
+ test("default", () => {
+ const en = new Intl.NumberFormat("en");
+ expect(en.formatToParts(123456n)).toEqual([
+ { type: "integer", value: "123" },
+ { type: "group", value: "," },
+ { type: "integer", value: "456" },
+ ]);
+
+ const ar = new Intl.NumberFormat("ar");
+ expect(ar.formatToParts(123456n)).toEqual([
+ { type: "integer", value: "\u0661\u0662\u0663" },
+ { type: "group", value: "\u066c" },
+ { type: "integer", value: "\u0664\u0665\u0666" },
+ ]);
+ });
+
+ test("useGrouping=false", () => {
+ const en = new Intl.NumberFormat("en", { useGrouping: false });
+ expect(en.formatToParts(123456n)).toEqual([{ type: "integer", value: "123456" }]);
+
+ const ar = new Intl.NumberFormat("ar", { useGrouping: false });
+ expect(ar.formatToParts(123456n)).toEqual([
+ { type: "integer", value: "\u0661\u0662\u0663\u0664\u0665\u0666" },
+ ]);
+ });
+
+ test("significant digits", () => {
+ const en = new Intl.NumberFormat("en", {
+ minimumSignificantDigits: 4,
+ maximumSignificantDigits: 6,
+ });
+ expect(en.formatToParts(1234567n)).toEqual([
+ { type: "integer", value: "1" },
+ { type: "group", value: "," },
+ { type: "integer", value: "234" },
+ { type: "group", value: "," },
+ { type: "integer", value: "570" },
+ ]);
+ expect(en.formatToParts(1234561n)).toEqual([
+ { type: "integer", value: "1" },
+ { type: "group", value: "," },
+ { type: "integer", value: "234" },
+ { type: "group", value: "," },
+ { type: "integer", value: "560" },
+ ]);
+
+ const ar = new Intl.NumberFormat("ar", {
+ minimumSignificantDigits: 4,
+ maximumSignificantDigits: 6,
+ });
+ expect(ar.formatToParts(1234567n)).toEqual([
+ { type: "integer", value: "\u0661" },
+ { type: "group", value: "\u066c" },
+ { type: "integer", value: "\u0662\u0663\u0664" },
+ { type: "group", value: "\u066c" },
+ { type: "integer", value: "\u0665\u0667\u0660" },
+ ]);
+ expect(ar.formatToParts(1234561n)).toEqual([
+ { type: "integer", value: "\u0661" },
+ { type: "group", value: "\u066c" },
+ { type: "integer", value: "\u0662\u0663\u0664" },
+ { type: "group", value: "\u066c" },
+ { type: "integer", value: "\u0665\u0666\u0660" },
+ ]);
+ });
+});