Actually, more like `Numbers` did a number on me. But we’re good friends now. `Numbers` is ready to go. This is a long post; `Numbers` is big.

In the previous post, A numbers game, I talked about how I might approach implementing `clojure.lang.Numbers`, the heart of the implementation of arithmetic in Clojure. At the end of that post was a section called “What we’ll do”:

• Implement enough of `Ops` and its subclasses to get `equiv`, `compare`, and `hasheq`
• Maybe contemplate whether there is a better way to accomplish the two-parameter arg dispatch in F#.
• Definitely contemplate how to get the remaining operations, mostly arithmetic operations, working with this when we have the other pieces to make that possible. (But mostly kicking this can down the road.)

I ended up implementing all of it except for one tiny piece that won’t be needed until much later. I did end up changing the type-dispatch based on some benchmarking And the third point is now moot.

Here’s how it went down, including a quick look at the internals of `Numbers`.

I had contemplated porting just enough of `Numbers` to enable continued work on the collections implementation. Two reasons for this: (1) some concerns about how entangled `Numbers` was with other parts of Clojure; and (2) `Numbers` is big. For (1), further analysis showed that the entanglement was not too bad. For (2), to give you an idea, my port of just the code in `Numbers` itself has around 650 member methods. That does not include some of the supporting classes, such as `Ratio` and `BigInt.` But I finally decided to do it all just to make sure I was not missing something that would be a bigger problem down the road.

## Dispatching dispatch

In the previous post, we discussed the need to take objects and map them to a numerical category, and take pairs of objects and map them to a common category.

Our categories are L = `Long`, D = `Double`, R = `Ratio`, BI = `BigInteger` and BD = `BigDouble`, UL = `Unsigned long` and CD = `decimal (CLR built-in)`. Our mapping table is this:

* L D R BI BD UL CD
L > L D R BI BD BI CD
D > D D D D D D D
R > R D R R BD R BD
BI > BI D R BI BD BI BD
BD > BD D BD BD BD BD BD
UL > BI D R BI BD UL CD
CD > CD D BD BD BD CD CD

I couldn’t think of anything simpler than just using an enum to represent the categories, and 2D array to represent the table.

Mapping an object to its category is a simple `match` on type. The code, leaving out some of the table initialization–that gets boring quickly– is here:

``````type OpsType =
| Long = 0 | Double = 1 | Ratio = 2 | BigInteger = 3
| BigDecimal = 4 | ULong = 5  | ClrDecimal = 6

module OpsSelector =

let selectorTable =
array2D
[|  // first row of table
[| OpsType.Long; OpsType.Double; OpsType.Ratio; OpsType.BigInteger;
OpsType.BigDecimal; OpsType.BigInteger OpsType.ClrDecimal |]

// etc

|]

let ops (x: obj) : OpsType =
match Type.GetTypeCode(x.GetType()) with
| TypeCode.SByte
| TypeCode.Int16
| TypeCode.Int32
| TypeCode.Int64 -> OpsType.Long

| TypeCode.Byte
| TypeCode.UInt16
| TypeCode.UInt32
| TypeCode.UInt64 -> OpsType.ULong

| TypeCode.Single
| TypeCode.Double -> OpsType.Double

| TypeCode.Decimal -> OpsType.ClrDecimal

| _ ->
match x with
| :? BigInt
| :? BigInteger -> OpsType.BigInteger
| :? Ratio -> OpsType.Ratio
| :? BigDecimal -> OpsType.BigDecimal
| _ -> OpsType.Long

let combine (t1: OpsType, t2: OpsType) = selectorTable[int (t1), int (t2)]
``````

(I’ll back to that placement of `Byte` later on.)

Yes, you have to set your enum values and arrange the table properly. I have a unit test that explicitly lists and checks for all 49 entries. But we now can see the interactions all in one place; that beats chasing around a 4000-line file hunting down the connections.

Unsurprisingly, array lookup is faster than working through virtual methods as the current implementation does. More than twice as fast.

The benchmark results are:

Method Mean Error StdDev Ratio RatioSD
TypeCombine 349.9 us 6.98 us 10.23 us 1.00 0.00
LookupCombine 178.9 us 2.81 us 2.63 us 0.51 0.02
LookupCombine2D 168.9 us 2.04 us 1.91 us 0.48 0.02

`TypeCombine` is the virtual method approach. `LookupCombine2D` is the 2D table lookup. The `LookupCombine` was a version of table lookup that used an array of arrays rather than a 2D array. That requires an extra array lookup. Just thought I’d check. Not least because in the virtual method approach, the `combine` operation hands back the implementation object for the category. In my 2D approach, `combine` hands back an `enum` which I then use to index into an array of objects. So I end up with two array lookups. I’m pretty confident from this test that that is still a win.

The benchmark code can be found here.

## Clojure arithmetic

Check out the Clojure cheatsheet for a handy list of Clojure functions. For our purposes, look under the heading Primitives/Numbers; the functions listed as Arithmetic, Compare, Bitwise, and Unchecked, as well as a few others such as `zero?` all rely on the class `clojure.lang.Numbers`.

Much of the complexity of `Numbers` comes from the implementation of the arithmetic operators. The functions `+`, `-`, `*`, `inc`, and `dec` are all quite similar. And they come in quoted versions: `+'`, `='`, etc. The functions `unchecked-add` and the other `unchecked-` functions will come into play, too.

Here again is the Clojure source for `+`:

``````(defn +
"Returns the sum of nums. (+) returns 0. Does not auto-promote
:inline-arities >1?
([] 0)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (add x y)))
([x y & more]
(reduce1 + (+ x y) more)))
``````

You need to look at the `:inline`. The `nary-inline` call makes `+` expand into a call to `Numbers.add` or `Numbers.unchecked_add` depending on the value of `*unchecked-math*`. By comparison, here is the source for `+'`:

``````(defn +'
"Returns the sum of nums. (+') returns 0. Supports arbitrary precision.
:inline-arities >1?
([] 0)
([x] (cast Number x))
([x y] (. clojure.lang.Numbers (addP x y)))
([x y & more]
(reduce1 +' (+' x y) more)))
``````

Unchecked math no longer comes into play. We just call `Numbers.addP`

Finally, `unchecked-add` is:

``````(defn unchecked-add
"Returns the sum of x and y, both long.
Note - uses a primitive operator subject to overflow."
{:inline (fn [x y] `(. clojure.lang.Numbers (unchecked_add ~x ~y)))
[x y] (. clojure.lang.Numbers (unchecked_add x y)))
``````

It just uses `Numbers.unchecked_add`, so it is the same as `+` in an unchecked context. Note the slight variations in the comments:

• `+`: “Does not auto-promote longs, will throw on overflow.”
• `+'`: “Supports arbitrary precision.”
• `unchecked-add`: “uses a primitive operator subject to overflow.”

(The comment on `+` is incorrect – it will throw if `*unchecked-math*` is false, else it will “[use] a primitive operator subject to overflow.”)

Note that “auto-promote” and “supports arbitrary precision”. are the same: if the result of an operation cannot be represented in the type in question, promote to a wider type and do the computation there. As an example, add 1 to the maximum long value with each flavor of addition:

``````user=> (+ Int64/MaxValue 1)
...
integer overflow                          <---- BOOOOMMM!
user=>
user=> (+' Int64/MaxValue 1)
9223372036854775808N                      <-  BigInteger, our of Int64 range
-9223372036854775808                      <- overflow, wrap around to negative
user=> (class *1)
System.Int64
user=> (== *2 Int64/MinValue)
true
user=>
``````

We will dig into `Numbers.add`, `Numbers.addP`, and `Numbers.unchecked_add`. The other arithmetic operators are similar. `Numbers.add` has a lot of overloads:

``````add(x: obj, y: obj)
``````

And the same for `addP` and `unchecked_add`. (Starting to get an idea why there are 650 methods in `Numbers`?)

Why so many overloads? Why these in particular?

Leaving out `decimal` for the moment, the types mentioned are the three primitive numeric types you are likely to run into–`double`, `int64` and `uint64`–plus `obj`, which covers everthing else. The sixteen entries cover all pairs. The ones with specific numeric primitive type will be called if there is an inferred type at the point of invocation. Depending on the particulars, we might be able to avoid boxing, both at the call site and inside the method itself.

Take the `double` overloads in all their glory:

``````    static member add(x: double, y: double) = x + y
static member add(x: double, y: obj) = x + y
static member add(x: obj, y: double) = x + y
static member add(x: double, y: int64) = x + double (y)
static member add(x: int64, y: double) = double (x) + y
static member add(x: double, y: uint64) = x + double (y)
static member add(x: uint64, y: double) = double (x) + y
``````

Because `double` is contagious – if there is a double in your computation, the other argument will be converted to double.– in the overloads of `double` with the primitive numeric types, we just convert and do the addition – not boxing.

(For some reason the double/obj and obj/double methods on the JVM are written the equivalent of `static member add(x: double, y: obj) = Numbers.add (x, convertToDouble (y))` I have no idea why. If you do, let me know.)

As a bonus, the return type for each is `double`, providing typing for the result that might avoid boxing at the call site.

The situation is more complicated when overflow is involved. (`double` arithmetic does not overflow – `+/-Inf` take care of that.) Let’s look at `int64`. We cannot just define

``````    static member add(x: int64, y: int64) = x + y
``````

because the `+` operator is unchecked. In C#, one could wrap this in `checked` context. In F#, there are checked versions of the arithmetic operators, but I’ve not found a good way to use them locally; see [this StackOverflow entry] https://stackoverflow.com/questions/2271198/f-checked-arithmetics-scope) for an example. (The JVM version uses `java.util.math.addExact(long,long))` here.) So we’ll do it ourselves.

How can we tell if an overflow has occurred?

Overflow occurs when the result of the addition cannot be represented in the type in question. With unchecked addition, you still get a result, its just not the arithemetically correct answer: `9223372036854775807L + 1L = -9223372036854775808L`. Overflow in addition cannot happen when the operands are of differing sign (+/-). The symptom of overflow is that operands have the same sign and the result has the opposite sign. One could write a complicated condition to test for this. Or one could use some bit-twiddling magic to do it more efficiently.

In 2’s-complement arithmetic, the sign is carried in the most-signifiant bit (MSB). If you XOR two integers, the result will have 0 in the MSB if their MSBs agree (i.e., they have the same sign) and 1 if they disagree. Thus a negative XOR result indicates differing sign. Our overflow test is that the sum differs in sign from both operands. Thus:

``````static member add(x: int64, y: int64) =
let ret = x + y

if (ret ^^^ x) < 0 && (ret ^^^ y) < 0 then
raise <| OverflowException("integer overflow")
else
ret
``````

You are going to have do this kind of analysis for each of `int64 `, `uint64`, and `decimal` for each arithmetic operation. If your’re thinking those unchecked operators are looking better, well, they won’t help in the long run. `addP` is going to have to check for overflow, so you’re still going to have to put in the work. (Or you could try checked and catch overflow exceptions, but that just seems wrong.)

The other overloads of `add` for `int64` pretty much have to throw in the towel. For example,

``````static member add(x: int64, y: obj) = Numbers.add (x :> obj, y)
``````

In other words, just defer to `add(obj,obj)` and let it do its magic. This the default approach when you can’t figure out something more bespoke to do.

And thus we arrive at the most general case:

``````static member add(x: obj, y: obj) = Numbers.getOps(x, y).add (x, y)
``````

We discussed this operation in detail in the previous post. We dispatch to an appropriate handler depending the types of the arguments, per the table given above.

This is not circular. The method we are defining above is `Numbers.add`. The `add` we calling there is on a class specialized for operations of a particular category, the category selected by the `getOps`.

Let’s continue with addition and look at `addP`. This is the promoting version of the operation. It has the same overloads as `add`. Promoting happens when the result of the operation is not representable in the type in question. That can’t happen with `double`, so the `addP` overloads involving `double` are identical to their `add` counterparts. For `int64`, the result not being representable is just the condition of overflow. Promotion here means to move to a type that can represent the result. Thus

``````static member addP(x: int64, y: int64) =
let ret = x + y

if (ret ^^^ x) < 0 && (ret ^^^ y) < 0 then
Numbers.addP (x :> obj, y :> obj)
else
ret :> obj
``````

When overflow is detected, rather than throwing an `OverflowException` as we did in `add`, we default to the double-`obj` case, which is just as you might expect:

``````static member addP(x: obj, y: obj) = Numbers.getOps(x, y).addP (x, y)
``````

And I’ve never understood why this is coded this way. Because here’s what’s going to happen. The `getOps` is going to hand us a `LongOps` object because both arguments are `int64` and our combination table says `L x L -> L`. In `LongOps.addP` we are going to do the exact same thing as here: do the addition and the check for overflow. The difference is what we do on this overflow. We call `Numbers.BIGINT_OPS.add (x, y)`. WHy didn’t we just do that directly? Are we afraid that somewhere down the line `int64` overflows are going to be handled by something other than `BigInt`? We incur a cost here of an extra method call, to conversions of the boxed integers, and the extra addition/overflow check. (I’ll probably be changing to a direct call and just leave note in the code.)

After all that, `unchecked_add` is marvelously uncomplicated. Just unchecked addition using the built-in operation.

``````static member unchecked_add(x: int64, y: int64) = x + y
``````

And the general case is handled in the usual way:

``````static member unchecked_add(x: obj, y: obj) =
``````

## The general case

The static class `Numbers` itself provides the entry points that Clojure code calls into. Where possible, as you’ve seen, it will try to do the operation. But the general case always comes down to: take the operands, determine what category of number we should be working in–`L`, `D`, `R`, `BI`, `BD`, `UL`, or `CD`, as above– and hand it off to a specialized object. These objects implement the `Ops` inteface:

``````type Ops =
abstract isZero: x: obj -> bool
abstract isPos: x: obj -> bool
abstract isNeg: x: obj -> bool

abstract add: x: obj * y: obj -> obj
abstract addP: x: obj * y: obj -> obj
abstract unchecked_add: x: obj * y: obj ->

...
``````

There is class for each numeric category: `LongOps`, `DoubleOps`, etc. When a method on one of these is invoked, say `BigDecimalOps`, you know that you are working in a context where the arguments can be converted to the type in question, namely `BigDecimal`. Do that, compute your result accordingly.

A few examples will give the general idea. For `DoubleOps`,

``````member this.add(x, y) : obj =
convertToDouble (x) + convertToDouble (y) :> obj
``````

while for `LongOps`:

``````member this.add(x, y) : obj =
``````

Here is why it hard to decouple `Numbers` from the `Ops` classes. They call back and forth.

There are some other tricks used in the code. For example, for types that do not involve promotion (`D`, `R`, `BI`, and `BD`), the `P` versions of the operations can just call the regular versions:

``````member this.addP(x, y) = (this :> Ops).add (x, y)
``````

Given that these things are true for four classes, we capture those commonalities in an abstract base class for those specific classes.

With this background, you should be able to work through the rest of the code. It all comes down to the peculiarities of the individual categories.

## The problem with Byte

In Java, the primitive type `byte` is signed. The CLR has both `Byte` and `SByte`, unsigned and signed, respectively. `Byte` is used when you are doing byte-level work for I/O, etc. I don’ think I’ve every used `SBtye`.

When I created the signed and unsigned hierarchies, I assigned `Byte` to the unsigned side.

``````      sbyte <  int16 <  int32 <  int64
byte < uint16 < uint32 < uint64
``````

Though that is technically pure, the result is that `(+ b 1)`, where `b` is byte, ends up involving `BigInt` because we are mixing signed and unsigned integers, which takes us into `int64` and `uint64` by promotion, and those two `combine` to `BigIntOps`. I’m not sure that’s what people would expect.

When I first thought about adding proper handling of unsigned, I debated just promoting all the unsigned type other than `uint64` into `int64`–their values are all representable in `int64`. Only `uint64` has values not representable in `int64` (and vice versa). The fact is that is almost impossible to stay working with any primitive numeric type other than `int64` and `double` in Clojure.

I have a dream of someday adding a compiler mode that allows all the primitive types to be handled efficiently. It certainly would be non-portable and hence need to be a switched feature. But I’m thinking here of someone like a person using Clojure in Unity where efficiency in handling those numeric types is important.

## Status

As I write this, I have completed porting the `Numbers` code. I’m writing tests like crazy. In an upcoming post I’ll provide a link to a snapshot of the code at this point.

In the process of writing tests, I found bugs that I traced back to the original code. (My code, not the JVM code.) These are all edge cases in the handling of `decimal` and `uint64`. Code to handle those types are a very recent addition. I will be doing bug fixes in the current code.