Skip to content

Commit ba99ea2

Browse files
authored
Merge pull request #11 from carpentry-org/claude/lua-double-numbers
carry Lua numbers as doubles
2 parents 6c300f6 + 18dc3b1 commit ba99ea2

4 files changed

Lines changed: 181 additions & 14 deletions

File tree

‎README.md‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,22 @@ state and closes it when the block exits:
3636
```
3737

3838
The `Lua` module wraps the Lua C API directly: stack operations
39-
(`push-int`, `push-float`, `push-bool`, `push-string`, `get-int`, etc.),
39+
(`push-int`, `push-double`, `push-bool`, `push-string`, `get-int`, etc.),
4040
globals (`get-global`, `set-global`), tables (`create-table`, `set-field`,
4141
`get-field`, `next`), code execution (`do-string`, `do-file`, `call`), and
4242
type checking (`type-of`, `TYPE_NIL`, `TYPE_NUMBER`, etc.). It also provides a
4343
few conveniences: `Lua.fun` for defining Lua functions inline, `Lua.val` for
4444
evaluating Lua expressions into globals, and `Lua.eval-file` for loading Lua
4545
files with error handling.
4646

47+
A Lua number is a C double, so `push-double` and `get-double` (and the matching
48+
`Luax.set-double-global`, `Luax.get-double-field`, …) carry Lua floats at full
49+
precision. The `float` versions still work — a float widens into a double
50+
exactly — but `get-float` narrows on the way back, so reading through it drops
51+
everything past single precision. Lua integers are a separate 64-bit subtype;
52+
`get-double` converts them to doubles, so integers above 2^53 are no longer all
53+
exactly representable and may come back rounded.
54+
4755
The `Luax` module provides safe wrappers that return `Maybe` and `Result`
4856
types instead of requiring manual type checks:
4957

@@ -58,8 +66,8 @@ types instead of requiring manual type checks:
5866
(Luax.make-table lua player
5967
(name (Lua.push-carp-str "Ada"))
6068
(hp (Lua.push-int 100))
61-
(x (Lua.push-float 3.5f))
62-
(y (Lua.push-float 7.0f)))
69+
(x (Lua.push-double 3.5))
70+
(y (Lua.push-double 7.0)))
6371

6472
; read table fields — returns Maybe, keeps the stack clean
6573
(Lua.get-global lua (cstr "player"))

‎lua.carp‎

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,10 @@ automatically.")
163163
(register push-light-user-data (Fn [&Lua (Ptr ())] ()) "lua_pushlightuserdata")
164164
(doc push-nil "Push nil onto the stack.")
165165
(register push-nil (Fn [&Lua] ()) "lua_pushnil")
166-
(doc push-float "Push a float (Lua number) onto the stack.")
167-
(register push-float (Fn [&Lua Float] ()) "lua_pushnumber")
166+
(doc push-double "Push a double (Lua number) onto the stack.")
167+
(register push-double (Fn [&Lua Double] ()) "lua_pushnumber")
168+
(doc push-float "Push a float onto the stack as a Lua number.")
169+
(defn push-float [lua f] (push-double lua (Double.from-float f)))
168170
(doc push-string "Push a C string onto the stack. For Carp strings, use
169171
[`push-carp-str`](#push-carp-str) instead.")
170172
(deftemplate push-string
@@ -189,9 +191,13 @@ elements on the stack.")
189191
(doc get-int "Read the value at `index` as an integer. Does not check the
190192
type—use [`Luax.maybe-get-int`](#maybe-get-int) for a safe version.")
191193
(register get-int (Fn [&Lua Int] Int) "lua_tointeger")
192-
(doc get-float "Read the value at `index` as a float. Does not check the
194+
(doc get-double "Read the value at `index` as a double. Does not check the
195+
type—use [`Luax.maybe-get-double`](#maybe-get-double) for a safe version.")
196+
(register get-double (Fn [&Lua Int] Double) "lua_tonumber")
197+
(doc get-float "Read the value at `index` as a float. Lua numbers are doubles,
198+
so this narrows; use [`get-double`](#get-double) instead. Does not check the
193199
type—use [`Luax.maybe-get-float`](#maybe-get-float) for a safe version.")
194-
(register get-float (Fn [&Lua Int] Float) "lua_tonumber")
200+
(defn get-float [lua index] (Double.to-float (get-double lua index)))
195201
(doc get-string "Read the value at `index` as a C string pointer. Returns a
196202
raw pointer; prefer [`Luax.get-carp-str`](#get-carp-str) or
197203
[`Luax.maybe-get-string`](#maybe-get-string) for safe access.")
@@ -595,30 +601,35 @@ the Lua standard library available.
595601
```
596602

597603
Values are passed between Carp and Lua through the stack. Push values with
598-
[`push-int`](#push-int), [`push-float`](#push-float),
604+
[`push-int`](#push-int), [`push-double`](#push-double),
599605
[`push-bool`](#push-bool), [`push-carp-str`](#push-carp-str), etc. Read them
600-
back with [`get-int`](#get-int), [`get-float`](#get-float), and so on, using
606+
back with [`get-int`](#get-int), [`get-double`](#get-double), and so on, using
601607
negative indices to address from the top of the stack (-1 is the top element).
602608

603609
```
604610
; push two values, read the top one
605611
(Lua.push-int lua 42)
606-
(Lua.push-float lua 3.14f)
607-
(let [f (Lua.get-float lua -1) ; 3.14
608-
i (Lua.get-int lua -2)] ; 42
612+
(Lua.push-double lua 3.14)
613+
(let [d (Lua.get-double lua -1) ; 3.14
614+
i (Lua.get-int lua -2)] ; 42
609615
(Lua.pop lua 2))
610616
```
611617

618+
A Lua number is a C double. [`push-float`](#push-float) widens exactly, but
619+
[`get-float`](#get-float) narrows on the way back, so a number read through it
620+
loses everything past single precision and anything above the float range comes
621+
back as an infinity.
622+
612623
To call a Lua function at the low level, push the function, then its arguments,
613624
then use [`call`](#call) with the argument and result counts. The result
614625
replaces the function and arguments on the stack.
615626

616627
```
617628
(Lua.get-global lua (cstr \"math\"))
618629
(ignore (Lua.get-field lua -1 (cstr \"sqrt\")))
619-
(Lua.push-float lua 9.0f)
630+
(Lua.push-double lua 9.0)
620631
(ignore (Lua.call lua 1 1 0))
621-
(IO.println &(str (Lua.get-float lua -1))) ; 3.0
632+
(IO.println &(str (Lua.get-double lua -1))) ; 3.0
622633
(Lua.pop lua 2) ; pop result and math table
623634
```
624635

@@ -782,6 +793,11 @@ and assigns it to a global in one expression:
782793
(String.from-cstr-or (Lua.to-string lua index) @""))
783794

784795
(luax--def-maybe-get int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number")
796+
(luax--def-maybe-get double
797+
Lua.TYPE_NUMBER
798+
Lua.get-double
799+
"a double"
800+
"a number")
785801
(luax--def-maybe-get float Lua.TYPE_NUMBER Lua.get-float "a float" "a number")
786802
(luax--def-maybe-get bool
787803
Lua.TYPE_BOOLEAN
@@ -804,6 +820,7 @@ unchanged.")
804820
(Maybe.Nothing))))
805821

806822
(luax--def-set-global int Lua.push-int)
823+
(luax--def-set-global double Lua.push-double)
807824
(luax--def-set-global float Lua.push-float)
808825
(luax--def-set-global bool Lua.push-bool)
809826
; `string` is defined explicitly rather than via luax--def-set-global: the
@@ -814,6 +831,11 @@ unchanged.")
814831
(do (Lua.push-carp-str lua value) (Lua.set-global lua (cstr name))))
815832

816833
(luax--def-get-global int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number")
834+
(luax--def-get-global double
835+
Lua.TYPE_NUMBER
836+
Lua.get-double
837+
"a double"
838+
"a number")
817839
(luax--def-get-global float Lua.TYPE_NUMBER Lua.get-float "a float" "a number")
818840
(luax--def-get-global bool
819841
Lua.TYPE_BOOLEAN
@@ -833,11 +855,17 @@ unchanged.")
833855
result)))
834856

835857
(luax--def-set-field int Lua.push-int)
858+
(luax--def-set-field double Lua.push-double)
836859
(luax--def-set-field float Lua.push-float)
837860
(luax--def-set-field bool Lua.push-bool)
838861
(luax--def-set-field string Lua.push-carp-str)
839862

840863
(luax--def-get-field int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number")
864+
(luax--def-get-field double
865+
Lua.TYPE_NUMBER
866+
Lua.get-double
867+
"a double"
868+
"a number")
841869
(luax--def-get-field float Lua.TYPE_NUMBER Lua.get-float "a float" "a number")
842870
(luax--def-get-field bool
843871
Lua.TYPE_BOOLEAN

‎test/lua.carp‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,59 @@
4848
2.5f
4949
(Lua.with-lua-do (Lua.push-float lua 2.5f) (Lua.get-float lua -1))
5050
"push-float / get-float round-trip")
51+
(assert-equal test
52+
3.1415926535897931
53+
(Lua.with-lua-do (Lua.push-double lua 3.1415926535897931)
54+
(Lua.get-double lua -1))
55+
"push-double / get-double round-trip keeps all 17 significant digits")
56+
(assert-equal test
57+
16777217.0
58+
(Lua.with-lua-do (Lua.libs lua)
59+
(ignore (Luax.do-in lua "n = 16777217"))
60+
(Lua.get-global lua (cstr "n"))
61+
(Lua.get-double lua -1))
62+
"get-double reads a Lua integer above 2^24 intact")
63+
(assert-true test
64+
(let [n (Lua.with-lua-do (Lua.libs lua)
65+
(ignore (Luax.do-in lua "n = 1e300"))
66+
(Lua.get-global lua (cstr "n"))
67+
(Lua.get-double lua -1))]
68+
(and (Double.> n 0.0) (Double.< n Double.MAX)))
69+
"get-double reads 1e300 as a finite value")
70+
(assert-true test
71+
(let [n (Lua.with-lua-do (Lua.libs lua)
72+
(ignore (Luax.do-in lua "n = -1e300"))
73+
(Lua.get-global lua (cstr "n"))
74+
(Lua.get-double lua -1))]
75+
(and (Double.< n 0.0) (Double.> n (Double.neg Double.MAX))))
76+
"get-double reads -1e300 as a finite value")
77+
(assert-equal test
78+
3.1415926535897931
79+
(Lua.with-lua-do (Lua.libs lua)
80+
(Lua.push-double lua 3.1415926535897931)
81+
(Lua.set-global lua (cstr "n"))
82+
(ignore (Luax.do-in lua "n = n * 2 / 2"))
83+
(Lua.get-global lua (cstr "n"))
84+
(Lua.get-double lua -1))
85+
"push-double hands Lua the full value")
86+
87+
(assert-equal test
88+
(Double.to-float 3.1415926535897931)
89+
(Lua.with-lua-do (Lua.push-double lua 3.1415926535897931)
90+
(Lua.get-float lua -1))
91+
"get-float still narrows to single precision")
92+
(assert-true test
93+
(Float.>
94+
(Lua.with-lua-do (Lua.libs lua)
95+
(ignore (Luax.do-in lua "n = 1e300"))
96+
(Lua.get-global lua (cstr "n"))
97+
(Lua.get-float lua -1))
98+
Float.MAX)
99+
"get-float still overflows past the float range")
100+
(assert-equal test
101+
(Double.from-float 2.5f)
102+
(Lua.with-lua-do (Lua.push-float lua 2.5f) (Lua.get-double lua -1))
103+
"push-float still accepts a Float")
51104
(assert-true test
52105
(Lua.with-lua-do (Lua.push-bool lua true) (Lua.get-bool lua -1))
53106
"push-bool true round-trip")

‎test/midlevel.carp‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@
3131
&(Lua.with-lua-do (Lua.push-bool lua true) (Luax.maybe-get-float lua -1)))
3232
"maybe-get-float returns Nothing for boolean value")
3333

34+
(assert-true test
35+
(= &(Maybe.Just 3.1415926535897931)
36+
&(Lua.with-lua-do (Lua.push-double lua 3.1415926535897931)
37+
(Luax.maybe-get-double lua -1)))
38+
"maybe-get-double returns Just for number value")
39+
(assert-true test
40+
(Maybe.nothing?
41+
&(Lua.with-lua-do (Lua.push-bool lua true) (Luax.maybe-get-double lua -1)))
42+
"maybe-get-double returns Nothing for boolean value")
43+
(assert-true test
44+
(Maybe.nothing?
45+
&(Lua.with-lua-do
46+
(ignore (Lua.push-string lua (cstr "hello")))
47+
(Luax.maybe-get-double lua -1)))
48+
"maybe-get-double returns Nothing for string value")
49+
3450
(assert-true test
3551
(= &(Maybe.Just true)
3652
&(Lua.with-lua-do (Lua.push-bool lua true) (Luax.maybe-get-bool lua -1)))
@@ -191,6 +207,22 @@
191207
(Lua.get-float lua -1))
192208
"set-float-global sets a float global")
193209

210+
(assert-equal test
211+
3.1415926535897931
212+
(Lua.with-lua-do (Lua.libs lua)
213+
(Luax.set-double-global lua "d" 3.1415926535897931)
214+
(Lua.get-global lua (cstr "d"))
215+
(Lua.get-double lua -1))
216+
"set-double-global sets a double global without losing precision")
217+
(assert-true test
218+
(let [n (Lua.with-lua-do (Lua.libs lua)
219+
(ignore (Luax.do-in lua "d = 1e300"))
220+
(ignore (Luax.do-in lua "d = d + 0"))
221+
(Lua.get-global lua (cstr "d"))
222+
(Lua.get-double lua -1))]
223+
(and (Double.> n 0.0) (Double.< n Double.MAX)))
224+
"double globals survive a round trip through Lua arithmetic")
225+
194226
(assert-true test
195227
(Lua.with-lua-do (Lua.libs lua)
196228
(Luax.set-bool-global lua "flag" true)
@@ -238,6 +270,23 @@
238270
(Luax.get-float-global lua "f")))
239271
"get-float-global returns Just for existing float")
240272

273+
(assert-true test
274+
(= &(Maybe.Just 3.1415926535897931)
275+
&(Lua.with-lua-do (Lua.libs lua)
276+
(Luax.set-double-global lua "d" 3.1415926535897931)
277+
(Luax.get-double-global lua "d")))
278+
"get-double-global returns Just for existing number")
279+
(assert-true test
280+
(Maybe.nothing?
281+
&(Lua.with-lua-do (Lua.libs lua) (Luax.get-double-global lua "nope")))
282+
"get-double-global returns Nothing for missing global")
283+
(assert-true test
284+
(Maybe.nothing?
285+
&(Lua.with-lua-do (Lua.libs lua)
286+
(Luax.set-string-global lua "s" "hello")
287+
(Luax.get-double-global lua "s")))
288+
"get-double-global returns Nothing for wrong type")
289+
241290
(assert-true test
242291
(= &(Maybe.Just true)
243292
&(Lua.with-lua-do (Lua.libs lua)
@@ -302,6 +351,24 @@
302351
(Luax.get-float-field lua -1 "val")))
303352
"get-float-field returns Just for existing float field")
304353

354+
(assert-true test
355+
(= &(Maybe.Just 3.1415926535897931)
356+
&(Lua.with-lua-do (Lua.libs lua)
357+
(ignore
358+
(Lua.do-string lua
359+
(cstr "t = {val = 3.141592653589793}")))
360+
(Lua.get-global lua (cstr "t"))
361+
(Luax.get-double-field lua -1 "val")))
362+
"get-double-field returns Just for existing number field")
363+
(assert-true test
364+
(Maybe.nothing?
365+
&(Lua.with-lua-do (Lua.libs lua)
366+
(ignore
367+
(Lua.do-string lua (cstr "t = {name = 'Alice'}")))
368+
(Lua.get-global lua (cstr "t"))
369+
(Luax.get-double-field lua -1 "name")))
370+
"get-double-field returns Nothing on type mismatch")
371+
305372
(assert-true test
306373
(= &(Maybe.Just true)
307374
&(Lua.with-lua-do (Lua.libs lua)
@@ -344,6 +411,17 @@
344411
(Lua.get-float lua -1))
345412
"set-float-field sets a float field on a table")
346413

414+
(assert-equal test
415+
3.1415926535897931
416+
(Lua.with-lua-do (Lua.libs lua)
417+
(Lua.create-table lua 0 1)
418+
(Luax.set-double-field lua -1 "val" 3.1415926535897931)
419+
(Lua.set-global lua (cstr "t"))
420+
(Lua.get-global lua (cstr "t"))
421+
(ignore (Lua.get-field lua -1 (cstr "val")))
422+
(Lua.get-double lua -1))
423+
"set-double-field sets a double field without losing precision")
424+
347425
(assert-true test
348426
(Lua.with-lua-do (Lua.libs lua)
349427
(Lua.create-table lua 0 1)

0 commit comments

Comments
 (0)