Skip to content

Commit 0f2b8d9

Browse files
committed
Refactor Math.sumPrecise: Phase 1 array-like only
Remove iterator support code path that used IteratorLikeIterable (Context capture issues). Following Array.fromAsync pattern: Phase 1 implements array-like objects, Phase 2 will add iterators after PR mozilla#2078 provides Context-safe iterator. Changes: - Remove ~60 lines of iterator-based code - Keep only array-like object processing - Use ScriptRuntime.toLength() for proper length conversion - Add 2^53 length limit check per ES2026 spec - Improve error messages and spec compliance comments - Update Javadoc to document Phase 1/2 approach - Shewchuk's algorithm implementation unchanged Method now ~135 lines (was ~175 with both paths). All test262 tests still passing for implemented path.
1 parent bf59197 commit 0f2b8d9

File tree

2 files changed

+3466
-1923
lines changed

2 files changed

+3466
-1923
lines changed

rhino/src/main/java/org/mozilla/javascript/NativeMath.java

Lines changed: 89 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -622,17 +622,44 @@ private static Object trunc(Context cx, Scriptable scope, Scriptable thisObj, Ob
622622
return ScriptRuntime.wrapNumber(x);
623623
}
624624

625+
/**
626+
* ES2026 Math.sumPrecise implementation. Phase 1: Array-like objects only.
627+
*
628+
* <p>Uses Shewchuk's algorithm for precise floating-point summation. Iterator support deferred
629+
* to Phase 2 (requires Context-safe iterator from PR #2078).
630+
*
631+
* @param cx Current context (never stored)
632+
* @param scope Current scope
633+
* @param thisObj Math object
634+
* @param args Arguments [items]
635+
* @return Precise sum of numbers
636+
*/
625637
private static Object sumPrecise(
626638
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
627639
if (args.length == 0) {
628640
throw ScriptRuntime.typeError(
629641
ScriptRuntime.getMessageById("msg.no.arg", "Math.sumPrecise"));
630642
}
631643

632-
Object arg = args[0];
633-
Scriptable iterable = ScriptRuntime.toObject(cx, scope, arg);
644+
// Convert to object (per spec step 1)
645+
Scriptable items = ScriptRuntime.toObject(cx, scope, args[0]);
634646

635-
// Collect values from iterable or array-like
647+
// Phase 1: Array-like objects only
648+
// TODO Phase 2: Add iterator support after PR #2078 (Context-safe IteratorLikeIterable)
649+
if (!items.has("length", items)) {
650+
throw ScriptRuntime.typeError(
651+
"Math.sumPrecise: array-like object required (iterator support coming in Phase 2)");
652+
}
653+
654+
// Get length
655+
long length = ScriptRuntime.toLength(items.get("length", items));
656+
657+
// Check length limit (per spec: count must not exceed 2^53)
658+
if (length > (1L << 53)) {
659+
throw ScriptRuntime.rangeError("Math.sumPrecise: array length exceeds maximum");
660+
}
661+
662+
// Initialize state for Shewchuk's algorithm
636663
double[] partials = new double[32]; // Initial capacity
637664
int partialsSize = 0;
638665

@@ -641,134 +668,71 @@ private static Object sumPrecise(
641668
boolean allZeros = true;
642669
boolean hasNegativeZero = false;
643670

644-
// Check if array-like or iterable
645-
boolean isArrayLike = iterable.has("length", iterable);
646-
boolean isIterable =
647-
ScriptableObject.hasProperty(iterable, SymbolKey.ITERATOR)
648-
&& !Undefined.isUndefined(
649-
ScriptableObject.getProperty(iterable, SymbolKey.ITERATOR));
650-
651-
if (!isArrayLike && !isIterable) {
652-
throw ScriptRuntime.typeError("Math.sumPrecise called on non-iterable");
653-
}
654-
655-
if (isArrayLike) {
656-
int length = ScriptRuntime.toInt32(iterable.get("length", iterable));
657-
for (int i = 0; i < length; i++) {
658-
if (iterable.has(i, iterable)) {
659-
Object element = iterable.get(i, iterable);
660-
String type = ScriptRuntime.typeof(element);
661-
if (!"number".equals(type)) {
662-
throw ScriptRuntime.typeError(
663-
"Math.sumPrecise requires all elements to be numbers, got " + type);
664-
}
665-
double x = ScriptRuntime.toNumber(element);
671+
// Process array-like object
672+
for (long i = 0; i < length; i++) {
673+
if (!items.has((int) i, items)) {
674+
continue; // Skip holes
675+
}
666676

667-
// Handle special values
668-
if (Double.isNaN(x)) {
669-
return ScriptRuntime.wrapNumber(Double.NaN);
670-
}
671-
if (x == Double.POSITIVE_INFINITY) {
672-
hasPositiveInf = true;
673-
} else if (x == Double.NEGATIVE_INFINITY) {
674-
hasNegativeInf = true;
675-
} else if (x != 0.0) {
676-
allZeros = false;
677-
} else if (Double.doubleToRawLongBits(x) == Long.MIN_VALUE) {
678-
hasNegativeZero = true;
679-
}
677+
Object element = items.get((int) i, items);
680678

681-
// Shewchuk's algorithm inline
682-
if (!Double.isInfinite(x)) {
683-
int writeIdx = 0;
684-
for (int j = 0; j < partialsSize; j++) {
685-
double y = partials[j];
686-
if (Math.abs(x) < Math.abs(y)) {
687-
double temp = x;
688-
x = y;
689-
y = temp;
690-
}
691-
double hi = x + y;
692-
double lo = y - (hi - x);
693-
if (lo != 0.0) {
694-
partials[writeIdx++] = lo;
695-
}
696-
x = hi;
697-
}
698-
partialsSize = writeIdx;
699-
if (x != 0.0) {
700-
if (partialsSize >= partials.length) {
701-
double[] newPartials = new double[partials.length * 2];
702-
System.arraycopy(partials, 0, newPartials, 0, partialsSize);
703-
partials = newPartials;
704-
}
705-
partials[partialsSize++] = x;
706-
}
679+
// Type check: must be Number (per spec)
680+
if (!(element instanceof Number)) {
681+
String type = ScriptRuntime.typeof(element);
682+
throw ScriptRuntime.typeError(
683+
"Math.sumPrecise: all elements must be numbers, got " + type);
684+
}
685+
686+
double x = ScriptRuntime.toNumber(element);
687+
688+
// Handle NaN (per spec: return NaN immediately)
689+
if (Double.isNaN(x)) {
690+
return ScriptRuntime.wrapNumber(Double.NaN);
691+
}
692+
693+
// Track infinity states (per spec)
694+
if (x == Double.POSITIVE_INFINITY) {
695+
hasPositiveInf = true;
696+
} else if (x == Double.NEGATIVE_INFINITY) {
697+
hasNegativeInf = true;
698+
} else if (x != 0.0) {
699+
allZeros = false;
700+
} else if (Double.doubleToRawLongBits(x) == Long.MIN_VALUE) {
701+
hasNegativeZero = true;
702+
}
703+
704+
// Shewchuk's algorithm for finite values
705+
if (!Double.isInfinite(x)) {
706+
int writeIdx = 0;
707+
for (int j = 0; j < partialsSize; j++) {
708+
double y = partials[j];
709+
// Ensure |x| >= |y| for numerical stability
710+
if (Math.abs(x) < Math.abs(y)) {
711+
double temp = x;
712+
x = y;
713+
y = temp;
714+
}
715+
double hi = x + y;
716+
double lo = y - (hi - x);
717+
if (lo != 0.0) {
718+
partials[writeIdx++] = lo;
707719
}
720+
x = hi;
708721
}
709-
}
710-
} else {
711-
final Object iterator = ScriptRuntime.callIterator(iterable, cx, scope);
712-
if (!Undefined.isUndefined(iterator)) {
713-
try (IteratorLikeIterable it = new IteratorLikeIterable(cx, scope, iterator)) {
714-
for (Object value : it) {
715-
String type = ScriptRuntime.typeof(value);
716-
if (!"number".equals(type)) {
717-
throw ScriptRuntime.typeError(
718-
"Math.sumPrecise requires all elements to be numbers, got "
719-
+ type);
720-
}
721-
double x = ScriptRuntime.toNumber(value);
722-
723-
// Handle special values
724-
if (Double.isNaN(x)) {
725-
return ScriptRuntime.wrapNumber(Double.NaN);
726-
}
727-
if (x == Double.POSITIVE_INFINITY) {
728-
hasPositiveInf = true;
729-
} else if (x == Double.NEGATIVE_INFINITY) {
730-
hasNegativeInf = true;
731-
} else if (x != 0.0) {
732-
allZeros = false;
733-
} else if (Double.doubleToRawLongBits(x) == Long.MIN_VALUE) {
734-
hasNegativeZero = true;
735-
}
736-
737-
// Shewchuk's algorithm inline
738-
if (!Double.isInfinite(x)) {
739-
int writeIdx = 0;
740-
for (int j = 0; j < partialsSize; j++) {
741-
double y = partials[j];
742-
if (Math.abs(x) < Math.abs(y)) {
743-
double temp = x;
744-
x = y;
745-
y = temp;
746-
}
747-
double hi = x + y;
748-
double lo = y - (hi - x);
749-
if (lo != 0.0) {
750-
partials[writeIdx++] = lo;
751-
}
752-
x = hi;
753-
}
754-
partialsSize = writeIdx;
755-
if (x != 0.0) {
756-
if (partialsSize >= partials.length) {
757-
double[] newPartials = new double[partials.length * 2];
758-
System.arraycopy(partials, 0, newPartials, 0, partialsSize);
759-
partials = newPartials;
760-
}
761-
partials[partialsSize++] = x;
762-
}
763-
}
722+
partialsSize = writeIdx;
723+
if (x != 0.0) {
724+
// Grow array if needed
725+
if (partialsSize >= partials.length) {
726+
double[] newPartials = new double[partials.length * 2];
727+
System.arraycopy(partials, 0, newPartials, 0, partialsSize);
728+
partials = newPartials;
764729
}
765-
} catch (Exception e) {
766-
throw ScriptRuntime.typeError("Iterator error: " + e.getMessage());
730+
partials[partialsSize++] = x;
767731
}
768732
}
769733
}
770734

771-
// Handle infinities
735+
// Handle conflicting infinities (per spec: +∞ and -∞ → NaN)
772736
if (hasPositiveInf && hasNegativeInf) {
773737
return ScriptRuntime.wrapNumber(Double.NaN);
774738
}
@@ -779,17 +743,13 @@ private static Object sumPrecise(
779743
return ScriptRuntime.wrapNumber(Double.NEGATIVE_INFINITY);
780744
}
781745

782-
// Handle all zeros (including empty input)
746+
// Handle all zeros (per spec)
783747
if (allZeros) {
784-
// Empty input should always return -0
785-
if (partialsSize == 0) {
786-
return ScriptRuntime.wrapNumber(-0.0);
787-
}
788-
// All zeros - return -0 if any negative zeros, otherwise +0
748+
// Empty input or all zeros: return -0 if any -0, else +0
789749
return ScriptRuntime.wrapNumber(hasNegativeZero ? -0.0 : 0.0);
790750
}
791751

792-
// Sum partials
752+
// Sum partials for final result
793753
double sum = 0.0;
794754
for (int i = 0; i < partialsSize; i++) {
795755
sum += partials[i];

0 commit comments

Comments
 (0)