Skip to content

Commit

Permalink
feat(ecmascript): Implement Math.sumPrecise proposal (#552)
Browse files Browse the repository at this point in the history
  • Loading branch information
eliassjogreen authored Feb 4, 2025
1 parent 0bf6500 commit feb16d7
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Clippy
run: >
cargo clippy --all-targets
--features math,json,date,array-buffer,shared-array-buffer,weak-refs,atomics,regexp,set,annex-b,interleaved-gc,typescript
--features default,annex-b,interleaved-gc,typescript
-- -D warnings
- name: Spell check
uses: crate-ci/typos@master
Expand Down
6 changes: 4 additions & 2 deletions nova_vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ annex-b-date = ["date"]
annex-b-regexp = ["regexp"]

# Enables all currently supported proposals
proposals = ["proposal-float16array"]
proposals = ["proposal-float16array", "proposal-math-sum"]
# Enables the [Float16Array proposal](https://tc39.es/proposal-float16array/)
proposal-float16array = ["array-buffer"]
proposal-float16array = []
# Enables the [Math.sumPrecise proposal](https://tc39.es/proposal-math-sum/)
proposal-math-sum = []

[build-dependencies]
small_string = { path = "../small_string" }
1 change: 1 addition & 0 deletions nova_vm/src/builtin_strings
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ sub
subarray
substr
substring
sumPrecise
sup
symbol
Symbol
Expand Down
152 changes: 147 additions & 5 deletions nova_vm/src/ecmascript/builtins/numbers_and_dates/math_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ use crate::{
heap::WellKnownSymbolIndexes,
};

#[cfg(feature = "proposal-math-sum")]
use crate::ecmascript::{
abstract_operations::{
operations_on_iterator_objects::{get_iterator, iterator_close, iterator_step_value},
testing_and_comparison::require_object_coercible,
},
execution::agent::ExceptionType,
};

pub(crate) struct MathObject;

struct MathObjectAbs;
Expand Down Expand Up @@ -347,6 +356,18 @@ impl Builtin for MathObjectF16round {
crate::ecmascript::builtins::Behaviour::Regular(MathObject::f16round);
}

#[cfg(feature = "proposal-math-sum")]
struct MathObjectSumPrecise;
#[cfg(feature = "proposal-math-sum")]
impl Builtin for MathObjectSumPrecise {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.sumPrecise;

const LENGTH: u8 = 1;

const BEHAVIOUR: crate::ecmascript::builtins::Behaviour =
crate::ecmascript::builtins::Behaviour::Regular(MathObject::sum_precise);
}

impl MathObject {
fn abs(
agent: &mut Agent,
Expand Down Expand Up @@ -1608,17 +1629,135 @@ impl MathObject {
Ok(Value::from_f64(agent, n64, gc.into_nogc()))
}

/// ### [2 Math.sumPrecise ( items )](https://tc39.es/proposal-math-sum/#sec-math.sumprecise)
///
/// Given an iterable of Numbers, this function sums each value in the
/// iterable and returns their sum. If any value is not a Number it throws
/// a TypeError exception.
///
/// > #### Note 1
/// >
/// > The value of sum can be computed without arbitrary-precision
/// > arithmetic by a variety of algorithms. One such is the "Grow-Expansion"
/// > algorithm given in Adaptive Precision Floating-Point Arithmetic and
/// > Fast Robust Geometric Predicates by Jonathan Richard Shewchuk. A more
/// > recent algorithm is given in "Fast exact summation using small and large superaccumulators",
/// > code for which is available at https://gitlab.com/radfordneal/xsum.
#[cfg(feature = "proposal-math-sum")]
fn sum_precise(
agent: &mut Agent,
_this_value: Value,
arguments: ArgumentsList,
mut gc: GcScope,
) -> JsResult<Value> {
let items = arguments.get(0);

// 1. Perform ? RequireObjectCoercible(items).
require_object_coercible(agent, items, gc.nogc())?;

// 2. Let iteratorRecord be ? GetIterator(items, sync).
let mut iterator_record = get_iterator(agent, items, false, gc.reborrow())?;

// 3. Let state be minus-zero.
let mut state = -0.0f64;
// 4. Let sum be 0.
let mut sum = 0.0f64;
// 5. Let count be 0.
let mut count = 0;

// 6. Let next be not-started.
// 7. Repeat, while next is not done,
// a. Set next to ? IteratorStepValue(iteratorRecord).
// b. If next is not done, then
while let Some(next) = iterator_step_value(agent, &mut iterator_record, gc.reborrow())? {
// i. Set count to count + 1.
count += 1;
// ii. If count β‰₯ 2**53, then
// 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.
if count >= 2i64.pow(53) {
// 1. Let error be ThrowCompletion(a newly created RangeError object).
let error = agent.throw_exception_with_static_message(
ExceptionType::RangeError,
"Iterator cannot exceed 5**53 items",
gc.nogc(),
);
// 2. Return ? IteratorClose(iteratorRecord, error).
return iterator_close(agent, &iterator_record, Err(error), gc);
}

// v. Let n be next.
if let Ok(n) = Number::try_from(next) {
// vi. If state is not not-a-number, then
if !state.is_nan() {
if n.is_nan(agent) {
// 1. If n is NaN, then
// a. Set state to not-a-number.
state = f64::NAN;
} else if n.is_pos_infinity(agent) {
// 2. Else if n is +βˆžπ”½, then
// a. If state is minus-infinity, set state to not-a-number.
// b. Else, set state to plus-infinity.
state = if state == f64::NEG_INFINITY {
f64::NAN
} else {
f64::INFINITY
};
} else if n.is_neg_infinity(agent) {
// 3. Else if n is -βˆžπ”½, then
// a. If state is plus-infinity, set state to not-a-number.
// b. Else, set state to minus-infinity.
state = if state == f64::INFINITY {
f64::NAN
} else {
f64::NEG_INFINITY
};
} else if !n.is_neg_zero(agent) && (state == -0.0 || state.is_finite()) {
// 4. Else if n is not -0𝔽 and state is either minus-zero or finite, then
// a. Set state to finite.
state = 0.0;
// b. Set sum to sum + ℝ(n).
sum += n.into_f64(agent);
}
}
} else {
// iv. If next is not a Number, then
// 1. Let error be ThrowCompletion(a newly created TypeError object).
let error = agent.throw_exception_with_static_message(
ExceptionType::RangeError,
"Iterator may only contain numbers",
gc.nogc(),
);
// 2. Return ? IteratorClose(iteratorRecord, error).
return iterator_close(agent, &iterator_record, Err(error), gc);
}
}

// 8. If state is not-a-number, return NaN.
// 9. If state is plus-infinity, return +βˆžπ”½.
// 10. If state is minus-infinity, return -βˆžπ”½.
// 11. If state is minus-zero, return -0𝔽.
if state.is_nan() || state.is_infinite() || state == -0.0 {
return Ok(Value::from_f64(agent, state, gc.into_nogc()));
}
// 12. Return 𝔽(sum).
Ok(Value::from_f64(agent, sum, gc.into_nogc()))
}

pub(crate) fn create_intrinsic(agent: &mut Agent, realm: RealmIdentifier, gc: NoGcScope) {
let intrinsics = agent.get_realm(realm).intrinsics();
let object_prototype = intrinsics.object_prototype();
let this = intrinsics.math();

let mut property_capacity = 44;
if cfg!(feature = "proposal-float16array") {
property_capacity += 1;
}
if cfg!(feature = "proposal-math-sum") {
property_capacity += 1;
}

let builder = OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this)
.with_property_capacity(if cfg!(feature = "proposal-float16array") {
45
} else {
44
})
.with_property_capacity(property_capacity)
.with_prototype(object_prototype)
.with_property(|builder| {
builder
Expand Down Expand Up @@ -1747,6 +1886,9 @@ impl MathObject {
#[cfg(feature = "proposal-float16array")]
let builder = builder.with_builtin_function_property::<MathObjectF16round>();

#[cfg(feature = "proposal-math-sum")]
let builder = builder.with_builtin_function_property::<MathObjectSumPrecise>();

builder.build();
}
}
Expand Down

0 comments on commit feb16d7

Please sign in to comment.