Skip to content

Commit 57b91b0

Browse files
committed
Merge 'fix-bignum-to-double-rounding-maint' into maint
OTP-20317
2 parents fca5dc1 + aae60d0 commit 57b91b0

2 files changed

Lines changed: 169 additions & 9 deletions

File tree

erts/emulator/beam/big.c

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,19 +2411,117 @@ erts_uint64_array_to_big(Uint **hpp, int neg, int len, Uint64 *array)
24112411
int
24122412
big_to_double(Eterm x, double* resp)
24132413
{
2414-
double d = 0.0;
24152414
Eterm* xp = big_val(x);
24162415
dsize_t xl = BIG_SIZE(xp);
2417-
ErtsDigit* s = BIG_V(xp) + xl;
2416+
ErtsDigit* v = BIG_V(xp);
24182417
short xsgn = BIG_SIGN(xp);
2419-
double dbase = ((double)(D_MASK)+1);
2418+
ErtsDigit msd;
2419+
Uint64 mant;
2420+
int bitlen, guard, msd_bits, half_bit, exp;
2421+
ErtsDigit lesser_bits;
2422+
dsize_t i;
2423+
double d;
2424+
2425+
ASSERT(xl > 0);
2426+
msd = v[xl-1];
2427+
ASSERT(msd != 0);
24202428

2421-
while (xl--) {
2422-
d = d * dbase + *--s;
2429+
/* Bit length of the most significant digit, and of the whole value. */
2430+
msd_bits = erts_fit_in_bits_uint(msd);
2431+
bitlen = (xl-1) * D_EXP + msd_bits;
2432+
2433+
#if D_EXP == 64
2434+
ERTS_CT_ASSERT(SMALL_BITS > 53);
2435+
ASSERT(bitlen > 53);
2436+
#elif D_EXP == 32
2437+
if (bitlen <= 53) {
2438+
/*
2439+
* The value fits the double mantissa exactly, so accumulating
2440+
* digit by digit cannot round.
2441+
*/
2442+
ASSERT(xl == 1 || xl == 2);
24232443

2424-
if (!erts_isfinite(d)) {
2425-
return -1;
2444+
d = (double) v[0];
2445+
if (xl == 2) {
2446+
const double dbase = ((double)(D_MASK)+1);
2447+
d += ((double) v[1]) * dbase;
24262448
}
2449+
*resp = xsgn ? -d : d;
2450+
return 0;
2451+
}
2452+
#endif
2453+
2454+
/*
2455+
* More than 1024 bits is at least 2^1024, above the largest finite double
2456+
* (2^1024 - 2^971). Reject it here rather than in the loop below, which
2457+
* may visit every digit to determine rounding. Exactly 1024 bits can still
2458+
* be finite, so it takes the rounding path, where the erts_isfinite()
2459+
* check catches a mantissa that carries up to 2^1024.
2460+
*/
2461+
if (bitlen > 1024) {
2462+
return -1;
2463+
}
2464+
2465+
/*
2466+
* Wider than the mantissa, so the result must be rounded. Accumulating
2467+
* `d = d * base + digit` per digit rounds once per digit and compounds
2468+
* the error, which can land on the wrong side of the true value; IEEE 754
2469+
* requires the nearest representable double, ties to even.
2470+
*
2471+
* First take the top 54 bits (53 of mantissa plus one half bit).
2472+
*
2473+
* Then visit as few lower bignum words as possible to determine rounding.
2474+
* Round up if the half bit is set and either the mantissa is odd
2475+
* or some lesser bits are set.
2476+
*/
2477+
guard = bitlen - 54;
2478+
mant = 0;
2479+
2480+
for (i = xl-1; ; i--) {
2481+
ErtsDigit dig = v[i];
2482+
Uint lsb = i * D_EXP; /* bit position of this digit's LSB */
2483+
2484+
if (lsb > guard) {
2485+
/* Entirely above the cut; every set bit lands within 54 bits. */
2486+
mant |= (Uint64)dig << (lsb - guard);
2487+
} else {
2488+
/* Lowest part of mantissa plus maybe some lesser bits */
2489+
Uint k = guard - lsb;
2490+
2491+
lesser_bits = (dig & (((ErtsDigit)1 << k) - 1));
2492+
mant |= (Uint64)(dig >> k);
2493+
break;
2494+
}
2495+
}
2496+
2497+
half_bit = (int)(mant & 1);
2498+
mant >>= 1; /* 53 significant bits remain */
2499+
exp = (int)(guard + 1);
2500+
2501+
/* Round to nearest, ties to even. */
2502+
if (half_bit) {
2503+
if (!(mant & 1)) {
2504+
while (!lesser_bits) {
2505+
if (i == 0) {
2506+
/* Exactly even and a half, round down */
2507+
goto mant_exp_done;
2508+
}
2509+
lesser_bits = v[--i];
2510+
}
2511+
}
2512+
2513+
/* Round up */
2514+
mant++;
2515+
if (mant == ((Uint64)1 << 53)) {
2516+
mant >>= 1; /* carried out of the mantissa */
2517+
exp++;
2518+
}
2519+
}
2520+
mant_exp_done:
2521+
2522+
d = ldexp((double)mant, exp);
2523+
if (!erts_isfinite(d)) {
2524+
return -1;
24272525
}
24282526

24292527
*resp = xsgn ? -d : d;

erts/emulator/test/big_SUITE.erl

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
-export([t_div/1, eq_28/1, eq_32/1, eq_big/1, eq_math/1, eq_big_mul_div/1,
2828
eq_big_rem/1,
2929
big_literals/1, borders/1, negative/1, karatsuba/1,
30-
big_float_1/1, big_float_2/1,
30+
big_float_1/1, big_float_2/1, big_float_3/1,
3131
bxor_2pow/1, band_2pow/1,
3232
shift_limit_1/1, powmod/1, system_limit/1, toobig/1, otp_6692/1,
3333
properties/1, reductions/1]).
@@ -56,7 +56,7 @@ all() ->
5656
properties, reductions].
5757

5858
groups() ->
59-
[{big_float, [], [big_float_1, big_float_2]}].
59+
[{big_float, [], [big_float_1, big_float_2, big_float_3]}].
6060

6161
%%
6262
%% Syntax of data files:
@@ -353,6 +353,68 @@ big_float_2(Config) when is_list(Config) ->
353353
{'EXIT', _} = (catch 4/(2*I)),
354354
ok.
355355

356+
%% Converting a bignum to a float must give the nearest representable
357+
%% double, ties to even. Accumulating digit by digit rounds once per digit
358+
%% and compounds the error, which lands on the wrong side of the true value
359+
%% for some values wider than one digit.
360+
big_float_3(Config) when is_list(Config) ->
361+
rand_seed(),
362+
%% Each of these converted to the second-nearest double when the
363+
%% conversion rounded per digit.
364+
[begin
365+
Nearest = correctly_rounded(I),
366+
Nearest = float(I),
367+
Nearest = 1.0 * I,
368+
NegNearest = -Nearest,
369+
NegNearest = float(-I)
370+
end
371+
|| I <- [428654966685883400000,
372+
38409289721754710000,
373+
34784104853086640000,
374+
385269108828434300000,
375+
96874578115970900000,
376+
252558769001389900000,
377+
26465126867694860000]],
378+
379+
%% Widths on both sides of the single-digit boundary, where the
380+
%% per-digit accumulation starts to compound.
381+
for(50, 300,
382+
fun(Bits) ->
383+
for(1, 200,
384+
fun(_) ->
385+
I = rand:uniform(1 bsl Bits),
386+
Nearest = correctly_rounded(I),
387+
Nearest = float(I)
388+
end)
389+
end),
390+
391+
%% 2-pows and neighbours
392+
[begin
393+
I = (1 bsl E) + Diff,
394+
Nearest = correctly_rounded(I),
395+
Nearest = float(I)
396+
end
397+
|| E <- lists:seq(0, 1023), Diff <- lists:seq(-2,2)],
398+
399+
%% Mantissa rounding edge cases
400+
[begin
401+
Mant = (1 bsl 52) + Odd,
402+
I = (Mant bsl Exp) + (Half bsl (Exp-1)) + (1 bsl Low),
403+
Nearest = correctly_rounded(I),
404+
Nearest = float(I)
405+
end
406+
|| Exp <- lists:seq(1, 1023-53),
407+
Odd <- [0,1],
408+
Half <- [1],
409+
Low <- [-1 | lists:seq(0, Exp-2, 8)]],
410+
411+
ok.
412+
413+
%% The platform's decimal parser is correctly rounded, so it serves as the
414+
%% oracle for what float/1 must return.
415+
correctly_rounded(I) ->
416+
binary_to_float(iolist_to_binary([integer_to_list(I), ".0"])).
417+
356418
%% OTP-3256
357419
shift_limit_1(Config) when is_list(Config) ->
358420
case catch (id(1) bsl 100000000) of

0 commit comments

Comments
 (0)