Numbers
Compared to other Lisps, GameLisp's numbers are relatively simple. A number may only be an int
(a 32-bit signed integer) or a flo
(a 32-bit IEEE float). Rational fractions, arbitrary-precision
integers and complex numbers are not supported.
The API for manipulating numbers is mostly unsurprising - the full list of functions is available here. Some minor points:
-
Bit manipulation is supported for integers.
-
We provide a simple random number generator.
-
There is a distinction between
sign
(which returns0
,1
or-1
for any number) andflo‑sign
(which returns the sign of a float as1.0
,-1.0
ornan.0
).
(prn (sign -0.0)) ; prints 0
(prn (flo-sign -0.0)) ; prints -1.0
Promotion to Float
Floats are "contagious". When a function such as +
receives both an integer and a
float among its arguments, the integer is promoted to a float before performing the operation, so
the return value is always a float.
(prn (+ 1 2 3 4)) ; prints 10
(prn (+ 1 2 3.0 4)) ; prints 10.0
(prn (/ 7 2)) ; prints 3
(prn (/ 7 2.0)) ; prints 3.5
(prn (% 5 1.5)) ; prints 0.5
Operations like min
and clamp
are the exception to the rule, because they return one of
their arguments unchanged.
(prn (max 1 2 3.0 4)) ; prints 4
(prn (clamp 1.0 3 5.0)) ; prints 3
NaN
According to the IEEE 754 specification, all comparisons involving NaN floats should return false. This means that NaN floats are unequal to themselves, and they're neither less than nor greater than any other number.
This rule is inconvenient when sorting numbers, and when working with hash tables, priority queues, and sorted arrays. GameLisp therefore imposes a total ordering on floats: NaN floats compare greater than all other floats (including positive infinity), and all NaN floats compare equal to all other NaN floats.
Wrapping Arithmetic
Integer arithmetic is always unchecked (wrapping), even when your crate is compiled in debug mode.
(prn (+ 1 2147483647)) ; prints -2147483648
This helps to keep the language simple. If GameLisp were to take Rust's approach to integer overflow, it would need to provide distinct APIs for normal, wrapping and checked arithmetic. In Rust, this adds a large complexity burden when working with integers.
GameLisp chose wrapping arithmetic over checked arithmetic in order to make fatal errors less likely. In game development, overflowing an entity's coordinates and getting a nonsensical value will usually be a minor bug, but overflowing an entity's coordinates and crashing the game's executable would be catastrophic.