From c31392510aa21d3ce5000f19a12dcf43abf9613d Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Sat, 19 Jun 2021 17:02:42 +0300 Subject: [PATCH] LibJS: Add the Number.prototype.toFixed method --- .../LibJS/Runtime/CommonPropertyNames.h | 1 + Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + .../LibJS/Runtime/NumberPrototype.cpp | 32 ++++++++++++++ .../Libraries/LibJS/Runtime/NumberPrototype.h | 1 + .../Number/Number.prototype.toFixed.js | 44 +++++++++++++++++++ 5 files changed, 79 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 504175cd94..693a8b1257 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -297,6 +297,7 @@ namespace JS { P(test) \ P(then) \ P(toDateString) \ + P(toFixed) \ P(toGMTString) \ P(toISOString) \ P(toJSON) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index cc18ee3e7c..22358b5874 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -32,6 +32,7 @@ M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object") \ M(InvalidAssignToConst, "Invalid assignment to const variable") \ M(InvalidCodePoint, "Invalid code point {}, must be an integer no less than 0 and no greater than 0x10FFFF") \ + M(InvalidFractionDigits, "Fraction Digits must be an integer no less than 0, and no greater than 100") \ M(InvalidHint, "Invalid hint: \"{}\"") \ M(InvalidIndex, "Index must be a positive integer") \ M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \ diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp index c5304443fd..831a74e943 100644 --- a/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.cpp @@ -34,6 +34,7 @@ void NumberPrototype::initialize(GlobalObject& object) auto& vm = this->vm(); Object::initialize(object); u8 attr = Attribute::Configurable | Attribute::Writable; + define_native_function(vm.names.toFixed, to_fixed, 1, attr); define_native_function(vm.names.toString, to_string, 1, attr); define_native_function(vm.names.valueOf, value_of, 0, attr); } @@ -54,6 +55,37 @@ static Value this_number_value(GlobalObject& global_object, Value value) return {}; } +// 21.1.3.3 Number.prototype.toFixed ( fractionDigits ), https://tc39.es/ecma262/#sec-number.prototype.tofixed +JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_fixed) +{ + auto number_value = this_number_value(global_object, vm.this_value(global_object)); + if (vm.exception()) + return {}; + + auto fraction_digits = vm.argument(0).to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + if (!vm.argument(0).is_finite_number()) { + vm.throw_exception(global_object, ErrorType::InvalidFractionDigits); + return {}; + } + + if (fraction_digits < 0 || fraction_digits > 100) { + vm.throw_exception(global_object, ErrorType::InvalidFractionDigits); + return {}; + } + + if (!number_value.is_finite_number()) + return js_string(vm, number_value.to_string(global_object)); + + auto number = number_value.as_double(); + if (fabs(number) >= 1e+21) + return js_string(vm, number_value.to_string(global_object)); + + return js_string(vm, String::formatted("{:0.{1}}", number, static_cast(fraction_digits))); +} + // 21.1.3.6 Number.prototype.toString ( [ radix ] ), https://tc39.es/ecma262/#sec-number.prototype.tostring JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_string) { diff --git a/Userland/Libraries/LibJS/Runtime/NumberPrototype.h b/Userland/Libraries/LibJS/Runtime/NumberPrototype.h index dece015294..ed36778376 100644 --- a/Userland/Libraries/LibJS/Runtime/NumberPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/NumberPrototype.h @@ -18,6 +18,7 @@ public: virtual void initialize(GlobalObject&) override; virtual ~NumberPrototype() override; + JS_DECLARE_NATIVE_FUNCTION(to_fixed); JS_DECLARE_NATIVE_FUNCTION(to_string); JS_DECLARE_NATIVE_FUNCTION(value_of); }; diff --git a/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js new file mode 100644 index 0000000000..12f99616cd --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Number/Number.prototype.toFixed.js @@ -0,0 +1,44 @@ +describe("correct behavior", () => { + test("length", () => { + expect(Number.prototype.toFixed).toHaveLength(1); + }); + + test("basic functionality", () => { + [ + [0, 5, "0.00000"], + [Infinity, 6, "Infinity"], + [-Infinity, 7, "-Infinity"], + [NaN, 8, "NaN"], + [12.81646112, 3, "12.816"], + [84.23, 4, "84.2300"], + [3.00003, 5, "3.00003"], + // Numbers >= 1e+21 + [1e21, 5, "1e+21"], + [1e22, 0, "1e+22"], + ].forEach(testCase => { + expect(testCase[0].toFixed(testCase[1])).toBe(testCase[2]); + }); + }); + + test("decimal fixed digits gets converted to int", () => { + expect((30.521).toFixed(1.9)).toBe("30.5"); + expect((30.521).toFixed(2.2)).toBe("30.52"); + }); +}); + +test("errors", () => { + test("must be called with numeric |this|", () => { + [true, [], {}, Symbol("foo"), "bar", 1n].forEach(value => { + expect(() => Number.prototype.toFixed.call(value)).toThrowWithMessage( + TypeError, + "Not a Number object" + ); + }); + }); + + test("fixed digits RangeError", () => { + [-Infinity, -5, 105, Infinity, NaN].forEach(value => { + expect(() => (0).toFixed(value)).toThrow(RangeError); + }); + }); +});