Skip to main content

Numeric Tower

Intermediate 3 min read Last updated
On this page

After this page you can pick the right number type for exact money math, large integers, and fractions, and predict what arithmetic returns when types mix.

Phel numbers come in five shapes. Arithmetic, comparisons, and predicates work across all of them. For literal syntax and the everyday operators, see Basic Types.

The five shapes

TypeWhere it comes fromNotes
intnative PHP int64-bit signed on common platforms
Phel\Lang\BigIntbigint, +', *', ...arbitrary-precision signed integer
Phel\Lang\Ratio1/2 literals, /, rationalizealways reduced; becomes an int or BigInt when whole
Phel\Lang\BigDecimal1.5M literal, bigdecarbitrary-precision exact decimal
floatnative PHP float (IEEE-754 double)inexact

+, -, *, /, the comparison operators, and the numeric predicates handle all five through Phel\Lang\NumericOperations, because PHP's native operators do not work on these objects.

Exact decimal literals: 1.5M

A number with an M suffix reads as a BigDecimal and prints with the M suffix. Use it for money and any calculation where float rounding errors are not acceptable.

(println 1.5M)               ; 1.5M (a BigDecimal)
(println (+ 0.1M 0.2M))      ; 0.3M (no float drift)
(println (bigdec "3.14159")) ; 3.14159M
(println (bigdec? 1.5M))     ; true

Contrast with float. The sum prints as 0.3, but it is not 0.3:

(println (= (+ 0.1 0.2) 0.3))       ; false (float drift)
(println (= (+ 0.1M 0.2M) 0.3M))    ; true

Promoting integers: bigint and +'

Promote explicitly with the constructors. A BigInt prints as a plain integer (no suffix):

(println (bigint 42))                            ; 42 (a BigInt)
(println (bigint "1000000000000000000000"))
(println (bigint? (bigint 42)))                  ; true

The promoting operators +', -', *', inc', and dec' switch to BigInt on overflow instead of wrapping. Use them whenever overflow is possible:

(println (*' 1000000000 1000000000 1000000000)) ; 1000000000000000000000000000
(println (inc' 9223372036854775807))            ; 9223372036854775808

The plain ops (+, *, inc, ...) stay in native int and wrap on overflow, matching PHP.

Large integer literals lex as float

PHP's lexer parses an oversize integer literal as a float, and so does Phel's:

(println (float? 12345678901234567890)) ; true (PHP int range overflow)

Wrap the value in a string and pass it to bigint to keep full precision:

(println (bigint "12345678901234567890"))

Floats to exact: rationalize

rationalize converts a float to an exact Ratio. It uses the shortest decimal that converts back to the same float, so binary noise digits do not leak in:

(println (rationalize 0.1)) ; 1/10

It returns 1/10, not 10000000000000001/100000000000000000. Floats with no short decimal representation (such as (/ 1.0 3.0)) keep their round-trip representation as a Ratio. Passing ##Inf, ##-Inf, or ##NaN throws an InvalidArgumentException.

A Ratio is always reduced, and it becomes an integer when the result is whole:

(println (numerator 1/2))   ; 1
(println (denominator 1/2)) ; 2
(println (/ 4 2))           ; 2 (collapses to int)

Gotchas

bit-shift-right is arithmetic

bit-shift-right performs an arithmetic (sign-preserving) shift, matching PHP's >>. There is no unsigned-bit-shift-right; for a logical shift use a mask:

(println (bit-and (bit-shift-right -8 1) 0x7FFFFFFFFFFFFFFF))

== arity-1 asserts numeric

In Clojure, (== x) returns true for any single argument. Phel's == requires a number and throws otherwise. Single-argument == is a numeric assertion, not an identity check:

(println (== 5)) ; true
(println (try (== :a)
              (catch \Exception e (.getMessage e))))
; Argument must be a number, got: :a

Quick reference

NeedUse
Exact integer beyond PHP int(bigint "...") or promoting ops +', *'
Exact non-integer ratioRatio literal 1/2, (/ a b), (rationalize x)
Exact decimal (money, etc.)BigDecimal literal 1.5M, (bigdec "...")
Inexact decimalnative float
Predicateint?, bigint?, ratio?, bigdec?, decimal?, float?, number?
Numerator / denominatornumerator, denominator
Float to exactrationalize

The full list of numeric functions and predicates is in the core API.