forked from Shopify/go-lua
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmath.go
More file actions
332 lines (322 loc) · 8.43 KB
/
math.go
File metadata and controls
332 lines (322 loc) · 8.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
package lua
import (
"math"
"math/rand"
)
const radiansPerDegree = math.Pi / 180.0
func mathUnaryOp(f func(float64) float64) Function {
return func(l *State) int {
l.PushNumber(f(CheckNumber(l, 1)))
return 1
}
}
func mathBinaryOp(f func(float64, float64) float64) Function {
return func(l *State) int {
l.PushNumber(f(CheckNumber(l, 1), CheckNumber(l, 2)))
return 1
}
}
// reduce creates a min/max function that preserves integer type in Lua 5.3
func reduce(f func(float64, float64) float64, isMax bool) Function {
return func(l *State) int {
n := l.Top() // number of arguments
CheckAny(l, 1) // "value expected" error if no arguments
// Track if all arguments are integers and result should be integer
allInt := true
var intResult int64
var floatResult float64
for i := 1; i <= n; i++ {
if allInt && l.IsInteger(i) {
v, _ := l.ToInteger64(i)
if i == 1 {
intResult = v
} else {
if isMax {
if v > intResult {
intResult = v
}
} else {
if v < intResult {
intResult = v
}
}
}
} else {
// Switch to float mode
if allInt {
floatResult = float64(intResult)
allInt = false
}
v := CheckNumber(l, i)
if i == 1 || allInt {
floatResult = v
} else {
floatResult = f(floatResult, v)
}
}
}
if allInt {
l.PushInteger64(intResult)
} else {
l.PushNumber(floatResult)
}
return 1
}
}
var mathLibrary = []RegistryFunction{
{"abs", func(l *State) int {
// Lua 5.3: abs preserves integer type
if l.IsInteger(1) {
i, _ := l.ToInteger64(1)
if i < 0 {
i = -i // overflow wraps for minint
}
l.PushInteger64(i)
} else {
l.PushNumber(math.Abs(CheckNumber(l, 1)))
}
return 1
}},
{"acos", mathUnaryOp(math.Acos)},
{"asin", mathUnaryOp(math.Asin)},
{"atan2", mathBinaryOp(math.Atan2)},
{"atan", func(l *State) int {
// Lua 5.3: atan(y [, x]) - if x is given, returns atan2(y, x)
y := CheckNumber(l, 1)
if l.IsNoneOrNil(2) {
l.PushNumber(math.Atan(y))
} else {
x := CheckNumber(l, 2)
l.PushNumber(math.Atan2(y, x))
}
return 1
}},
{"ceil", func(l *State) int {
if l.IsInteger(1) {
l.SetTop(1) // integer is its own ceil
} else {
x := CheckNumber(l, 1)
c := math.Ceil(x)
if i := int64(c); float64(i) == c && c >= float64(math.MinInt64) && c <= float64(math.MaxInt64) {
l.PushInteger64(i)
} else {
l.PushNumber(c)
}
}
return 1
}},
{"cosh", mathUnaryOp(math.Cosh)},
{"cos", mathUnaryOp(math.Cos)},
{"deg", mathUnaryOp(func(x float64) float64 { return x / radiansPerDegree })},
{"exp", mathUnaryOp(math.Exp)},
{"floor", func(l *State) int {
if l.IsInteger(1) {
l.SetTop(1) // integer is its own floor
} else {
x := CheckNumber(l, 1)
f := math.Floor(x)
if i := int64(f); float64(i) == f && f >= float64(math.MinInt64) && f <= float64(math.MaxInt64) {
l.PushInteger64(i)
} else {
l.PushNumber(f)
}
}
return 1
}},
{"fmod", func(l *State) int {
// Lua 5.3: fmod preserves integer type when both args are integers
if l.IsInteger(1) && l.IsInteger(2) {
x, _ := l.ToInteger64(1)
y, _ := l.ToInteger64(2)
if y == 0 {
Errorf(l, "zero")
}
l.PushInteger64(x % y)
} else {
l.PushNumber(math.Mod(CheckNumber(l, 1), CheckNumber(l, 2)))
}
return 1
}},
{"frexp", func(l *State) int {
f, e := math.Frexp(CheckNumber(l, 1))
l.PushNumber(f)
l.PushInteger(e)
return 2
}},
{"ldexp", func(l *State) int {
x, e := CheckNumber(l, 1), CheckInteger(l, 2)
l.PushNumber(math.Ldexp(x, e))
return 1
}},
{"log", func(l *State) int {
x := CheckNumber(l, 1)
if l.IsNoneOrNil(2) {
l.PushNumber(math.Log(x))
} else if base := CheckNumber(l, 2); base == 10.0 {
l.PushNumber(math.Log10(x))
} else {
l.PushNumber(math.Log(x) / math.Log(base))
}
return 1
}},
{"max", reduce(math.Max, true)},
{"min", reduce(math.Min, false)},
{"modf", func(l *State) int {
// Lua 5.3: first return value is integer when it fits
n := CheckNumber(l, 1)
// Handle infinity: Lua returns (±inf, 0.0), Go returns (±inf, NaN)
if math.IsInf(n, 0) {
l.PushNumber(n)
l.PushNumber(0.0)
return 2
}
i, f := math.Modf(n)
if ii := int64(i); float64(ii) == i && i >= float64(math.MinInt64) && i <= float64(math.MaxInt64) {
l.PushInteger64(ii)
} else {
l.PushNumber(i)
}
l.PushNumber(f)
return 2
}},
{"pow", mathBinaryOp(math.Pow)},
{"rad", mathUnaryOp(func(x float64) float64 { return x * radiansPerDegree })},
{"random", func(l *State) int {
// Helper to get int64 argument
checkInt64 := func(index int) int64 {
i, ok := l.ToInteger64(index)
if !ok {
ArgumentError(l, index, "integer expected")
}
return i
}
// randRange returns a random int64 in [lo, u] inclusive
randRange := func(lo, u int64) int64 {
// Use uint64 arithmetic to avoid overflow
rangeLow := uint64(lo - math.MinInt64)
rangeHigh := uint64(u - math.MinInt64)
rangeSize := rangeHigh - rangeLow + 1
if rangeSize == 0 {
// Full 64-bit range (overflow to 0 means 2^64)
return int64(rand.Uint64())
}
// Unbiased: use rejection sampling for large ranges
r := rand.Uint64() % rangeSize
return int64(r+rangeLow) + math.MinInt64
}
switch l.Top() {
case 0: // no arguments - returns float in [0,1)
// Use exactly 53 bits of randomness, like C Lua 5.4
l.PushNumber(float64(rand.Int63()>>10) / float64(int64(1)<<53))
case 1: // upper limit only - returns integer in [1, u], or full-range for 0
u := checkInt64(1)
if u == 0 {
// Lua 5.4: random(0) returns a full-range random integer
l.PushInteger64(int64(rand.Uint64()))
} else {
ArgumentCheck(l, 1 <= u, 1, "interval is empty")
l.PushInteger64(randRange(1, u))
}
case 2: // lower and upper limits - returns integer in [lo, u]
lo := checkInt64(1)
u := checkInt64(2)
ArgumentCheck(l, lo <= u, 2, "interval is empty")
l.PushInteger64(randRange(lo, u))
default:
Errorf(l, "wrong number of arguments")
}
return 1
}},
{"randomseed", func(l *State) int {
rand.Seed(int64(CheckUnsigned(l, 1)))
rand.Float64() // discard first value to avoid undesirable correlations
return 0
}},
{"sinh", mathUnaryOp(math.Sinh)},
{"sin", mathUnaryOp(math.Sin)},
{"sqrt", mathUnaryOp(math.Sqrt)},
{"tanh", mathUnaryOp(math.Tanh)},
{"tan", mathUnaryOp(math.Tan)},
// Lua 5.3: integer functions
{"tointeger", func(l *State) int {
switch v := l.ToValue(1).(type) {
case int64:
l.PushInteger64(v)
case float64:
// Check range before conversion to avoid overflow
// float64 can represent values outside int64 range
const maxInt64Float = float64(1 << 63) // 2^63
if v >= maxInt64Float || v < -maxInt64Float {
l.PushNil()
} else if i := int64(v); float64(i) == v {
l.PushInteger64(i)
} else {
l.PushNil()
}
default:
// Try string conversion - use parseNumberEx to preserve integer precision
if s, ok := l.ToValue(1).(string); ok {
if intVal, floatVal, isInt, ok := l.parseNumberEx(s); ok {
if isInt {
l.PushInteger64(intVal)
} else {
// Float value - apply same range check
const maxInt64Float = float64(1 << 63)
if floatVal >= maxInt64Float || floatVal < -maxInt64Float {
l.PushNil()
} else if i := int64(floatVal); float64(i) == floatVal {
l.PushInteger64(i)
} else {
l.PushNil()
}
}
} else {
l.PushNil()
}
} else {
l.PushNil()
}
}
return 1
}},
{"type", func(l *State) int {
CheckAny(l, 1)
// Check actual type, not convertible type (strings should return nil)
v := l.ToValue(1)
switch v.(type) {
case int64:
l.PushString("integer")
case float64:
l.PushString("float")
default:
l.PushNil()
}
return 1
}},
{"ult", func(l *State) int {
a, ok1 := l.ToInteger64(1)
b, ok2 := l.ToInteger64(2)
if !ok1 {
ArgumentError(l, 1, "number has no integer representation")
}
if !ok2 {
ArgumentError(l, 2, "number has no integer representation")
}
l.PushBoolean(uint64(a) < uint64(b))
return 1
}},
}
// MathOpen opens the math library. Usually passed to Require.
func MathOpen(l *State) int {
NewLibrary(l, mathLibrary)
l.PushNumber(3.1415926535897932384626433832795) // TODO use math.Pi instead? Values differ.
l.SetField(-2, "pi")
l.PushNumber(math.Inf(1)) // Lua defines math.huge as infinity
l.SetField(-2, "huge")
// Lua 5.3: integer limits
l.PushInteger(math.MaxInt64)
l.SetField(-2, "maxinteger")
l.PushInteger(math.MinInt64)
l.SetField(-2, "mininteger")
return 1
}