Skip to content

Commit a13b04f

Browse files
bptatoFabrice Bellard
andauthored
Port bellard/quickjs Math.sumPrecise (#1170)
Co-authored-by: Fabrice Bellard <[email protected]>
1 parent 6167dcb commit a13b04f

File tree

8 files changed

+221
-1327
lines changed

8 files changed

+221
-1327
lines changed

CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,6 @@ set(qjs_sources
231231
libregexp.c
232232
libunicode.c
233233
quickjs.c
234-
xsum.c
235234
)
236235

237236
if(QJS_BUILD_LIBC)

amalgam.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ const quickjs_h = loadFile("quickjs.h")
1818
const quickjs_libc_c = loadFile("quickjs-libc.c")
1919
const quickjs_libc_h = loadFile("quickjs-libc.h")
2020
const quickjs_opcode_h = loadFile("quickjs-opcode.h")
21-
const xsum_c = loadFile("xsum.c")
22-
const xsum_h = loadFile("xsum.h")
2321
const gen_builtin_array_fromasync_h = loadFile("builtin-array-fromasync.h")
2422

2523
let source = "#if defined(QJS_BUILD_LIBC) && defined(__linux__) && !defined(_GNU_SOURCE)\n"
@@ -32,14 +30,12 @@ let source = "#if defined(QJS_BUILD_LIBC) && defined(__linux__) && !defined(_GNU
3230
+ libunicode_h // exports lre_is_id_start, used by libregexp.h
3331
+ libregexp_h
3432
+ libunicode_table_h
35-
+ xsum_h
3633
+ quickjs_h
3734
+ quickjs_c
3835
+ cutils_c
3936
+ dtoa_c
4037
+ libregexp_c
4138
+ libunicode_c
42-
+ xsum_c
4339
+ "#ifdef QJS_BUILD_LIBC\n"
4440
+ quickjs_libc_h
4541
+ quickjs_libc_c

fuzz.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include "cutils.c"
55
#include "libregexp.c"
66
#include "libunicode.c"
7-
#include "xsum.c"
87
#include <stdlib.h>
98

109
int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len)

meson.build

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ qjs_srcs = files(
142142
'libregexp.c',
143143
'libunicode.c',
144144
'quickjs.c',
145-
'xsum.c'
146145
)
147146
qjs_hdrs = files(
148147
'quickjs.h',

quickjs.c

Lines changed: 217 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
#include "list.h"
4848
#include "quickjs.h"
4949
#include "libregexp.h"
50-
#include "xsum.h"
5150
#include "dtoa.h"
5251

5352
#if defined(EMSCRIPTEN) || defined(_MSC_VER)
@@ -11998,7 +11997,7 @@ static JSValue js_atof(JSContext *ctx, const char *str, const char **pp,
1199811997
bool buf_allocated = false;
1199911998
JSValue val;
1200011999
JSATODTempMem atod_mem;
12001-
12000+
1200212001
/* optional separator between digits */
1200312002
sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256;
1200412003
has_legacy_octal = false;
@@ -12799,7 +12798,7 @@ static JSValue js_dtoa2(JSContext *ctx,
1279912798
JSValue res;
1280012799
JSDTOATempMem dtoa_mem;
1280112800
len_max = js_dtoa_max_len(d, radix, n_digits, flags);
12802-
12801+
1280312802
/* longer buffer may be used if radix != 10 */
1280412803
if (len_max > sizeof(static_buf) - 1) {
1280512804
tmp_buf = js_malloc(ctx, len_max + 1);
@@ -44667,93 +44666,249 @@ static JSValue js_math_clz32(JSContext *ctx, JSValueConst this_val,
4466744666
return js_int32(r);
4466844667
}
4466944668

44670-
typedef enum SumPreciseStateEnum {
44669+
/* we add one extra limb to avoid having to test for overflows during the sum */
44670+
#define SUM_PRECISE_ACC_LEN 34
44671+
44672+
typedef enum {
4467144673
SUM_PRECISE_STATE_MINUS_ZERO,
44672-
SUM_PRECISE_STATE_NOT_A_NUMBER,
44673-
SUM_PRECISE_STATE_MINUS_INFINITY,
44674-
SUM_PRECISE_STATE_PLUS_INFINITY,
4467544674
SUM_PRECISE_STATE_FINITE,
44675+
SUM_PRECISE_STATE_INFINITY,
44676+
SUM_PRECISE_STATE_MINUS_INFINITY, /* must be after SUM_PRECISE_STATE_INFINITY */
44677+
SUM_PRECISE_STATE_NAN, /* must be after SUM_PRECISE_STATE_MINUS_INFINITY */
4467644678
} SumPreciseStateEnum;
4467744679

44680+
typedef struct {
44681+
uint64_t acc[SUM_PRECISE_ACC_LEN];
44682+
int n_limbs; /* acc is not necessarily normalized */
44683+
SumPreciseStateEnum state;
44684+
} SumPreciseState;
44685+
44686+
static void sum_precise_init(SumPreciseState *s)
44687+
{
44688+
s->state = SUM_PRECISE_STATE_MINUS_ZERO;
44689+
s->acc[0] = 0;
44690+
s->n_limbs = 1;
44691+
}
44692+
44693+
#define ADDC64(res, carry_out, op1, op2, carry_in) \
44694+
do { \
44695+
uint64_t __v, __a, __k, __k1; \
44696+
__v = (op1); \
44697+
__a = __v + (op2); \
44698+
__k1 = __a < __v; \
44699+
__k = (carry_in); \
44700+
__a = __a + __k; \
44701+
carry_out = (__a < __k) | __k1; \
44702+
res = __a; \
44703+
} while (0)
44704+
44705+
static void sum_precise_add(SumPreciseState *s, double d)
44706+
{
44707+
uint64_t a, m, a0, carry, acc_sign, a_sign;
44708+
int sgn, e, p, n, i;
44709+
unsigned shift;
44710+
44711+
a = float64_as_uint64(d);
44712+
sgn = a >> 63;
44713+
e = (a >> 52) & ((1 << 11) - 1);
44714+
m = a & (((uint64_t)1 << 52) - 1);
44715+
if (unlikely(e == 2047)) {
44716+
if (m == 0) {
44717+
/* +/- infinity */
44718+
if (s->state == SUM_PRECISE_STATE_NAN ||
44719+
(s->state == SUM_PRECISE_STATE_MINUS_INFINITY && !sgn) ||
44720+
(s->state == SUM_PRECISE_STATE_INFINITY && sgn)) {
44721+
s->state = SUM_PRECISE_STATE_NAN;
44722+
} else {
44723+
s->state = SUM_PRECISE_STATE_INFINITY + sgn;
44724+
}
44725+
} else {
44726+
/* NaN */
44727+
s->state = SUM_PRECISE_STATE_NAN;
44728+
}
44729+
} else if (e == 0) {
44730+
if (likely(m == 0)) {
44731+
/* zero */
44732+
if (s->state == SUM_PRECISE_STATE_MINUS_ZERO && !sgn)
44733+
s->state = SUM_PRECISE_STATE_FINITE;
44734+
} else {
44735+
/* subnormal */
44736+
p = 0;
44737+
shift = 0;
44738+
goto add;
44739+
}
44740+
} else {
44741+
m |= (uint64_t)1 << 52;
44742+
shift = e - 1;
44743+
p = shift / 64;
44744+
/* 'p' is the position of a0 in acc */
44745+
shift %= 64;
44746+
add:
44747+
if (s->state >= SUM_PRECISE_STATE_INFINITY)
44748+
return;
44749+
s->state = SUM_PRECISE_STATE_FINITE;
44750+
n = s->n_limbs;
44751+
44752+
acc_sign = (int64_t)s->acc[n - 1] >> 63;
44753+
44754+
/* sign extend acc */
44755+
for(i = n; i <= p; i++)
44756+
s->acc[i] = acc_sign;
44757+
44758+
carry = sgn;
44759+
a_sign = -sgn;
44760+
a0 = m << shift;
44761+
ADDC64(s->acc[p], carry, s->acc[p], a0 ^ a_sign, carry);
44762+
if (shift >= 12) {
44763+
p++;
44764+
if (p >= n)
44765+
s->acc[p] = acc_sign;
44766+
a0 = m >> (64 - shift);
44767+
ADDC64(s->acc[p], carry, s->acc[p], a0 ^ a_sign, carry);
44768+
}
44769+
p++;
44770+
if (p >= n) {
44771+
n = p;
44772+
} else {
44773+
/* carry */
44774+
for(i = p; i < n; i++) {
44775+
/* if 'a' positive: stop condition: carry = 0.
44776+
if 'a' negative: stop condition: carry = 1. */
44777+
if (carry == sgn)
44778+
goto done;
44779+
ADDC64(s->acc[i], carry, s->acc[i], a_sign, carry);
44780+
}
44781+
}
44782+
44783+
/* extend the accumulator if needed */
44784+
a0 = carry + acc_sign + a_sign;
44785+
/* -1 <= a0 <= 1 (if both acc and a are negative, carry is set) */
44786+
if (a0 != ((int64_t)s->acc[n - 1] >> 63)) {
44787+
s->acc[n++] = a0;
44788+
}
44789+
done:
44790+
s->n_limbs = n;
44791+
}
44792+
}
44793+
44794+
static double sum_precise_get_result(SumPreciseState *s)
44795+
{
44796+
int n, shift, e, p, is_neg, i;
44797+
uint64_t m, addend, carry;
44798+
44799+
if (s->state != SUM_PRECISE_STATE_FINITE) {
44800+
switch(s->state) {
44801+
default:
44802+
case SUM_PRECISE_STATE_MINUS_ZERO:
44803+
return -0.0;
44804+
case SUM_PRECISE_STATE_INFINITY:
44805+
return INFINITY;
44806+
case SUM_PRECISE_STATE_MINUS_INFINITY:
44807+
return -INFINITY;
44808+
case SUM_PRECISE_STATE_NAN:
44809+
return NAN;
44810+
}
44811+
}
44812+
44813+
/* extract the sign and absolute value */
44814+
n = s->n_limbs;
44815+
is_neg = s->acc[n - 1] >> 63;
44816+
if (is_neg) {
44817+
/* acc = -acc */
44818+
carry = 1;
44819+
for(i = 0; i < n; i++) {
44820+
ADDC64(s->acc[i], carry, ~s->acc[i], 0, carry);
44821+
}
44822+
}
44823+
/* normalize */
44824+
while (n > 0 && s->acc[n - 1] == 0)
44825+
n--;
44826+
/* zero result. The spec tells it is always positive in the finite case */
44827+
if (n == 0)
44828+
return 0.0;
44829+
/* subnormal case */
44830+
if (n == 1 && s->acc[0] < ((uint64_t)1 << 52))
44831+
return uint64_as_float64(((uint64_t)is_neg << 63) | s->acc[0]);
44832+
/* normal case */
44833+
e = n * 64;
44834+
p = n - 1;
44835+
m = s->acc[p];
44836+
shift = clz64(m);
44837+
e = e - shift - 52;
44838+
if (shift != 0) {
44839+
m <<= shift;
44840+
if (p > 0) {
44841+
int shift1;
44842+
uint64_t nz;
44843+
p--;
44844+
shift1 = 64 - shift;
44845+
nz = s->acc[p] & (((uint64_t)1 << shift1) - 1);
44846+
m = m | (s->acc[p] >> shift1) | (nz != 0);
44847+
}
44848+
}
44849+
if ((m & ((1 << 10) - 1)) == 0) {
44850+
/* see if the LSB part is non zero for the final rounding */
44851+
while (p > 0) {
44852+
p--;
44853+
if (s->acc[p] != 0) {
44854+
m |= 1;
44855+
break;
44856+
}
44857+
}
44858+
}
44859+
/* rounding to nearest with ties to even */
44860+
addend = (1 << 10) - 1 + ((m >> 11) & 1);
44861+
m = (m + addend) >> 11;
44862+
/* handle overflow in the rounding */
44863+
if (m == 0)
44864+
e++;
44865+
if (unlikely(e >= 2047)) {
44866+
/* infinity */
44867+
return uint64_as_float64(((uint64_t)is_neg << 63) | ((uint64_t)2047 << 52));
44868+
} else {
44869+
m &= (((uint64_t)1 << 52) - 1);
44870+
return uint64_as_float64(((uint64_t)is_neg << 63) | ((uint64_t)e << 52) | m);
44871+
}
44872+
}
44873+
4467844874
static JSValue js_math_sumPrecise(JSContext *ctx, JSValueConst this_val,
4467944875
int argc, JSValueConst *argv)
4468044876
{
4468144877
JSValue iter, next, item, ret;
44878+
uint32_t tag;
4468244879
int done;
4468344880
double d;
44684-
xsum_small_accumulator acc;
44685-
SumPreciseStateEnum state;
44881+
SumPreciseState s_s, *s = &s_s;
4468644882

44687-
iter = JS_GetIterator(ctx, argv[0], /*async*/false);
44883+
iter = JS_GetIterator(ctx, argv[0], /*is_async*/false);
4468844884
if (JS_IsException(iter))
4468944885
return JS_EXCEPTION;
4469044886
ret = JS_EXCEPTION;
4469144887
next = JS_GetProperty(ctx, iter, JS_ATOM_next);
4469244888
if (JS_IsException(next))
4469344889
goto fail;
44694-
xsum_small_init(&acc);
44695-
state = SUM_PRECISE_STATE_MINUS_ZERO;
44890+
sum_precise_init(s);
4469644891
for (;;) {
4469744892
item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done);
4469844893
if (JS_IsException(item))
4469944894
goto fail;
4470044895
if (done)
44701-
break; // item == JS_UNDEFINED
44702-
switch (JS_VALUE_GET_TAG(item)) {
44703-
default:
44896+
break;
44897+
tag = JS_VALUE_GET_TAG(item);
44898+
if (JS_TAG_IS_FLOAT64(tag)) {
44899+
d = JS_VALUE_GET_FLOAT64(item);
44900+
} else if (tag == JS_TAG_INT) {
44901+
d = JS_VALUE_GET_INT(item);
44902+
} else {
4470444903
JS_FreeValue(ctx, item);
4470544904
JS_ThrowTypeError(ctx, "not a number");
44905+
JS_IteratorClose(ctx, iter, /*is_exception_pending*/true);
4470644906
goto fail;
44707-
case JS_TAG_INT:
44708-
d = JS_VALUE_GET_INT(item);
44709-
break;
44710-
case JS_TAG_FLOAT64:
44711-
d = JS_VALUE_GET_FLOAT64(item);
44712-
break;
44713-
}
44714-
44715-
if (state != SUM_PRECISE_STATE_NOT_A_NUMBER) {
44716-
if (isnan(d))
44717-
state = SUM_PRECISE_STATE_NOT_A_NUMBER;
44718-
else if (!isfinite(d) && d > 0.0)
44719-
if (state == SUM_PRECISE_STATE_MINUS_INFINITY)
44720-
state = SUM_PRECISE_STATE_NOT_A_NUMBER;
44721-
else
44722-
state = SUM_PRECISE_STATE_PLUS_INFINITY;
44723-
else if (!isfinite(d) && d < 0.0)
44724-
if (state == SUM_PRECISE_STATE_PLUS_INFINITY)
44725-
state = SUM_PRECISE_STATE_NOT_A_NUMBER;
44726-
else
44727-
state = SUM_PRECISE_STATE_MINUS_INFINITY;
44728-
else if (!(d == 0.0 && signbit(d)) && (state == SUM_PRECISE_STATE_MINUS_ZERO || state == SUM_PRECISE_STATE_FINITE)) {
44729-
state = SUM_PRECISE_STATE_FINITE;
44730-
xsum_small_add1(&acc, d);
44731-
}
4473244907
}
44908+
sum_precise_add(s, d);
4473344909
}
44734-
44735-
switch (state) {
44736-
case SUM_PRECISE_STATE_NOT_A_NUMBER:
44737-
d = NAN;
44738-
break;
44739-
case SUM_PRECISE_STATE_MINUS_INFINITY:
44740-
d = -INFINITY;
44741-
break;
44742-
case SUM_PRECISE_STATE_PLUS_INFINITY:
44743-
d = INFINITY;
44744-
break;
44745-
case SUM_PRECISE_STATE_MINUS_ZERO:
44746-
d = -0.0;
44747-
break;
44748-
case SUM_PRECISE_STATE_FINITE:
44749-
d = xsum_small_round(&acc);
44750-
break;
44751-
default:
44752-
abort();
44753-
}
44754-
ret = js_float64(d);
44910+
ret = js_float64(sum_precise_get_result(s));
4475544911
fail:
44756-
JS_IteratorClose(ctx, iter, JS_IsException(ret));
4475744912
JS_FreeValue(ctx, iter);
4475844913
JS_FreeValue(ctx, next);
4475944914
return ret;

tests/test_builtin.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,10 +392,11 @@ function test_math()
392392
assert(Math.imul((-2)**31, (-2)**31), 0);
393393
assert(Math.imul(2**31-1, 2**31-1), 1);
394394
assert(Math.fround(0.1), 0.10000000149011612);
395-
assert(Math.hypot() == 0);
396-
assert(Math.hypot(-2) == 2);
397-
assert(Math.hypot(3, 4) == 5);
395+
assert(Math.hypot(), 0);
396+
assert(Math.hypot(-2), 2);
397+
assert(Math.hypot(3, 4), 5);
398398
assert(Math.abs(Math.hypot(3, 4, 5) - 7.0710678118654755) <= 1e-15);
399+
assert(Math.sumPrecise([1,Number.EPSILON/2,Number.MIN_VALUE]), 1.0000000000000002);
399400
}
400401

401402
function test_number()

0 commit comments

Comments
 (0)