Skip to end of metadata
Go to start of metadata

This page is in progress and is the artifact for http://dev.clojure.org/jira/browse/CLJ-718.

Numbers and Math

As of version 1.3, Clojure provides full support for JVM primitive values, allowing high performance, idiomatic Clojure code for numeric applications.

Numeric Types

  • Primitives - These are raw JVM primitives, not object references. They are by far the fastest way to do math on the JVM. Although it supports all primitives for the purposes of Java interoperability, pure Clojure uses only long and double. Clojure will seamlessly promote ints to longs and floats to doubles as needed. When calling back to Java for interop, conversions to a smaller primitive type are checked for overflow, so no safety is lost.
  • Boxed Numbers - These are full objects that extend the java.lang.Number class. Clojure supports the full set of numeric types built into the JVM: Byte, Short, Integer, Long, BigInteger, Float, Double, and BigDecimal, as well as two additional types provided by Clojure:
    • clojure.lang.Ratio, for expressing rational numbers that cannot be expressed as a terminating decimal
    • clojure.lang.BigInt, which is similar to Java's java.math.BigInteger in allowing integer sizes exceeding 64 bits, but provides better performance by delegating to native operations when small enough. Also, unlike BigInteger, Clojure's BigInt provides a consistent hash value with Integer and Long, meaning that it can be used interchangeably with them as map keys. Clojure code utilizes BigInt in favor of BigInteger.

Although Clojure supports the full set of JVM boxed types, Clojure does not use the shorter types (Byte, Integer, Float) internally. They are used only for Java interoperability. Clojure may automatically promote these types to Long and Double.

Literals

In contrast to previous versions of Clojure, numeric literals are parsed as primitive longs or doubles. Clojure also provides a new literal syntax for the new BigInt class: append a capital N to the number, e.g, 42N. Integers too long to be primitives are also parsed as BigInts.

Math Operations

All of Clojure's mathematical functions and operators work polymorphically on all numeric types, primitive or boxed. When given primitives, they will use native JVM operations.

When different types of numbers are used in a math operation, the result will be the larger or more general of the two types. For example, any integer operation involving a BigInt will result in a BigInt, and any operation involving a double or float will result in a double. In other words, BigInts and floating point types are "contagious" across operations. This is an intentional and useful feature, as it allows entire algorithms to be fully polymorphic based on their inputs. An algorithm written using standard operators will use native integer math when given primitive longs, native floating point math when given doubles, and will function correctly with no overflow when a BigInt is introduced.

When a primitive integer operation results in a value that too large to be contained in a primitive value, a java.lang.ArithmeticException is thrown - values will never roll over silently. This feature makes primitive math possible, while retaining safety from bugs due to silent overflow. It also ensures consistent semantics across usage with both primitives and boxed numbers.

If automatic promotion to a BigInt is required, and introducing a BigInt to the original operation to make use of BigInt contagion is not possible, Clojure provides a set of alternative math operators suffixed with an apostrophe: +', -', *', inc', and dec'. These operators auto-promote to BigInt upon overflow, but are less efficient than the regular math operators in that they cannot return primitive integers, only boxed ones.

For applications where integer overflow is not a concern, or where behavior consistent with Java is required, additional performance can be achieved by using unchecked math operations. These can be enabled by setting the compiler flag *unchecked-math* to true. Alternatively, Clojure provides a series of unchecked operation functions:  unchecked-add, unchecked-subtract, unchecked-multiply, unchecked-divide, unchecked-inc, unchecked-dec, unchecked-negate and unchecked-remainder. Using unchecked operations eliminates the slight overhead associated with bounds checking, but is unsafe in that primitive types will silently roll over when they exceed the size allowed by their bit length, instead of throwing an exception as the standard operators do.

Equality

The = operator (equality) tests equality. It compares values in a type-independent manner, but not between floating points and integer types. This allows numbers to be used as map keys with correct semantics. To check numerical equivalence between floating point and integer types, use the == (equivalence) operator.

Loops

Loop constructs initialized with a primitive value will compile to a primitive loop. If the type of the value passed to recur is not primitive, the loop will be recompiled to use boxed numbers instead. This will trigger a warning if the *warn-on-reflection* flag is enabled, to facilitate debugging a program's use of primitives.

Primitive Hinting

By default, when a number is passed to or returned from a Clojure function, it is boxed (that is, it is handled as a java.lang.Object). To prevent this, it is possible to add long or double primitive type hints to a Clojure function. The return type can be hinted as a primitive by placing metadata on the arg list, and arguments by putting metadata on them directly.

For example, the following function takes and returns only primitive longs with no boxing, even across its recursive function call boundary:

It is important to note that due to the underlying Java implementation, functions which take primitive arguments may only have 4 or fewer arguments. Trying to define a function with primitive hints and more than four arguments will result in a compiler exception.

Casting and Coercions

It is possible to convert a boxed number into a JVM primitive using one of the following coercion functions: byte, char, int, long, float or double. Be aware, however, that unless the value is an immediate argument to a Java interop call or a Clojure function hinted to match, it is likely to be immediately auto-converted back to a boxed type.

Similarly, it is possible to convert a primitive number into a boxed form using the num function, or into a BigDecimal or BigInt using the bigdec or bigint functions, respectively.

Labels:
  1. Jun 01, 2011

    Comment re version 9 on Mar 18, 2011:

    Clear and concise. More examples of what to do and, more importantly, what not to do would be a useful addition.