using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Jint.Native.Math; /// /// https://raw.githubusercontent.com/es-shims/Math.sumPrecise/main/sum.js /// adapted from https://github.com/tc39/proposal-math-sum/blob/f4286d0a9d8525bda61be486df964bf2527c8789/polyfill/polyfill.mjs /// https://www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps /// Shewchuk's algorithm for exactly floating point addition /// as implemented in Python's fsum: https://github.com/python/cpython/blob/48dfd74a9db9d4aa9c6f23b4a67b461e5d977173/Modules/mathmodule.c#L1359-L1474 /// adapted to handle overflow via an additional "biased" partial, representing 2**1024 times its actual value /// internal static class SumPrecise { // exponent 11111111110, significand all 1s private const double MaxDouble = 1.79769313486231570815e+308; // i.e. (2**1024 - 2**(1023 - 52)) // exponent 11111111110, significand all 1s except final 0 private const double PenultimateDouble = 1.79769313486231550856e+308; // i.e. (2**1024 - 2 * 2**(1023 - 52)) private const double Two1023 = 8.98846567431158e+307; // 2 ** 1023 // exponent 11111001010, significand all 0s private const double MaxUlp = MaxDouble - PenultimateDouble; // 1.99584030953471981166e+292, i.e. 2**(1023 - 52) [StructLayout(LayoutKind.Auto)] private readonly record struct TwoSumResult(double Hi, double Lo); // prerequisite: $abs(x) >= $abs(y) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TwoSumResult TwoSum(double x, double y) { var hi = x + y; var lo = y - (hi - x); return new TwoSumResult(hi, lo); } // preconditions: // - array only contains numbers // - none of them are -0, NaN, or ±Infinity // - all of them are finite [MethodImpl(512)] internal static double Sum(List array) { double hi, lo; List partials = []; var overflow = 0; // conceptually 2**1024 times this value; the final partial is biased by this amount var index = -1; // main loop while (index + 1 < array.Count) { var x = +array[++index]; var actuallyUsedPartials = 0; for (var j = 0; j < partials.Count; j += 1) { var y = partials[j]; if (System.Math.Abs(x) < System.Math.Abs(y)) { var tmp = x; x = y; y = tmp; } (hi, lo) = TwoSum(x, y); if (double.IsPositiveInfinity(System.Math.Abs(hi))) { var sign = double.IsPositiveInfinity(hi) ? 1 : -1; overflow += sign; x = x - sign * Two1023 - sign * Two1023; if (System.Math.Abs(x) < System.Math.Abs(y)) { var tmp2 = x; x = y; y = tmp2; } var s2 = TwoSum(x, y); hi = s2.Hi; lo = s2.Lo; } if (lo != 0) { partials[actuallyUsedPartials] = lo; actuallyUsedPartials += 1; } x = hi; } while (partials.Count > actuallyUsedPartials) { partials.RemoveAt(partials.Count - 1); } if (x != 0) { partials.Add(x); } } // compute the exact sum of partials, stopping once we lose precision var n = partials.Count - 1; hi = 0; lo = 0; if (overflow != 0) { var next = n >= 0 ? partials[n] : 0; n -= 1; if (System.Math.Abs(overflow) > 1 || (overflow > 0 && next > 0) || (overflow < 0 && next < 0)) { return overflow > 0 ? double.PositiveInfinity : double.NegativeInfinity; } // here we actually have to do the arithmetic // drop a factor of 2 so we can do it without overflow // assert($abs(overflow) === 1) var s = TwoSum(overflow * Two1023, next / 2); hi = s.Hi; lo = s.Lo; lo *= 2; if (double.IsPositiveInfinity(System.Math.Abs(2 * hi))) { // stupid edge case: rounding to the maximum value // MAX_DOUBLE has a 1 in the last place of its significand, so if we subtract exactly half a ULP from 2**1024, the result rounds away from it (i.e. to infinity) under ties-to-even // but if the next partial has the opposite sign of the current value, we need to round towards MAX_DOUBLE instead // this is the same as the "handle rounding" case below, but there's only one potentially-finite case we need to worry about, so we just hardcode that one if (hi > 0) { if (hi == Two1023 && lo == -(MaxUlp / 2) && n >= 0 && partials[n] < 0) { return MaxDouble; } return double.PositiveInfinity; } if (hi == -Two1023 && lo == (MaxUlp / 2) && n >= 0 && partials[n] > 0) { return -MaxDouble; } return double.NegativeInfinity; } if (lo != 0) { partials[n + 1] = lo; n += 1; lo = 0; } hi *= 2; } while (n >= 0) { var x1 = hi; var y1 = partials[n]; n -= 1; // assert: $abs(x1) > $abs(y1) (hi, lo) = TwoSum(x1, y1); if (lo != 0) { break; // eslint-disable-line no-restricted-syntax } } // handle rounding // when the roundoff error is exactly half of the ULP for the result, we need to check one more partial to know which way to round if (n >= 0 && ((lo < 0.0 && partials[n] < 0.0) || (lo > 0.0 && partials[n] > 0.0))) { var y2 = lo * 2.0; var x2 = hi + y2; var yr = x2 - hi; if (y2 == yr) { hi = x2; } } return hi; } }