Skip to content

Commit 5382a22

Browse files
committed
Corrections in the implementation of '%' for floats.
The multiplication (m*b) used to test whether 'm' is non-zero and 'm' and 'b' have different signs can underflow for very small numbers, giving a wrong result. The use of explicit comparisons solves this problem. This commit also adds several new tests for '%' (both for floats and for integers) to exercise more corner cases, such as very large and very small values.
1 parent 8c8a91f commit 5382a22

5 files changed

Lines changed: 87 additions & 18 deletions

File tree

llimits.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,15 +293,17 @@ typedef unsigned long Instruction;
293293
#endif
294294

295295
/*
296-
** modulo: defined as 'a - floor(a/b)*b'; this definition gives NaN when
297-
** 'b' is huge, but the result should be 'a'. 'fmod' gives the result of
298-
** 'a - trunc(a/b)*b', and therefore must be corrected when 'trunc(a/b)
299-
** ~= floor(a/b)'. That happens when the division has a non-integer
300-
** negative result, which is equivalent to the test below.
296+
** modulo: defined as 'a - floor(a/b)*b'; the direct computation
297+
** using this definition has several problems with rounding errors,
298+
** so it is better to use 'fmod'. 'fmod' gives the result of
299+
** 'a - trunc(a/b)*b', and therefore must be corrected when
300+
** 'trunc(a/b) ~= floor(a/b)'. That happens when the division has a
301+
** non-integer negative result, which is equivalent to the tests below.
301302
*/
302303
#if !defined(luai_nummod)
303304
#define luai_nummod(L,a,b,m) \
304-
{ (m) = l_mathop(fmod)(a,b); if ((m)*(b) < 0) (m) += (b); }
305+
{ (void)L; (m) = l_mathop(fmod)(a,b); \
306+
if (((m) > 0) ? (b) < 0 : ((m) < 0 && (b) > 0)) (m) += (b); }
305307
#endif
306308

307309
/* exponentiation */

lobject.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,7 @@ static lua_Number numarith (lua_State *L, int op, lua_Number v1,
106106
case LUA_OPPOW: return luai_numpow(L, v1, v2);
107107
case LUA_OPIDIV: return luai_numidiv(L, v1, v2);
108108
case LUA_OPUNM: return luai_numunm(L, v1);
109-
case LUA_OPMOD: {
110-
lua_Number m;
111-
luai_nummod(L, v1, v2, m);
112-
return m;
113-
}
109+
case LUA_OPMOD: return luaV_modf(L, v1, v2);
114110
default: lua_assert(0); return 0;
115111
}
116112
}

lvm.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,16 @@ lua_Integer luaV_mod (lua_State *L, lua_Integer m, lua_Integer n) {
655655
}
656656

657657

658+
/*
659+
** Float modulus
660+
*/
661+
lua_Number luaV_modf (lua_State *L, lua_Number m, lua_Number n) {
662+
lua_Number r;
663+
luai_nummod(L, m, n, r);
664+
return r;
665+
}
666+
667+
658668
/* number of bits in an integer */
659669
#define NBITS cast_int(sizeof(lua_Integer) * CHAR_BIT)
660670

@@ -1142,10 +1152,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
11421152
setivalue(s2v(ra), luaV_mod(L, ivalue(rb), ic));
11431153
}
11441154
else if (tonumberns(rb, nb)) {
1145-
lua_Number m;
11461155
lua_Number nc = cast_num(ic);
1147-
luai_nummod(L, nb, nc, m);
1148-
setfltvalue(s2v(ra), m);
1156+
setfltvalue(s2v(ra), luaV_modf(L, nb, nc));
11491157
}
11501158
else
11511159
Protect(luaT_trybiniTM(L, rb, ic, 0, ra, TM_MOD));
@@ -1370,9 +1378,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
13701378
setivalue(s2v(ra), luaV_mod(L, ib, ic));
13711379
}
13721380
else if (tonumberns(rb, nb) && tonumberns(rc, nc)) {
1373-
lua_Number m;
1374-
luai_nummod(L, nb, nc, m);
1375-
setfltvalue(s2v(ra), m);
1381+
setfltvalue(s2v(ra), luaV_modf(L, nb, nc));
13761382
}
13771383
else
13781384
Protect(luaT_trybinTM(L, rb, rc, ra, TM_MOD));

lvm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ LUAI_FUNC void luaV_execute (lua_State *L, CallInfo *ci);
116116
LUAI_FUNC void luaV_concat (lua_State *L, int total);
117117
LUAI_FUNC lua_Integer luaV_div (lua_State *L, lua_Integer x, lua_Integer y);
118118
LUAI_FUNC lua_Integer luaV_mod (lua_State *L, lua_Integer x, lua_Integer y);
119+
LUAI_FUNC lua_Number luaV_modf (lua_State *L, lua_Number x, lua_Number y);
119120
LUAI_FUNC lua_Integer luaV_shiftl (lua_Integer x, lua_Integer y);
120121
LUAI_FUNC void luaV_objlen (lua_State *L, StkId ra, const TValue *rb);
121122

testes/math.lua

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-- $Id: testes/math.lua $
1+
-- $Id: testes/math.lua 2018-07-25 15:31:04 -0300 $
22
-- See Copyright Notice in file all.lua
33

44
print("testing numbers and math lib")
@@ -541,9 +541,73 @@ assert(eqT(-4 % 3, 2))
541541
assert(eqT(4 % -3, -2))
542542
assert(eqT(-4.0 % 3, 2.0))
543543
assert(eqT(4 % -3.0, -2.0))
544+
assert(eqT(4 % -5, -1))
545+
assert(eqT(4 % -5.0, -1.0))
546+
assert(eqT(4 % 5, 4))
547+
assert(eqT(4 % 5.0, 4.0))
548+
assert(eqT(-4 % -5, -4))
549+
assert(eqT(-4 % -5.0, -4.0))
550+
assert(eqT(-4 % 5, 1))
551+
assert(eqT(-4 % 5.0, 1.0))
552+
assert(eqT(4.25 % 4, 0.25))
553+
assert(eqT(10.0 % 2, 0.0))
554+
assert(eqT(-10.0 % 2, 0.0))
555+
assert(eqT(-10.0 % -2, 0.0))
544556
assert(math.pi - math.pi % 1 == 3)
545557
assert(math.pi - math.pi % 0.001 == 3.141)
546558

559+
do -- very small numbers
560+
local i, j = 0, 20000
561+
while i < j do
562+
local m = (i + j) // 2
563+
if 10^-m > 0 then
564+
i = m + 1
565+
else
566+
j = m
567+
end
568+
end
569+
-- 'i' is the smallest possible ten-exponent
570+
local b = 10^-(i - (i // 10)) -- a very small number
571+
assert(b > 0 and b * b == 0)
572+
local delta = b / 1000
573+
assert(eq((2.1 * b) % (2 * b), (0.1 * b), delta))
574+
assert(eq((-2.1 * b) % (2 * b), (2 * b) - (0.1 * b), delta))
575+
assert(eq((2.1 * b) % (-2 * b), (0.1 * b) - (2 * b), delta))
576+
assert(eq((-2.1 * b) % (-2 * b), (-0.1 * b), delta))
577+
end
578+
579+
580+
-- basic consistency between integer modulo and float modulo
581+
for i = -10, 10 do
582+
for j = -10, 10 do
583+
if j ~= 0 then
584+
assert((i + 0.0) % j == i % j)
585+
end
586+
end
587+
end
588+
589+
for i = 0, 10 do
590+
for j = -10, 10 do
591+
if j ~= 0 then
592+
assert((2^i) % j == (1 << i) % j)
593+
end
594+
end
595+
end
596+
597+
do -- precision of module for large numbers
598+
local i = 10
599+
while (1 << i) > 0 do
600+
assert((1 << i) % 3 == i % 2 + 1)
601+
i = i + 1
602+
end
603+
604+
i = 10
605+
while 2^i < math.huge do
606+
assert(2^i % 3 == i % 2 + 1)
607+
i = i + 1
608+
end
609+
end
610+
547611
assert(eqT(minint % minint, 0))
548612
assert(eqT(maxint % maxint, 0))
549613
assert((minint + 1) % minint == minint + 1)

0 commit comments

Comments
 (0)