@@ -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