Numbers

10th January 2026

This article describes the implementation of numbers in the 32-bit versions of uLisp.

Integers

Integers can be between 2147483647 and -2147483648.

Floating point

uLisp uses the standard 32-bit single-precision floating-point format which uses 1 bit for the sign, 8 bits for the exponent which can be from -126 to +127, and 23 bits for the mantissa which can be from 0 to 8388607. The largest number you can represent is about 2127 ≈ 1.7014e38. If you create a result greater than this uLisp will display Inf for infinity:

> 1e39
Inf

The smallest number you can represent is about 2-126 ≈ 1.1755e-38. Numbers smaller than this will be treated as zero.

Precision

The precision of a floating-point number is the difference between that number and the next number you can represent. Floating point format is peculiar in that the precision is very high near zero, and it decreases in steps as you move towards the most positive (or most negative) number it can represent, because there are exactly 8388608 possible floating-point values between each pair of powers of two.

For example, what is the precision of 7.5 in floating-point format? This lies between 4 and 8, and there are 8388608 possible floating-point values between 4 and 8, so the precision is:

8 – 4223
 = 
28388608
 ≈ 0.0000004768

Between 8388608 and 1677216 the precision is 1, so you can only represent integers exactly.

Precision pitfalls

Above 1677216 the precision is worse than 1, which gives rise to some strange results; for example:

> (+ 2147483640 10 -2147483640 10)
10.0

We would expect the answer 20 so what's going on here?

The explanation is that the sum is evaluated left to right; 2147483640+10 is larger than the largest integer, so it's converted to floating point:

> (+ 2147483640 10)
2.14748e9

The result lies between 2147483648 and 4294967296 where, using the above calculation, the precision is 256, so adding 10 to 2147483640 didn't change it, and adding -2147483640 gave 0.0. Finally, adding 10 gave 10.0.

Doing the sum in a different order can give the correct result:

> (+ 2147483640 -2147483640 10 10)
20

Take care when doing floating-point calculations with numbers of dramatically different magnitudes.

For an excellent explanation of floating point see Fabian Sanglard's Floating Point Visually Explained.

Conversion between integer and floating point

uLisp aims to keep calculations in integer format wherever possible. When a result can't be represented as a 32-bit integer it is automatically converted to floating point.

uLisp never converts a floating-point result back to integer format.

Some special cases

Minus and abs

Some calculations will give a floating-point result where you might not expect it. For example, applying '-' or abs to an integer can give a floating point result:

> (- -2147483648)
-2.14748e9

because 2147483648 cannot be represented as an integer.

Raise to power

If the arguments to the expt (raise to power) function are integers it gives an integer result where possible:

> (expt 3 19)
1162261467
> (expt -1 100)
1

but a floating-point result otherwise:

> (expt 2 31)
2.14748e9

Truncate, floor, and ceiling

The function truncate:

(truncate n m)

is equivalent to:

(truncate (/ n m))

However, giving the divisor as a second parameter allows uLisp to make truncate provide a more accurate answer. For example:

(/ 123456789 10)

when expressed as a 32-bit floating-point number doesn't have the 8 digits of precision to give the correct answer of 12345678:

> (truncate 12345678.9)
12345679 

However, the call:

(truncate 123456789 10)

where the parameters are 32-bit integers, returns a precise result [1]:

> (truncate 123456789 10)
12345678

The same is true of floor and ceiling.


  1. ^ As of ARM 4.8e and ESP 4.8f.

Previous: Built-in symbols

Next: Strings