mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 19:17:44 +00:00
LibJS: Implement Number.prototype.toPrecision
As noted in the prototype comments, this implementation becomes less accurate as the precision approaches the limit of 100. For example: (3).toPrecision(100) Should result in "3." followed by 99 "0"s. However, due to the loss of accuracy in the floating point computations, we currently result in "2.9999999...".
This commit is contained in:
parent
f1eb975a7a
commit
dc984c53d8
5 changed files with 260 additions and 0 deletions
|
@ -468,6 +468,7 @@ namespace JS {
|
|||
P(toPlainMonthDay) \
|
||||
P(toPlainTime) \
|
||||
P(toPlainYearMonth) \
|
||||
P(toPrecision) \
|
||||
P(toString) \
|
||||
P(total) \
|
||||
P(toTemporalInstant) \
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
M(InvalidIndex, "Index must be a positive integer") \
|
||||
M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment") \
|
||||
M(InvalidLength, "Invalid {} length") \
|
||||
M(InvalidPrecision, "Precision must be an integer no less than 1, and no greater than 100") \
|
||||
M(InvalidTimeValue, "Invalid time value") \
|
||||
M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36") \
|
||||
M(IsNotA, "{} is not a {}") \
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <LibJS/Runtime/Intl/NumberFormatConstructor.h>
|
||||
#include <LibJS/Runtime/NumberObject.h>
|
||||
#include <LibJS/Runtime/NumberPrototype.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -29,6 +30,23 @@ static const u8 max_precision_for_radix[37] = {
|
|||
|
||||
static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
static String decimal_digits_to_string(double number)
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
||||
double integral_part = 0;
|
||||
(void)modf(number, &integral_part);
|
||||
|
||||
while (integral_part > 0) {
|
||||
auto index = static_cast<size_t>(fmod(integral_part, 10));
|
||||
builder.append(digits[index]);
|
||||
|
||||
integral_part = floor(integral_part / 10.0);
|
||||
}
|
||||
|
||||
return builder.build().reverse();
|
||||
}
|
||||
|
||||
NumberPrototype::NumberPrototype(GlobalObject& global_object)
|
||||
: NumberObject(0, *global_object.object_prototype())
|
||||
{
|
||||
|
@ -41,6 +59,7 @@ void NumberPrototype::initialize(GlobalObject& object)
|
|||
u8 attr = Attribute::Configurable | Attribute::Writable;
|
||||
define_native_function(vm.names.toFixed, to_fixed, 1, attr);
|
||||
define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr);
|
||||
define_native_function(vm.names.toPrecision, to_precision, 1, attr);
|
||||
define_native_function(vm.names.toString, to_string, 1, attr);
|
||||
define_native_function(vm.names.valueOf, value_of, 0, attr);
|
||||
}
|
||||
|
@ -133,6 +152,138 @@ JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_locale_string)
|
|||
return js_string(vm, move(formatted));
|
||||
}
|
||||
|
||||
// 21.1.3.5 Number.prototype.toPrecision ( precision ), https://tc39.es/ecma262/#sec-number.prototype.toprecision
|
||||
JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_precision)
|
||||
{
|
||||
auto precision_value = vm.argument(0);
|
||||
|
||||
// 1. Let x be ? thisNumberValue(this value).
|
||||
auto number_value = TRY(this_number_value(global_object, vm.this_value(global_object)));
|
||||
|
||||
// 2. If precision is undefined, return ! ToString(x).
|
||||
if (precision_value.is_undefined())
|
||||
return js_string(vm, MUST(number_value.to_string(global_object)));
|
||||
|
||||
// 3. Let p be ? ToIntegerOrInfinity(precision).
|
||||
auto precision = TRY(precision_value.to_integer_or_infinity(global_object));
|
||||
|
||||
// 4. If x is not finite, return ! Number::toString(x).
|
||||
if (!number_value.is_finite_number())
|
||||
return js_string(vm, MUST(number_value.to_string(global_object)));
|
||||
|
||||
// 5. If p < 1 or p > 100, throw a RangeError exception.
|
||||
if ((precision < 1) || (precision > 100))
|
||||
return vm.throw_completion<RangeError>(global_object, ErrorType::InvalidPrecision);
|
||||
|
||||
// 6. Set x to ℝ(x).
|
||||
auto number = number_value.as_double();
|
||||
|
||||
// 7. Let s be the empty String.
|
||||
auto sign = ""sv;
|
||||
|
||||
String number_string;
|
||||
int exponent = 0;
|
||||
|
||||
// 8. If x < 0, then
|
||||
if (number < 0) {
|
||||
// a. Set s to the code unit 0x002D (HYPHEN-MINUS).
|
||||
sign = "-"sv;
|
||||
|
||||
// b. Set x to -x.
|
||||
number = -number;
|
||||
}
|
||||
|
||||
// 9. If x = 0, then
|
||||
if (number == 0) {
|
||||
// a. Let m be the String value consisting of p occurrences of the code unit 0x0030 (DIGIT ZERO).
|
||||
number_string = String::repeated('0', precision);
|
||||
|
||||
// b. Let e be 0.
|
||||
exponent = 0;
|
||||
}
|
||||
// 10. Else,
|
||||
else {
|
||||
// FIXME: The computations below fall apart for large values of 'p'. A double typically has 52 mantissa bits, which gives us
|
||||
// up to 2^52 before loss of precision. However, the largest value of 'p' may be 100, resulting in numbers on the order
|
||||
// of 10^100, thus we lose precision in these computations.
|
||||
|
||||
// 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 = static_cast<int>(floor(log10(number)));
|
||||
number = round(number / pow(10, exponent - precision + 1));
|
||||
|
||||
// b. Let m be the String value consisting of the digits of the decimal representation of n (in order, with no leading zeroes).
|
||||
number_string = decimal_digits_to_string(number);
|
||||
|
||||
// c. If e < -6 or e ≥ p, then
|
||||
if ((exponent < -6) || (exponent >= precision)) {
|
||||
// i. Assert: e ≠ 0.
|
||||
VERIFY(exponent != 0);
|
||||
|
||||
// ii. If p ≠ 1, then
|
||||
if (precision != 1) {
|
||||
// 1. Let a be the first code unit of m.
|
||||
auto first = number_string.substring_view(0, 1);
|
||||
|
||||
// 2. Let b be the other p - 1 code units of m.
|
||||
auto second = number_string.substring_view(1);
|
||||
|
||||
// 3. Set m to the string-concatenation of a, ".", and b.
|
||||
number_string = String::formatted("{}.{}", first, second);
|
||||
}
|
||||
|
||||
char exponent_sign = 0;
|
||||
|
||||
// iii. If e > 0, then
|
||||
if (exponent > 0) {
|
||||
// 1. Let c be the code unit 0x002B (PLUS SIGN).
|
||||
exponent_sign = '+';
|
||||
}
|
||||
// iv. Else,
|
||||
else {
|
||||
// 1. Assert: e < 0.
|
||||
VERIFY(exponent < 0);
|
||||
|
||||
// 2. Let c be the code unit 0x002D (HYPHEN-MINUS).
|
||||
exponent_sign = '-';
|
||||
|
||||
// 3. Set e to -e.
|
||||
exponent = -exponent;
|
||||
}
|
||||
|
||||
// v. Let d be the String value consisting of the digits of the decimal representation of e (in order, with no leading zeroes).
|
||||
auto exponent_string = String::number(exponent);
|
||||
|
||||
// vi. Return the string-concatenation of s, m, the code unit 0x0065 (LATIN SMALL LETTER E), c, and d.
|
||||
return js_string(vm, String::formatted("{}{}e{}{}", sign, number_string, exponent_sign, exponent_string));
|
||||
}
|
||||
}
|
||||
|
||||
// 11. If e = p - 1, return the string-concatenation of s and m.
|
||||
if (exponent == precision - 1)
|
||||
return js_string(vm, String::formatted("{}{}", sign, number_string));
|
||||
|
||||
// 12. If e ≥ 0, then
|
||||
if (exponent >= 0) {
|
||||
// a. Set m to the string-concatenation of the first e + 1 code units of m, the code unit 0x002E (FULL STOP), and the remaining p - (e + 1) code units of m.
|
||||
number_string = String::formatted(
|
||||
"{}.{}",
|
||||
number_string.substring_view(0, exponent + 1),
|
||||
number_string.substring_view(exponent + 1));
|
||||
}
|
||||
// 13. Else,
|
||||
else {
|
||||
// a. Set m to the string-concatenation of the code unit 0x0030 (DIGIT ZERO), the code unit 0x002E (FULL STOP), -(e + 1) occurrences of the code unit 0x0030 (DIGIT ZERO), and the String m.
|
||||
number_string = String::formatted(
|
||||
"0.{}{}",
|
||||
String::repeated('0', -1 * (exponent + 1)),
|
||||
number_string);
|
||||
}
|
||||
|
||||
// 14. Return the string-concatenation of s and m.
|
||||
return js_string(vm, String::formatted("{}{}", sign, number_string));
|
||||
}
|
||||
|
||||
// 21.1.3.6 Number.prototype.toString ( [ radix ] ), https://tc39.es/ecma262/#sec-number.prototype.tostring
|
||||
JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_string)
|
||||
{
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_fixed);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_precision);
|
||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||
JS_DECLARE_NATIVE_FUNCTION(value_of);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue