Skip to content

Commit e4c0a67

Browse files
committed
Add replacement frexp() for 5.3 without math.frexp() available
1 parent 04aa2db commit e4c0a67

5 files changed

Lines changed: 105 additions & 29 deletions

File tree

frexp.lua

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- math.frexp() replacement for Lua 5.3 when compiled without LUA_COMPAT_MATHLIB.
2+
-- The C approach is just to type-pun the float, but we can't do that here short
3+
-- of stupid loadstring() tricks, which would be both architecture and version
4+
-- dependent and a maintenance headache at best. So instead we use math.
5+
6+
local abs,floor,log = math.abs,math.floor,math.log
7+
local log2 = log(2)
8+
9+
return function(x)
10+
if x == 0 then return 0.0,0.0 end
11+
local e = floor(log(abs(x)) / log2)
12+
if e > 0 then
13+
-- Why not x / 2^e? Because for large-but-still-legal values of e this
14+
-- ends up rounding to inf and the wheels come off.
15+
x = x * 2^-e
16+
else
17+
x = x / 2^e
18+
end
19+
-- Normalize to the range [0.5,1)
20+
if abs(x) >= 1.0 then
21+
x,e = x/2,e+1
22+
end
23+
return x,e
24+
end

io/f.lua

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
local struct = require "vstruct"
44
local io = require "vstruct.io"
55
local unpack = table.unpack or unpack
6-
local frexp = assert(math.frexp,
7-
"math.frexp missing -- please recompile lua with LUA_COMPAT_MATHLIB enabled")
6+
local frexp = math.frexp or require "vstruct.frexp"
87

98
local sizes = {
109
[4] = {1, 8, 23};

test/frexp.lua

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
-- regression tests for vstruct
2+
-- tests for fixed bugs go here to make sure they don't recur
3+
-- tests are named after the bug they trigger, not the intended behaviour
4+
5+
local test = require "vstruct.test.common"
6+
local frexp = require "vstruct.frexp"
7+
8+
-- No point in running these tests if there isn't a builtin frexp to compare to.
9+
if not math.frexp then return end
10+
11+
test.group "frexp()"
12+
13+
local function test_frexp(x)
14+
local m,e = math.frexp(x)
15+
if m >= 1 or m <= -1 then
16+
-- At least on my machine, frexp() sometimes returns 1 as the mantissa, in
17+
-- violation as the spec. Correct this so that we can more properly compare
18+
-- things.
19+
m,e = m/2, e+1
20+
end
21+
local m2,e2 = frexp(x)
22+
local ok = m == m2 and e == e2
23+
test.record("frexp equivalence", ok, x,
24+
"(builtin) %f,%f != %f,%f (lua)", m, e, m2, e2)
25+
end
26+
27+
local inf = math.huge
28+
local z = 0.0
29+
local nz = -z
30+
local doubles = {
31+
0.0, 1.0, 2.0,
32+
1/2, 1/4, 1/8,
33+
4.9406564584124654418e-324,
34+
7.4169128616906696301e-309,
35+
1.483382572338133926e-308,
36+
2.225073858507200889e-308,
37+
2.2250738585072013831e-308,
38+
2.2250738585072018772e-308,
39+
2.9667651446762683461e-308,
40+
3.7084564308453353091e-308,
41+
4.4501477170144022721e-308,
42+
8.9884656743115795386e+307,
43+
8.9884656743115815345e+307,
44+
1.1984620899082105386e+308,
45+
1.4980776123852631234e+308,
46+
1.7976931348623157081e+308,
47+
4.9406564584124654418e-324,
48+
2.225073858507200889e-308,
49+
2.2250738585072013831e-308,
50+
1.7976931348623157081e+308,
51+
0.0,
52+
nz,
53+
-4.9406564584124654418e-324,
54+
-7.4169128616906696301e-309,
55+
-1.483382572338133926e-308,
56+
-2.225073858507200889e-308,
57+
-2.2250738585072013831e-308,
58+
-2.2250738585072018772e-308,
59+
-2.9667651446762683461e-308,
60+
-3.7084564308453353091e-308,
61+
-4.4501477170144022721e-308,
62+
-8.9884656743115795386e+307,
63+
-8.9884656743115815345e+307,
64+
-1.1984620899082105386e+308,
65+
-1.4980776123852631234e+308,
66+
-1.7976931348623157081e+308,
67+
-4.9406564584124654418e-324,
68+
-2.225073858507200889e-308,
69+
-2.2250738585072013831e-308,
70+
-1.7976931348623157081e+308,
71+
nz,
72+
}
73+
74+
for _,double in ipairs(doubles) do
75+
test_frexp(double)
76+
end

test/regression.lua

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,3 @@ T("p passes invalid value to string.char and crashes",
2525
"> p2,2",
2626
x"FD 00", -192.098910,
2727
x"FD 00", -192.00)
28-
29-
-- math.frexp() replacement for Lua 5.3 when compiled without LUA_COMPAT_MATHLIB.
30-
-- Work in progress; fails on very large/small numbers because 2^e ends up being
31-
-- inf/0 in the final calculation of (x/2^e).
32-
local abs,floor,log = math.abs,math.floor,math.log
33-
local log2 = log(2)
34-
local function frexp(x)
35-
if x == 0 then return 0, 0 end
36-
local e = floor(log(abs(x)) / log2 + 1)
37-
return x / 2 ^ e, e
38-
end
39-
40-
local function test_frexp(x)
41-
local m,e = math.frexp(x)
42-
local m2,e2 = frexp(x)
43-
test.record("frexp equivalence", m == m2 and e == e2, x,
44-
"(builtin) %f,%f != %f,%f (lua)", m, e, m2, e2)
45-
end
46-
47-
-- test_frexp(1.7976931348623157081e+308)
48-
-- test_frexp(-1.7976931348623157081e+308)
49-
-- test_frexp(1.7976931348623157081e-308)
50-
-- test_frexp(-1.7976931348623157081e-308)

test/struct-test-gen.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ local CONF = {
88
MAX_DEPTH = 5, -- Limit on nesting depth of test formats.
99
COMPOUND_LENGTH = 2, -- # formats in a sequence.
1010
SEED = false, -- Seed for generating tests.
11-
11+
1212
NROF_TESTS = 2^10, -- number of random test cases to run
1313
}
1414

@@ -77,8 +77,8 @@ local test = require "vstruct.test.common"
7777
local record = test.record
7878
local __EOG__ = __EOG__
7979

80-
local abs,ceil,floor,fmod,frexp,log = math.abs, math.ceil, math.floor, math.fmod, math.frexp, math.log
81-
80+
local abs,ceil,floor,fmod = math.abs, math.ceil, math.floor, math.fmod
81+
local frexp = math.frexp or require "vstruct.frexp"
8282
local char_ = string.char
8383

8484
local function char(x) return char_(x % 256) end
@@ -136,7 +136,7 @@ function dump.number(x)
136136
if e == 0 then return ("%.0f"):format(m) end
137137

138138
local op = e < 0 and "/" or "*"
139-
e = math.abs(e)
139+
e = abs(e)
140140
local exp = e > 1 and "2^"..e or "2"
141141
return ("%.0f%s%s"):format(m, op, exp)
142142
end

0 commit comments

Comments
 (0)