SumPrecise.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. using System.Runtime.CompilerServices;
  2. using System.Runtime.InteropServices;
  3. namespace Jint.Native.Math;
  4. /// <summary>
  5. /// https://raw.githubusercontent.com/es-shims/Math.sumPrecise/main/sum.js
  6. /// adapted from https://github.com/tc39/proposal-math-sum/blob/f4286d0a9d8525bda61be486df964bf2527c8789/polyfill/polyfill.mjs
  7. /// https://www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps
  8. /// Shewchuk's algorithm for exactly floating point addition
  9. /// as implemented in Python's fsum: https://github.com/python/cpython/blob/48dfd74a9db9d4aa9c6f23b4a67b461e5d977173/Modules/mathmodule.c#L1359-L1474
  10. /// adapted to handle overflow via an additional "biased" partial, representing 2**1024 times its actual value
  11. /// </summary>
  12. internal static class SumPrecise
  13. {
  14. // exponent 11111111110, significand all 1s
  15. private const double MaxDouble = 1.79769313486231570815e+308; // i.e. (2**1024 - 2**(1023 - 52))
  16. // exponent 11111111110, significand all 1s except final 0
  17. private const double PenultimateDouble = 1.79769313486231550856e+308; // i.e. (2**1024 - 2 * 2**(1023 - 52))
  18. private const double Two1023 = 8.98846567431158e+307; // 2 ** 1023
  19. // exponent 11111001010, significand all 0s
  20. private const double MaxUlp = MaxDouble - PenultimateDouble; // 1.99584030953471981166e+292, i.e. 2**(1023 - 52)
  21. [StructLayout(LayoutKind.Auto)]
  22. private readonly record struct TwoSumResult(double Hi, double Lo);
  23. // prerequisite: $abs(x) >= $abs(y)
  24. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  25. private static TwoSumResult TwoSum(double x, double y)
  26. {
  27. var hi = x + y;
  28. var lo = y - (hi - x);
  29. return new TwoSumResult(hi, lo);
  30. }
  31. // preconditions:
  32. // - array only contains numbers
  33. // - none of them are -0, NaN, or ±Infinity
  34. // - all of them are finite
  35. [MethodImpl(512)]
  36. internal static double Sum(List<double> array)
  37. {
  38. double hi, lo;
  39. List<double> partials = [];
  40. var overflow = 0; // conceptually 2**1024 times this value; the final partial is biased by this amount
  41. var index = -1;
  42. // main loop
  43. while (index + 1 < array.Count)
  44. {
  45. var x = +array[++index];
  46. var actuallyUsedPartials = 0;
  47. for (var j = 0; j < partials.Count; j += 1)
  48. {
  49. var y = partials[j];
  50. if (System.Math.Abs(x) < System.Math.Abs(y))
  51. {
  52. var tmp = x;
  53. x = y;
  54. y = tmp;
  55. }
  56. (hi, lo) = TwoSum(x, y);
  57. if (double.IsPositiveInfinity(System.Math.Abs(hi)))
  58. {
  59. var sign = double.IsPositiveInfinity(hi) ? 1 : -1;
  60. overflow += sign;
  61. x = x - sign * Two1023 - sign * Two1023;
  62. if (System.Math.Abs(x) < System.Math.Abs(y))
  63. {
  64. var tmp2 = x;
  65. x = y;
  66. y = tmp2;
  67. }
  68. var s2 = TwoSum(x, y);
  69. hi = s2.Hi;
  70. lo = s2.Lo;
  71. }
  72. if (lo != 0)
  73. {
  74. partials[actuallyUsedPartials] = lo;
  75. actuallyUsedPartials += 1;
  76. }
  77. x = hi;
  78. }
  79. while (partials.Count > actuallyUsedPartials)
  80. {
  81. partials.RemoveAt(partials.Count - 1);
  82. }
  83. if (x != 0)
  84. {
  85. partials.Add(x);
  86. }
  87. }
  88. // compute the exact sum of partials, stopping once we lose precision
  89. var n = partials.Count - 1;
  90. hi = 0;
  91. lo = 0;
  92. if (overflow != 0)
  93. {
  94. var next = n >= 0 ? partials[n] : 0;
  95. n -= 1;
  96. if (System.Math.Abs(overflow) > 1 || (overflow > 0 && next > 0) || (overflow < 0 && next < 0))
  97. {
  98. return overflow > 0 ? double.PositiveInfinity : double.NegativeInfinity;
  99. }
  100. // here we actually have to do the arithmetic
  101. // drop a factor of 2 so we can do it without overflow
  102. // assert($abs(overflow) === 1)
  103. var s = TwoSum(overflow * Two1023, next / 2);
  104. hi = s.Hi;
  105. lo = s.Lo;
  106. lo *= 2;
  107. if (double.IsPositiveInfinity(System.Math.Abs(2 * hi)))
  108. {
  109. // stupid edge case: rounding to the maximum value
  110. // 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
  111. // but if the next partial has the opposite sign of the current value, we need to round towards MAX_DOUBLE instead
  112. // 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
  113. if (hi > 0)
  114. {
  115. if (hi == Two1023 && lo == -(MaxUlp / 2) && n >= 0 && partials[n] < 0)
  116. {
  117. return MaxDouble;
  118. }
  119. return double.PositiveInfinity;
  120. }
  121. if (hi == -Two1023 && lo == (MaxUlp / 2) && n >= 0 && partials[n] > 0)
  122. {
  123. return -MaxDouble;
  124. }
  125. return double.NegativeInfinity;
  126. }
  127. if (lo != 0)
  128. {
  129. partials[n + 1] = lo;
  130. n += 1;
  131. lo = 0;
  132. }
  133. hi *= 2;
  134. }
  135. while (n >= 0)
  136. {
  137. var x1 = hi;
  138. var y1 = partials[n];
  139. n -= 1;
  140. // assert: $abs(x1) > $abs(y1)
  141. (hi, lo) = TwoSum(x1, y1);
  142. if (lo != 0)
  143. {
  144. break; // eslint-disable-line no-restricted-syntax
  145. }
  146. }
  147. // handle rounding
  148. // 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
  149. if (n >= 0 && ((lo < 0.0 && partials[n] < 0.0) || (lo > 0.0 && partials[n] > 0.0)))
  150. {
  151. var y2 = lo * 2.0;
  152. var x2 = hi + y2;
  153. var yr = x2 - hi;
  154. if (y2 == yr)
  155. {
  156. hi = x2;
  157. }
  158. }
  159. return hi;
  160. }
  161. }