Learn Clojure's numeric literals, ratios, BigInt and BigDecimal values, overflow behavior, equality rules, and JVM interop concerns without importing Java's primitive-first habits.
Clojure numbers run on the JVM, but they do not feel exactly like Java primitives. Clojure makes exact arithmetic convenient, gives ratios first-class syntax, and lets you choose when to care about primitive performance.
The practical rule is simple: write clear numeric code first, then make type and performance choices where correctness or profiling requires them.
| Literal | Common runtime type | Meaning | Java mental model |
|---|---|---|---|
42 |
java.lang.Long |
Default integer literal | More like long than Java’s default int |
42N |
clojure.lang.BigInt |
Arbitrary-precision integer | Similar role to BigInteger |
42M |
java.math.BigDecimal |
Decimal literal | Similar role to BigDecimal |
3.14 |
java.lang.Double |
Floating-point literal | Java double |
22/7 |
clojure.lang.Ratio |
Exact fraction | No direct Java literal equivalent |
Check values at the REPL:
1(map class [42 42N 42M 3.14 22/7])
2;; => (java.lang.Long
3;; clojure.lang.BigInt
4;; java.math.BigDecimal
5;; java.lang.Double
6;; clojure.lang.Ratio)
For Java developers, the surprise is usually 22/7. It is not truncated integer division and it is not a double. It is an exact ratio.
Java integer division truncates:
15 / 2; // => 2
Clojure integer division returns an exact ratio when the result is not integral:
1(/ 5 2)
2;; => 5/2
3
4(double (/ 5 2))
5;; => 2.5
That behavior is useful for exact calculations, but you should convert deliberately when a Java API expects double, float, BigDecimal, or a primitive numeric type.
Do not assume Clojure silently widens all arithmetic. Normal arithmetic operators are checked for primitive overflow:
1(+ Long/MAX_VALUE 1)
2;; ArithmeticException: integer overflow
Use explicit arbitrary-precision values or auto-promoting operators when that is what the domain requires:
1(+ 1N Long/MAX_VALUE)
2;; => 9223372036854775808N
3
4(+' Long/MAX_VALUE 1)
5;; => 9223372036854775808N
Use unchecked math only after measurement and only when overflow behavior is acceptable:
1(unchecked-add Long/MAX_VALUE 1)
2;; => -9223372036854775808
That is a performance tool, not a default application style.
M creates a BigDecimal literal:
1(+ 10.25M 0.75M)
2;; => 11.00M
Use BigDecimal-style values when decimal precision matters, such as money or externally specified decimal quantities. Be careful mixing unsuffixed decimal literals and M literals:
1(+ 10.25M (bigdec "0.75"))
2;; => 11.00M
If your domain is money, prefer integer minor units or consistent BigDecimal handling rather than ad hoc mixing with floating-point values.
Clojure has several equality predicates:
| Predicate | Use for | Example |
|---|---|---|
= |
Value equality | (= 1 1N) is true |
== |
Numeric equality | (== 1 1.0) is true |
identical? |
Same object or primitive identity | Rare in application code |
Examples:
1(= 1 1N)
2;; => true
3
4(= 1 1.0)
5;; => false
6
7(== 1 1.0)
8;; => true
Use = for most Clojure data comparisons. Use == when you specifically mean numeric equality across numeric representations.
Java methods may require exact primitive or boxed numeric types. Clojure will often coerce at invocation time, but explicit conversion makes intent clearer when ambiguity matters:
1(int 42)
2(long 42)
3(double 22/7)
4(bigdec "10.25")
When performance-sensitive Java interop is involved, type hints can remove reflection:
1(defn elapsed-millis [^java.time.Duration d]
2 (.toMillis d))
Do not scatter hints everywhere. Add them when reflection warnings or profiling show a concrete need.
| Need | Prefer | Why |
|---|---|---|
| Normal counts and sizes | Long literals |
Simple and efficient enough for most code |
| Exact fractions | Ratios | Preserve exactness through division |
| Very large integers | N literals, bigint, +' |
Avoid overflow intentionally |
| Decimal business values | BigDecimal or integer minor units |
Avoid binary floating-point surprises |
| Scientific or approximate math | double |
Matches JVM numeric libraries |
| Hot primitive loops | Type hints, primitive arrays, unchecked math after profiling | Performance work should be measured |
flowchart TD
A["Numeric value"] --> B{"Exact integer?"}
B -->|Normal range| C["Long"]
B -->|May exceed range| D["BigInt or auto-promoting op"]
A --> E{"Fractional?"}
E -->|Exact ratio| F["Ratio"]
E -->|Decimal business value| G["BigDecimal or minor units"]
E -->|Approximate math| H["Double"]
A --> I{"Java API boundary?"}
I -->|Yes| J["convert or type hint deliberately"]
(class 1), (class 1N), (class 1M), and (class 1/3) at the REPL.(/ 5 2) with (quot 5 2) and explain when each is appropriate.total-cents using integer minor units rather than floating-point dollars.+, then fix it with +' or N.Long, not Java’s default int./ returns ratios for non-exact integer division.= for general value equality and == for numeric equality across representations.