Skip to content

Commit f49d6a4

Browse files
author
Alexey Shvayka
committed
[JSC] Implement Math.sumPrecise()
https://bugs.webkit.org/show_bug.cgi?id=279768 <rdar://131580043> Reviewed by Yusuke Suzuki. This patch implements Math.sumPrecise() proposal [1] behind a flag by importing its polyfill [2] and rewriting it in C++, with a minor tweak of how negative zero edge case is handled (without extra loop) and proper iterator closure. radfordneal/xsum [3], mentioned in the proposal, didn't pass all the test262 tests. [1]: https://github.com/tc39/proposal-math-sum [2]: https://github.com/tc39/proposal-math-sum/blob/main/polyfill/polyfill.mjs [3]: https://gitlab.com/radfordneal/xsum * JSTests/stress/math-sum-precise.js: Added. * JSTests/test262/config.yaml: * Source/JavaScriptCore/runtime/MathObject.cpp: (JSC::MathObject::finishCreation): (JSC::JSC_DEFINE_HOST_FUNCTION): * Source/JavaScriptCore/runtime/OptionsList.h: * Source/WTF/LICENSE-SumPrecise.txt: Added. * Source/WTF/WTF.xcodeproj/project.pbxproj: * Source/WTF/wtf/CMakeLists.txt: * Source/WTF/wtf/sum_precise/LICENSE.txt: Added. * Source/WTF/wtf/sum_precise/SumPrecise.cpp: Added. (WTF::SumPrecise::twosum): (WTF::SumPrecise::add): (WTF::SumPrecise::compute): * Source/WTF/wtf/sum_precise/SumPrecise.h: Added. Canonical link: https://commits.webkit.org/283774@main
1 parent dc269f2 commit f49d6a4

File tree

8 files changed

+321
-1
lines changed

8 files changed

+321
-1
lines changed

JSTests/stress/math-sum-precise.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//@ requireOptions("--useMathSumPreciseMethod=1")
2+
3+
// Copyright (C) 2024 Kevin Gibbons. All rights reserved.
4+
// This code is governed by the BSD license found in the https://github.com/tc39/test262/blob/main/LICENSE file.
5+
6+
function shouldBe(actual, expected) {
7+
if (!Object.is(actual, expected))
8+
throw new Error(`Bad value: ${actual}!`);
9+
}
10+
11+
for (var i = 0; i < 1e4; i++) {
12+
shouldBe(Math.sumPrecise([1, 2, 3]), 6);
13+
shouldBe(Math.sumPrecise([1e308]), 1e308);
14+
shouldBe(Math.sumPrecise([1e308, -1e308]), 0);
15+
shouldBe(Math.sumPrecise([0.1]), 0.1);
16+
shouldBe(Math.sumPrecise([0.1, 0.1]), 0.2);
17+
shouldBe(Math.sumPrecise([0.1, -0.1]), 0);
18+
shouldBe(Math.sumPrecise([1e308, 1e308, 0.1, 0.1, 1e30, 0.1, -1e30, -1e308, -1e308]), 0.30000000000000004);
19+
shouldBe(Math.sumPrecise([1e30, 0.1, -1e30]), 0.1);
20+
21+
// These cases are chosen for having exercised bugs in real implementations.
22+
// There is no other logic behind choice of constants.
23+
shouldBe(Math.sumPrecise([8.98846567431158e+307, 8.988465674311579e+307, -1.7976931348623157e+308]), 9.9792015476736e+291);
24+
shouldBe(Math.sumPrecise([-5.630637621603525e+255, 9.565271205476345e+307, 2.9937604643020797e+292]), 9.565271205476347e+307);
25+
shouldBe(Math.sumPrecise([6.739986666787661e+66, 2, -1.2689709186578243e-116, 1.7046015739467354e+308, -9.979201547673601e+291, 6.160926733208294e+307, -3.179557053031852e+234, -7.027282978772846e+307, -0.7500000000000001]), 1.61796594939028e+308);
26+
shouldBe(Math.sumPrecise([0.31150493246968836, -8.988465674311582e+307, 1.8315037361673755e-270, -15.999999999999996, 2.9999999999999996, 7.345200721499384e+164, -2.033582473639399, -8.98846567431158e+307, -3.5737295155405993e+292, 4.13894772383715e-124, -3.6111186457260667e-35, 2.387234887098013e+180, 7.645295562778372e-298, 3.395189016861822e-103, -2.6331611115768973e-149]), -Infinity);
27+
shouldBe(Math.sumPrecise([-1.1442589134409902e+308, 9.593842098384855e+138, 4.494232837155791e+307, -1.3482698511467367e+308, 4.494232837155792e+307]), -1.5936821971565685e+308);
28+
shouldBe(Math.sumPrecise([-1.1442589134409902e+308, 4.494232837155791e+307, -1.3482698511467367e+308, 4.494232837155792e+307]), -1.5936821971565687e+308);
29+
shouldBe(Math.sumPrecise([9.593842098384855e+138, -6.948356297254111e+307, -1.3482698511467367e+308, 4.494232837155792e+307]), -1.5936821971565685e+308);
30+
shouldBe(Math.sumPrecise([-2.534858246857893e+115, 8.988465674311579e+307, 8.98846567431158e+307]), 1.7976931348623157e+308);
31+
shouldBe(Math.sumPrecise([1.3588124894186193e+308, 1.4803986201152006e+223, 6.741349255733684e+307]), Infinity);
32+
shouldBe(Math.sumPrecise([6.741349255733684e+307, 1.7976931348623155e+308, -7.388327292663961e+41]), Infinity);
33+
shouldBe(Math.sumPrecise([-1.9807040628566093e+28, 1.7976931348623157e+308, 9.9792015476736e+291]), 1.7976931348623157e+308);
34+
shouldBe(Math.sumPrecise([-1.0214557991173964e+61, 1.7976931348623157e+308, 8.98846567431158e+307, -8.988465674311579e+307]), 1.7976931348623157e+308);
35+
shouldBe(Math.sumPrecise([1.7976931348623157e+308, 7.999999999999999, -1.908963895403937e-230, 1.6445950082320264e+292, 2.0734856707605806e+205]), Infinity);
36+
shouldBe(Math.sumPrecise([6.197409167220438e-223, -9.979201547673601e+291, -1.7976931348623157e+308]), -Infinity);
37+
shouldBe(Math.sumPrecise([4.49423283715579e+307, 8.944251746776101e+307, -0.0002441406250000001, 1.1752060710043817e+308, 4.940846717201632e+292, -1.6836699406454528e+308]), 8.353845887521184e+307);
38+
shouldBe(Math.sumPrecise([8.988465674311579e+307, 7.999999999999998, 7.029158107234023e-308, -2.2303483759420562e-172, -1.7976931348623157e+308, -8.98846567431158e+307]), -1.7976931348623157e+308);
39+
shouldBe(Math.sumPrecise([8.98846567431158e+307, 8.98846567431158e+307]), Infinity);
40+
41+
shouldBe(Math.sumPrecise([NaN]), NaN);
42+
shouldBe(Math.sumPrecise([Infinity, -Infinity]), NaN);
43+
shouldBe(Math.sumPrecise([-Infinity, Infinity]), NaN);
44+
45+
shouldBe(Math.sumPrecise([Infinity]), Infinity);
46+
shouldBe(Math.sumPrecise([Infinity, Infinity]), Infinity);
47+
shouldBe(Math.sumPrecise([-Infinity]), -Infinity);
48+
shouldBe(Math.sumPrecise([-Infinity, -Infinity]), -Infinity);
49+
50+
shouldBe(Math.sumPrecise([]), -0);
51+
shouldBe(Math.sumPrecise([-0]), -0);
52+
shouldBe(Math.sumPrecise([-0, -0]), -0);
53+
shouldBe(Math.sumPrecise([-0, 0]), 0);
54+
}

JSTests/test262/config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ skip:
1818
- regexp-modifiers
1919
- source-phase-imports
2020
- Atomics.pause
21-
- Math.sumPrecise
2221
paths:
2322
- test/built-ins/Temporal/Calendar
2423
- test/built-ins/Temporal/Instant/prototype/toZonedDateTime

Source/JavaScriptCore/runtime/MathObject.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "MathCommon.h"
2626
#include <wtf/Assertions.h>
2727
#include <wtf/MathExtras.h>
28+
#include <wtf/PreciseSum.h>
2829
#include <wtf/Vector.h>
2930

3031
namespace JSC {
@@ -64,6 +65,7 @@ static JSC_DECLARE_HOST_FUNCTION(mathProtoFuncTanh);
6465
static JSC_DECLARE_HOST_FUNCTION(mathProtoFuncTrunc);
6566
static JSC_DECLARE_HOST_FUNCTION(mathProtoFuncIMul);
6667
static JSC_DECLARE_HOST_FUNCTION(mathProtoFuncF16Round);
68+
static JSC_DECLARE_HOST_FUNCTION(mathProtoFuncSumPrecise);
6769

6870
const ClassInfo MathObject::s_info = { "Math"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(MathObject) };
6971

@@ -123,6 +125,9 @@ void MathObject::finishCreation(VM& vm, JSGlobalObject* globalObject)
123125
putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier::fromString(vm, "trunc"_s), 1, mathProtoFuncTrunc, ImplementationVisibility::Public, TruncIntrinsic, static_cast<unsigned>(PropertyAttribute::DontEnum));
124126
putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier::fromString(vm, "imul"_s), 2, mathProtoFuncIMul, ImplementationVisibility::Public, IMulIntrinsic, static_cast<unsigned>(PropertyAttribute::DontEnum));
125127
putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier::fromString(vm, "f16round"_s), 1, mathProtoFuncF16Round, ImplementationVisibility::Public, F16RoundIntrinsic, static_cast<unsigned>(PropertyAttribute::DontEnum));
128+
129+
if (Options::useMathSumPreciseMethod())
130+
putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier::fromString(vm, "sumPrecise"_s), 1, mathProtoFuncSumPrecise, ImplementationVisibility::Public, NoIntrinsic, static_cast<unsigned>(PropertyAttribute::DontEnum));
126131
}
127132

128133
// ------------------------------ Functions --------------------------------
@@ -397,4 +402,29 @@ JSC_DEFINE_HOST_FUNCTION(mathProtoFuncF16Round, (JSGlobalObject* globalObject, C
397402
return JSValue::encode(jsDoubleNumber(Float16 { callFrame->argument(0).toNumber(globalObject) }));
398403
}
399404

405+
JSC_DEFINE_HOST_FUNCTION(mathProtoFuncSumPrecise, (JSGlobalObject* globalObject, CallFrame* callFrame))
406+
{
407+
VM& vm = globalObject->vm();
408+
auto scope = DECLARE_THROW_SCOPE(vm);
409+
410+
JSValue iterable = callFrame->argument(0);
411+
if (iterable.isUndefinedOrNull())
412+
return throwVMTypeError(globalObject, scope, "Math.sumPrecise requires first argument not be null or undefined"_s);
413+
414+
PreciseSum sum;
415+
416+
scope.release();
417+
forEachInIterable(globalObject, iterable, [&sum](VM& vm, JSGlobalObject* globalObject, JSValue value) {
418+
auto scope = DECLARE_THROW_SCOPE(vm);
419+
if (!value.isNumber()) {
420+
throwTypeError(globalObject, scope, "Math.sumPrecise was passed a non-number"_s);
421+
return;
422+
}
423+
424+
sum.add(value.asNumber());
425+
});
426+
427+
return JSValue::encode(jsNumber(sum.compute()));
428+
}
429+
400430
} // namespace JSC

Source/JavaScriptCore/runtime/OptionsList.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,7 @@ bool hasCapacityToUseLargeGigacage();
584584
\
585585
v(Bool, useFloat16Array, true, Normal, "Expose Float16Array."_s) \
586586
v(Bool, useIteratorHelpers, false, Normal, "Expose the Iterator Helpers."_s) \
587+
v(Bool, useMathSumPreciseMethod, false, Normal, "Expose the Math.sumPrecise() method."_s) \
587588
v(Bool, usePromiseTryMethod, true, Normal, "Expose the Promise.try() method."_s) \
588589
v(Bool, useRegExpEscape, true, Normal, "Expose RegExp.escape feature."_s) \
589590
v(Bool, useSharedArrayBuffer, false, Normal, nullptr) \

Source/WTF/WTF.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@
129129
7B8A1DC82A336B9000A4CE4E /* SequenceLocked.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B8A1DC72A336B9000A4CE4E /* SequenceLocked.h */; settings = {ATTRIBUTES = (Private, ); }; };
130130
8134013815B092FD001FF0B8 /* Base64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8134013615B092FD001FF0B8 /* Base64.cpp */; };
131131
8348BA0E21FBC0D500FD3054 /* ObjectIdentifier.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8348BA0D21FBC0D400FD3054 /* ObjectIdentifier.cpp */; };
132+
8AB05DFB2C99482E00738BDD /* PreciseSum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8AB05DF92C99482E00738BDD /* PreciseSum.cpp */; };
133+
8AB05DFC2C99482E00738BDD /* PreciseSum.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AB05DFA2C99482E00738BDD /* PreciseSum.h */; settings = {ATTRIBUTES = (Private, ); }; };
132134
93853DD328755A6C00FF4E2B /* EscapedFormsForJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 93853DD228755A6600FF4E2B /* EscapedFormsForJSON.h */; settings = {ATTRIBUTES = (Private, ); }; };
133135
93934BD318A1E8C300D0D6A1 /* StringViewCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = 93934BD218A1E8C300D0D6A1 /* StringViewCocoa.mm */; };
134136
93934BD518A1F16900D0D6A1 /* StringViewCF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 93934BD418A1F16900D0D6A1 /* StringViewCF.cpp */; };
@@ -1309,6 +1311,8 @@
13091311
83FBA93119DF459700F30ADB /* TypeCasts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TypeCasts.h; sourceTree = "<group>"; };
13101312
862A8D32278DE74A0014120C /* SignedPtr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignedPtr.h; sourceTree = "<group>"; };
13111313
86F46F5F1A2840EE00CCBF22 /* RefCounter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RefCounter.h; sourceTree = "<group>"; };
1314+
8AB05DF92C99482E00738BDD /* PreciseSum.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PreciseSum.cpp; sourceTree = "<group>"; };
1315+
8AB05DFA2C99482E00738BDD /* PreciseSum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreciseSum.h; sourceTree = "<group>"; };
13121316
93156C8D262C982200EAE27B /* SortedArrayMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SortedArrayMap.h; sourceTree = "<group>"; };
13131317
93241657243BC2E50032FAAE /* VectorCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VectorCocoa.h; sourceTree = "<group>"; };
13141318
933D63191FCB6AB90032ECD6 /* StringHasher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringHasher.h; sourceTree = "<group>"; };
@@ -2338,6 +2342,8 @@
23382342
7CFAF3C923D0AF1500D21BDD /* PlatformUse.h */,
23392343
0FF860941BCCBD740045127F /* PointerComparison.h */,
23402344
FEB6B035201BE0B600B958C1 /* PointerPreparations.h */,
2345+
8AB05DF92C99482E00738BDD /* PreciseSum.cpp */,
2346+
8AB05DFA2C99482E00738BDD /* PreciseSum.h */,
23412347
0F9D335D165DBA73005AD387 /* PrintStream.cpp */,
23422348
0F9D335E165DBA73005AD387 /* PrintStream.h */,
23432349
53EC253C1E95AD30000831B9 /* PriorityQueue.h */,
@@ -3407,6 +3413,7 @@
34073413
DD3DC96F27A4BF8E007E5B61 /* PointerPreparations.h in Headers */,
34083414
FFE39CAC2B1D7472004972B0 /* policy.h in Headers */,
34093415
FFE39CB22B1D7472004972B0 /* policy_holder.h in Headers */,
3416+
8AB05DFC2C99482E00738BDD /* PreciseSum.h in Headers */,
34103417
DD3DC93227A4BF8E007E5B61 /* PrintStream.h in Headers */,
34113418
DD3DC95027A4BF8E007E5B61 /* PriorityQueue.h in Headers */,
34123419
DD3DC89927A4BF8E007E5B61 /* ProcessID.h in Headers */,
@@ -4067,6 +4074,7 @@
40674074
51F1752C1F3D486000C74950 /* PersistentDecoder.cpp in Sources */,
40684075
51F1752D1F3D486000C74950 /* PersistentEncoder.cpp in Sources */,
40694076
FE1E2C42224187C600F6B729 /* PlatformRegisters.cpp in Sources */,
4077+
8AB05DFB2C99482E00738BDD /* PreciseSum.cpp in Sources */,
40704078
0F9D3362165DBA73005AD387 /* PrintStream.cpp in Sources */,
40714079
7AF023B52061E17000A8EFD6 /* ProcessPrivilege.cpp in Sources */,
40724080
FE1E2C3B2240C06600F6B729 /* PtrTag.cpp in Sources */,

Source/WTF/wtf/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ set(WTF_PUBLIC_HEADERS
217217
PlatformUse.h
218218
PointerComparison.h
219219
PointerPreparations.h
220+
PreciseSum.h
220221
PrintStream.h
221222
PriorityQueue.h
222223
ProcessID.h
@@ -527,6 +528,7 @@ set(WTF_SOURCES
527528
ParallelHelperPool.cpp
528529
ParallelJobsGeneric.cpp
529530
ParkingLot.cpp
531+
PreciseSum.cpp
530532
PrintStream.cpp
531533
ProcessPrivilege.cpp
532534
RAMSize.cpp

Source/WTF/wtf/PreciseSum.cpp

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright (C) 2024 Apple Inc. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions
6+
* are met:
7+
* 1. Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* 2. Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
*
13+
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21+
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
26+
/*
27+
* This is the Math.sumPrecise() polyfill by Kevin Gibbons, ported to C++. Original LICENSE is as follows:
28+
*
29+
* BSD 3-Clause License
30+
*
31+
* Copyright (c) 2024 Kevin Gibbons
32+
*
33+
* Redistribution and use in source and binary forms, with or without modification,
34+
* are permitted provided that the following conditions are met:
35+
*
36+
* 1. Redistributions of source code must retain the above copyright notice,
37+
* this list of conditions and the following disclaimer.
38+
*
39+
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
40+
* and the following disclaimer in the documentation and/or other materials provided with the distribution.
41+
*
42+
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse
43+
* or promote products derived from this software without specific prior written permission.
44+
*
45+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED
46+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
47+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
48+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
49+
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
50+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
51+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
52+
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
53+
*/
54+
55+
#include "config.h"
56+
#include <wtf/PreciseSum.h>
57+
58+
#include <cmath>
59+
60+
namespace WTF {
61+
62+
static constexpr double MAX_DOUBLE = 1.79769313486231570815e+308;
63+
static constexpr double PENULTIMATE_DOUBLE = 1.79769313486231550856e+308;
64+
static constexpr double MAX_ULP = MAX_DOUBLE - PENULTIMATE_DOUBLE;
65+
static constexpr double TWO_POW_1023 = 8.98846567431158e+307;
66+
67+
ALWAYS_INLINE std::pair<double, double> twosum(double x, double y)
68+
{
69+
double hi = x + y;
70+
double lo = y - (hi - x);
71+
return std::make_pair(hi, lo);
72+
}
73+
74+
void PreciseSum::add(double x)
75+
{
76+
if (!(!x && std::signbit(x)))
77+
m_everyValueIsNegativeZero = false;
78+
79+
unsigned actuallyUsedPartials = 0;
80+
81+
for (double y : m_partials) {
82+
if (std::fabs(x) < std::fabs(y))
83+
std::swap(x, y);
84+
85+
auto pair = twosum(x, y);
86+
if (std::isinf(std::fabs(pair.first))) {
87+
double sign = pair.first < 0 ? -1 : 1;
88+
m_overflow += sign;
89+
90+
x = (x - sign * TWO_POW_1023) - sign * TWO_POW_1023;
91+
if (std::fabs(x) < std::fabs(y))
92+
std::swap(x, y);
93+
94+
pair = twosum(x, y);
95+
}
96+
97+
if (auto lo = pair.second) {
98+
m_partials[actuallyUsedPartials] = lo;
99+
++actuallyUsedPartials;
100+
}
101+
102+
x = pair.first;
103+
}
104+
105+
m_partials.shrink(actuallyUsedPartials);
106+
107+
if (x)
108+
m_partials.append(x);
109+
}
110+
111+
double PreciseSum::compute()
112+
{
113+
if (m_everyValueIsNegativeZero)
114+
return -0.0;
115+
116+
int32_t n = m_partials.size() - 1;
117+
double hi = 0;
118+
double lo = 0;
119+
120+
if (m_overflow) {
121+
double next = n >= 0 ? m_partials[n] : 0;
122+
--n;
123+
if (std::fabs(m_overflow) > 1 || (m_overflow > 0 && next > 0) || (m_overflow < 0 && next < 0))
124+
return m_overflow > 0 ? std::numeric_limits<double>::infinity() : -std::numeric_limits<double>::infinity();
125+
126+
auto pair = twosum(m_overflow * TWO_POW_1023, next / 2);
127+
hi = pair.first;
128+
lo = pair.second * 2;
129+
130+
if (std::isinf(std::fabs(hi * 2))) {
131+
// silly edge case: rounding to the maximum value
132+
// 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
133+
// but if the next partial has the opposite sign of the current value, we need to round towards MAX_DOUBLE instead
134+
// 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
135+
if (hi > 0) {
136+
if (hi == TWO_POW_1023 && lo == -(MAX_ULP / 2) && n >= 0 && m_partials[n] < 0)
137+
return MAX_DOUBLE;
138+
return std::numeric_limits<double>::infinity();
139+
}
140+
141+
if (hi == -TWO_POW_1023 && lo == (MAX_ULP / 2) && n >= 0 && m_partials[n] > 0)
142+
return -MAX_DOUBLE;
143+
return -std::numeric_limits<double>::infinity();
144+
}
145+
146+
if (lo) {
147+
m_partials[n + 1] = lo;
148+
++n;
149+
lo = 0;
150+
}
151+
152+
hi *= 2;
153+
}
154+
155+
while (n >= 0) {
156+
double x = hi;
157+
double y = m_partials[n];
158+
--n;
159+
160+
auto pair = twosum(x, y);
161+
hi = pair.first;
162+
lo = pair.second;
163+
164+
if (lo)
165+
break;
166+
}
167+
168+
// handle rounding
169+
// 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
170+
if (n >= 0 && ((lo < 0 && m_partials[n] < 0) || (lo > 0 && m_partials[n] > 0))) {
171+
double y = lo * 2;
172+
double x = hi + y;
173+
double yr = x - hi;
174+
if (y == yr)
175+
hi = x;
176+
}
177+
178+
return hi;
179+
}
180+
181+
} // namespace WTF

0 commit comments

Comments
 (0)