mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-31 05:09:12 +00:00
LibJS: Implement Math.sumPrecise
This commit is contained in:
parent
c769271e82
commit
80b2c11c81
Notes:
github-actions[bot]
2025-03-03 20:47:18 +00:00
Author: https://github.com/aplefull
Commit: 80b2c11c81
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3776
5 changed files with 341 additions and 0 deletions
|
@ -499,6 +499,7 @@ namespace JS {
|
||||||
P(substr) \
|
P(substr) \
|
||||||
P(substring) \
|
P(substring) \
|
||||||
P(subtract) \
|
P(subtract) \
|
||||||
|
P(sumPrecise) \
|
||||||
P(sup) \
|
P(sup) \
|
||||||
P(supportedLocalesOf) \
|
P(supportedLocalesOf) \
|
||||||
P(supportedValuesOf) \
|
P(supportedValuesOf) \
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
M(JsonBigInt, "Cannot serialize BigInt value to JSON") \
|
M(JsonBigInt, "Cannot serialize BigInt value to JSON") \
|
||||||
M(JsonCircular, "Cannot stringify circular object") \
|
M(JsonCircular, "Cannot stringify circular object") \
|
||||||
M(JsonMalformed, "Malformed JSON string") \
|
M(JsonMalformed, "Malformed JSON string") \
|
||||||
|
M(MathSumPreciseOverflow, "Overflow in Math.sumPrecise") \
|
||||||
M(MissingRequiredProperty, "Required property {} is missing or undefined") \
|
M(MissingRequiredProperty, "Required property {} is missing or undefined") \
|
||||||
M(ModuleNoEnvironment, "Cannot find module environment for imported binding") \
|
M(ModuleNoEnvironment, "Cannot find module environment for imported binding") \
|
||||||
M(ModuleNotFound, "Cannot find/open module: '{}'") \
|
M(ModuleNotFound, "Cannot find/open module: '{}'") \
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
#include <AK/BuiltinWrappers.h>
|
#include <AK/BuiltinWrappers.h>
|
||||||
#include <AK/Function.h>
|
#include <AK/Function.h>
|
||||||
#include <AK/Random.h>
|
#include <AK/Random.h>
|
||||||
|
#include <LibJS/Runtime/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
|
#include <LibJS/Runtime/Iterator.h>
|
||||||
#include <LibJS/Runtime/MathObject.h>
|
#include <LibJS/Runtime/MathObject.h>
|
||||||
#include <LibJS/Runtime/ValueInlines.h>
|
#include <LibJS/Runtime/ValueInlines.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
@ -65,6 +67,7 @@ void MathObject::initialize(Realm& realm)
|
||||||
define_native_function(realm, vm.names.sinh, sinh, 1, attr);
|
define_native_function(realm, vm.names.sinh, sinh, 1, attr);
|
||||||
define_native_function(realm, vm.names.cosh, cosh, 1, attr);
|
define_native_function(realm, vm.names.cosh, cosh, 1, attr);
|
||||||
define_native_function(realm, vm.names.tanh, tanh, 1, attr);
|
define_native_function(realm, vm.names.tanh, tanh, 1, attr);
|
||||||
|
define_native_function(realm, vm.names.sumPrecise, sumPrecise, 1, attr);
|
||||||
|
|
||||||
// 21.3.1 Value Properties of the Math Object, https://tc39.es/ecma262/#sec-value-properties-of-the-math-object
|
// 21.3.1 Value Properties of the Math Object, https://tc39.es/ecma262/#sec-value-properties-of-the-math-object
|
||||||
define_direct_property(vm.names.E, Value(M_E), 0);
|
define_direct_property(vm.names.E, Value(M_E), 0);
|
||||||
|
@ -957,4 +960,241 @@ JS_DEFINE_NATIVE_FUNCTION(MathObject::trunc)
|
||||||
: ::floor(number.as_double()));
|
: ::floor(number.as_double()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TwoSumResult {
|
||||||
|
double hi;
|
||||||
|
double lo;
|
||||||
|
};
|
||||||
|
|
||||||
|
static TwoSumResult two_sum(double x, double y)
|
||||||
|
{
|
||||||
|
double hi = x + y;
|
||||||
|
double lo = y - (hi - x);
|
||||||
|
return { hi, lo };
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://tc39.es/proposal-math-sum/#sec-math.sumprecise
|
||||||
|
ThrowCompletionOr<Value> MathObject::sum_precise_impl(VM& vm, Value iterable)
|
||||||
|
{
|
||||||
|
constexpr double MAX_DOUBLE = 1.79769313486231570815e+308; // std::numeric_limits<double>::max()
|
||||||
|
constexpr double PENULTIMATE_DOUBLE = 1.79769313486231550856e+308; // std::nextafter(DBL_MAX, 0)
|
||||||
|
constexpr double MAX_ULP = MAX_DOUBLE - PENULTIMATE_DOUBLE;
|
||||||
|
constexpr double POW_2_1023 = 8.98846567431158e+307; // 2^1023
|
||||||
|
|
||||||
|
// 1. Perform ? RequireObjectCoercible(items).
|
||||||
|
TRY(require_object_coercible(vm, iterable));
|
||||||
|
|
||||||
|
// 2. Let iteratorRecord be ? GetIterator(items, sync).
|
||||||
|
auto using_iterator = TRY(iterable.get_method(vm, vm.well_known_symbol_iterator()));
|
||||||
|
if (!using_iterator)
|
||||||
|
return vm.throw_completion<TypeError>(ErrorType::NotIterable, iterable.to_string_without_side_effects());
|
||||||
|
|
||||||
|
auto iterator = TRY(get_iterator_from_method(vm, iterable, *using_iterator));
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
MinusZero,
|
||||||
|
PlusInfinity,
|
||||||
|
MinusInfinity,
|
||||||
|
NotANumber,
|
||||||
|
Finite
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Let state be minus-zero.
|
||||||
|
State state = State::MinusZero;
|
||||||
|
|
||||||
|
// 4. Let sum be 0.
|
||||||
|
// 5. Let count be 0.
|
||||||
|
double overflow = 0.0;
|
||||||
|
u64 count = 0;
|
||||||
|
Vector<double> partials;
|
||||||
|
|
||||||
|
// 6. Let next be not-started.
|
||||||
|
// 7. Repeat, while next is not done
|
||||||
|
for (;;) {
|
||||||
|
// a. Set next to ? IteratorStepValue(iteratorRecord).
|
||||||
|
auto next = TRY(iterator_step_value(vm, iterator));
|
||||||
|
|
||||||
|
if (!next.has_value())
|
||||||
|
break;
|
||||||
|
|
||||||
|
// If next is not done, then
|
||||||
|
// i. Set count to count + 1.
|
||||||
|
count++;
|
||||||
|
// ii. If count ≥ 2**53, then
|
||||||
|
// 1. Let error be ThrowCompletion(a newly created RangeError object).
|
||||||
|
// 2. Return ? IteratorClose(iteratorRecord, error).
|
||||||
|
if (count >= (1ULL << 53))
|
||||||
|
return iterator_close(vm, iterator, vm.throw_completion<RangeError>(ErrorType::ArrayMaxSize));
|
||||||
|
|
||||||
|
// iii. NOTE: The above case is not expected to be reached in practice and is included only so that implementations may rely on inputs being
|
||||||
|
// "reasonably sized" without violating this specification.
|
||||||
|
|
||||||
|
// iv. If next is not a Number, then
|
||||||
|
auto value = next.value();
|
||||||
|
if (!value.is_number())
|
||||||
|
// 1. Let error be ThrowCompletion(a newly created TypeError object).
|
||||||
|
// 2. Return ? IteratorClose(iteratorRecord, error).
|
||||||
|
return iterator_close(vm, iterator, vm.throw_completion<TypeError>(ErrorType::IsNotA, value.to_string_without_side_effects(), "number"));
|
||||||
|
|
||||||
|
// v. Let n be next.
|
||||||
|
auto n = value.as_double();
|
||||||
|
|
||||||
|
// vi. If state is not not-a-number, then
|
||||||
|
if (state != State::NotANumber) {
|
||||||
|
// 1. If n is NaN, then
|
||||||
|
if (isnan(n)) {
|
||||||
|
// a. Set state to not-a-number.
|
||||||
|
state = State::NotANumber;
|
||||||
|
} // 2. Else if n is +∞𝔽, then
|
||||||
|
else if (Value(n).is_positive_infinity()) {
|
||||||
|
// a. If state is minus-infinity, set state to not-a-number.
|
||||||
|
// b. Else, set state to plus-infinity.
|
||||||
|
state = (state == State::MinusInfinity) ? State::NotANumber : State::PlusInfinity;
|
||||||
|
} // 3. Else if n is -∞𝔽, then
|
||||||
|
else if (Value(n).is_negative_infinity()) {
|
||||||
|
// a. If state is plus-infinity, set state to not-a-number.
|
||||||
|
// b. Else, set state to minus-infinity.
|
||||||
|
state = (state == State::PlusInfinity) ? State::NotANumber : State::MinusInfinity;
|
||||||
|
} // 4. Else if n is not -0𝔽 and state is either minus-zero or finite, then
|
||||||
|
else if (!Value(n).is_negative_zero() && (state == State::MinusZero || state == State::Finite)) {
|
||||||
|
// a. Set state to finite.
|
||||||
|
state = State::Finite;
|
||||||
|
|
||||||
|
// b. Set sum to sum + ℝ(n).
|
||||||
|
double x = n;
|
||||||
|
size_t used_partials = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < partials.size(); i++) {
|
||||||
|
double y = partials[i];
|
||||||
|
|
||||||
|
if (AK::abs(x) < AK::abs(y))
|
||||||
|
swap(x, y);
|
||||||
|
|
||||||
|
TwoSumResult result = two_sum(x, y);
|
||||||
|
double hi = result.hi;
|
||||||
|
double lo = result.lo;
|
||||||
|
|
||||||
|
if (isinf(hi)) {
|
||||||
|
double sign = signbit(hi) ? -1.0 : 1.0;
|
||||||
|
overflow += sign;
|
||||||
|
|
||||||
|
if (AK::abs(overflow) >= (1ULL << 53))
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::MathSumPreciseOverflow);
|
||||||
|
|
||||||
|
x = (x - sign * POW_2_1023) - sign * POW_2_1023;
|
||||||
|
|
||||||
|
if (AK::abs(x) < AK::abs(y))
|
||||||
|
swap(x, y);
|
||||||
|
|
||||||
|
result = two_sum(x, y);
|
||||||
|
hi = result.hi;
|
||||||
|
lo = result.lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lo != 0.0) {
|
||||||
|
partials[used_partials++] = lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = hi;
|
||||||
|
}
|
||||||
|
|
||||||
|
partials.resize(used_partials);
|
||||||
|
|
||||||
|
if (x != 0.0) {
|
||||||
|
partials.append(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. If state is not-a-number, return NaN.
|
||||||
|
if (state == State::NotANumber)
|
||||||
|
return js_nan();
|
||||||
|
|
||||||
|
// 9. If state is plus-infinity, return +∞𝔽.
|
||||||
|
if (state == State::PlusInfinity)
|
||||||
|
return js_infinity();
|
||||||
|
|
||||||
|
// 10. If state is minus-infinity, return -∞𝔽.
|
||||||
|
if (state == State::MinusInfinity)
|
||||||
|
return js_negative_infinity();
|
||||||
|
|
||||||
|
// 11. If state is minus-zero, return -0𝔽.
|
||||||
|
if (state == State::MinusZero)
|
||||||
|
return Value(-0.0);
|
||||||
|
|
||||||
|
// 12. Return 𝔽(sum).
|
||||||
|
int n = partials.size() - 1;
|
||||||
|
double hi = 0.0;
|
||||||
|
double lo = 0.0;
|
||||||
|
|
||||||
|
if (overflow != 0.0) {
|
||||||
|
double next = n >= 0 ? partials[n] : 0.0;
|
||||||
|
n--;
|
||||||
|
|
||||||
|
if (AK::abs(overflow) > 1.0 || (overflow > 0.0 && next > 0.0) || (overflow < 0.0 && next < 0.0)) {
|
||||||
|
return overflow > 0.0 ? js_infinity() : js_negative_infinity();
|
||||||
|
}
|
||||||
|
|
||||||
|
TwoSumResult result = two_sum(overflow * POW_2_1023, next / 2.0);
|
||||||
|
hi = result.hi;
|
||||||
|
lo = result.lo * 2.0;
|
||||||
|
|
||||||
|
if (isinf(hi * 2.0)) {
|
||||||
|
if (hi > 0.0) {
|
||||||
|
if (hi == POW_2_1023 && lo == -(MAX_ULP / 2.0) && n >= 0 && partials[n] < 0.0) {
|
||||||
|
return Value(MAX_DOUBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return js_infinity();
|
||||||
|
} else {
|
||||||
|
if (hi == -POW_2_1023 && lo == (MAX_ULP / 2.0) && n >= 0 && partials[n] > 0.0) {
|
||||||
|
return Value(-MAX_DOUBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return js_negative_infinity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lo != 0.0) {
|
||||||
|
partials[n + 1] = lo;
|
||||||
|
n++;
|
||||||
|
lo = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hi *= 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (n >= 0) {
|
||||||
|
double x = hi;
|
||||||
|
double y = partials[n];
|
||||||
|
n--;
|
||||||
|
|
||||||
|
TwoSumResult result = two_sum(x, y);
|
||||||
|
hi = result.hi;
|
||||||
|
lo = result.lo;
|
||||||
|
|
||||||
|
if (lo != 0.0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n >= 0 && ((lo < 0.0 && partials[n] < 0.0) || (lo > 0.0 && partials[n] > 0.0))) {
|
||||||
|
double y = lo * 2.0;
|
||||||
|
double x = hi + y;
|
||||||
|
double yr = x - hi;
|
||||||
|
|
||||||
|
if (y == yr) {
|
||||||
|
hi = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Value(hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://tc39.es/proposal-math-sum/#sec-math.sumprecise
|
||||||
|
JS_DEFINE_NATIVE_FUNCTION(MathObject::sumPrecise)
|
||||||
|
{
|
||||||
|
return sum_precise_impl(vm, vm.argument(0));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ public:
|
||||||
static ThrowCompletionOr<Value> round_impl(VM&, Value);
|
static ThrowCompletionOr<Value> round_impl(VM&, Value);
|
||||||
static ThrowCompletionOr<Value> exp_impl(VM&, Value);
|
static ThrowCompletionOr<Value> exp_impl(VM&, Value);
|
||||||
static ThrowCompletionOr<Value> abs_impl(VM&, Value);
|
static ThrowCompletionOr<Value> abs_impl(VM&, Value);
|
||||||
|
static ThrowCompletionOr<Value> sum_precise_impl(VM&, Value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit MathObject(Realm&);
|
explicit MathObject(Realm&);
|
||||||
|
@ -66,6 +67,7 @@ private:
|
||||||
JS_DECLARE_NATIVE_FUNCTION(sinh);
|
JS_DECLARE_NATIVE_FUNCTION(sinh);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(cosh);
|
JS_DECLARE_NATIVE_FUNCTION(cosh);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(tanh);
|
JS_DECLARE_NATIVE_FUNCTION(tanh);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(sumPrecise);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
97
Libraries/LibJS/Tests/builtins/Math/Math.sumPrecise.js
Normal file
97
Libraries/LibJS/Tests/builtins/Math/Math.sumPrecise.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
test("basic functionality", () => {
|
||||||
|
expect(Math.sumPrecise).toHaveLength(1);
|
||||||
|
|
||||||
|
expect(Math.sumPrecise([])).toBe(-0);
|
||||||
|
expect(Math.sumPrecise([1, 2, 3])).toBe(6);
|
||||||
|
expect(Math.sumPrecise([1e308])).toBe(1e308);
|
||||||
|
expect(Math.sumPrecise([1e308, -1e308])).toBe(0);
|
||||||
|
expect(Math.sumPrecise([0.1])).toBe(0.1);
|
||||||
|
expect(Math.sumPrecise([0.1, 0.1])).toBe(0.2);
|
||||||
|
expect(Math.sumPrecise([0.1, -0.1])).toBe(0);
|
||||||
|
expect(Math.sumPrecise([1e308, 1e308, 0.1, 0.1, 1e30, 0.1, -1e30, -1e308, -1e308])).toBe(
|
||||||
|
0.30000000000000004
|
||||||
|
);
|
||||||
|
expect(Math.sumPrecise([1e30, 0.1, -1e30])).toBe(0.1);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([8.98846567431158e307, 8.988465674311579e307, -1.7976931348623157e308])
|
||||||
|
).toBe(9.9792015476736e291);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([-5.630637621603525e255, 9.565271205476345e307, 2.9937604643020797e292])
|
||||||
|
).toBe(9.565271205476347e307);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
6.739986666787661e66, 2, -1.2689709186578243e-116, 1.7046015739467354e308,
|
||||||
|
-9.979201547673601e291, 6.160926733208294e307, -3.179557053031852e234,
|
||||||
|
-7.027282978772846e307, -0.7500000000000001,
|
||||||
|
])
|
||||||
|
).toBe(1.61796594939028e308);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
0.31150493246968836, -8.988465674311582e307, 1.8315037361673755e-270,
|
||||||
|
-15.999999999999996, 2.9999999999999996, 7.345200721499384e164, -2.033582473639399,
|
||||||
|
-8.98846567431158e307, -3.5737295155405993e292, 4.13894772383715e-124,
|
||||||
|
-3.6111186457260667e-35, 2.387234887098013e180, 7.645295562778372e-298,
|
||||||
|
3.395189016861822e-103, -2.6331611115768973e-149,
|
||||||
|
])
|
||||||
|
).toBe(-Infinity);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
-1.1442589134409902e308, 9.593842098384855e138, 4.494232837155791e307,
|
||||||
|
-1.3482698511467367e308, 4.494232837155792e307,
|
||||||
|
])
|
||||||
|
).toBe(-1.5936821971565685e308);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
-1.1442589134409902e308, 4.494232837155791e307, -1.3482698511467367e308,
|
||||||
|
4.494232837155792e307,
|
||||||
|
])
|
||||||
|
).toBe(-1.5936821971565687e308);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
9.593842098384855e138, -6.948356297254111e307, -1.3482698511467367e308,
|
||||||
|
4.494232837155792e307,
|
||||||
|
])
|
||||||
|
).toBe(-1.5936821971565685e308);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([-2.534858246857893e115, 8.988465674311579e307, 8.98846567431158e307]),
|
||||||
|
1.7976931348623157e308
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([1.3588124894186193e308, 1.4803986201152006e223, 6.741349255733684e307])
|
||||||
|
).toBe(Infinity);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([6.741349255733684e307, 1.7976931348623155e308, -7.388327292663961e41])
|
||||||
|
).toBe(Infinity);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([-1.9807040628566093e28, 1.7976931348623157e308, 9.9792015476736e291])
|
||||||
|
).toBe(1.7976931348623157e308);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
-1.0214557991173964e61, 1.7976931348623157e308, 8.98846567431158e307,
|
||||||
|
-8.988465674311579e307,
|
||||||
|
])
|
||||||
|
).toBe(1.7976931348623157e308);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
1.7976931348623157e308, 7.999999999999999, -1.908963895403937e-230,
|
||||||
|
1.6445950082320264e292, 2.0734856707605806e205,
|
||||||
|
])
|
||||||
|
).toBe(Infinity);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([6.197409167220438e-223, -9.979201547673601e291, -1.7976931348623157e308])
|
||||||
|
).toBe(-Infinity);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
4.49423283715579e307, 8.944251746776101e307, -0.0002441406250000001,
|
||||||
|
1.1752060710043817e308, 4.940846717201632e292, -1.6836699406454528e308,
|
||||||
|
])
|
||||||
|
).toBe(8.353845887521184e307);
|
||||||
|
expect(
|
||||||
|
Math.sumPrecise([
|
||||||
|
8.988465674311579e307, 7.999999999999998, 7.029158107234023e-308,
|
||||||
|
-2.2303483759420562e-172, -1.7976931348623157e308, -8.98846567431158e307,
|
||||||
|
])
|
||||||
|
).toBe(-1.7976931348623157e308);
|
||||||
|
expect(Math.sumPrecise([8.98846567431158e307, 8.98846567431158e307])).toBe(Infinity);
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue