7

I'm running Clojure 1.4.0. Why is it if I add Integer/MAX_VALUE and 1, I get a Long, but if I add Integer/MAX_VALUE to itself, I get an exception?

=> (def one 1)
=> (class one)
java.lang.Integer
=> (def max-plus-one (+ Integer/MAX_VALUE one))
=> max-plus-one
2147483648
=> (class max-plus-one)
java.lang.Long

=> (+ Integer/MAX_VALUE Integer/MAX_VALUE)
java.lang.ArithmeticException: integer overflow (NO_SOURCE_FILE:0)

Shouldn't they both act the same way? Why does adding two MAX_VALUE values overflows but adding 1 doesn't?

I've seen this SO question but they are getting different behaviour than I am.

Community
  • 1
  • 1
cdmckay
  • 31,832
  • 25
  • 83
  • 114

3 Answers3

7

That's strange, I see different results with Clojure 1.4.0and Java(TM) SE Runtime Environment (build 1.7.0_06-b24), on Ubuntu 12.04 64bit:

user=> *clojure-version*
{:major 1, :minor 4, :incremental 0, :qualifier nil}
user=> (+ Integer/MAX_VALUE Integer/MAX_VALUE)
4294967294
user=> (type 1)
java.lang.Long
user=> (def max-plus-one (+ Integer/MAX_VALUE one))
#'user/max-plus-one
user=> max-plus-one
2147483648
user=> (type max-plus-one)
java.lang.Long
user=> (+ Integer/MAX_VALUE Integer/MAX_VALUE)
4294967294

You can always check the Java classes which clojure.core uses for numerics, to see how the functionality is implemented:

The implementation of the + operator in:

(defn +
  "Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'"
  {:inline (nary-inline 'add 'unchecked_add)
   :inline-arities >1?
   :added "1.2"}
  ([] 0)
  ([x] (cast Number x))
  ([x y] (. clojure.lang.Numbers (add x y)))
  ([x y & more]
     (reduce1 + (+ x y) more)))

Java implementation of adding longs:

final public Number add(Number x, Number y){
    return num(Numbers.add(x.longValue(),y.longValue()));
}

Edits: Tested with Clojure 1.2.1
I have done a quick test with Clojure 1.2.1, and with that version of Clojure I get exactly your behavior.

user=> *clojure-version*
{:major 1, :minor 2, :incremental 1, :qualifier ""}
user=> (def one 1)
#'user/one
user=> (class 1)
java.lang.Integer
user=> (def max-plus-one (+ Integer/MAX_VALUE one))
#'user/max-plus-one
user=> max-plus-one
2147483648
user=> (class max-plus-one)
java.lang.Long
user=> (+ Integer/MAX_VALUE Integer/MAX_VALUE)
java.lang.ArithmeticException: integer overflow (NO_SOURCE_FILE:0)

I'd say that you did the test with Clojure 1.2.x, and not with 1.4.0. What is the value of *clojure-version* in your REPL?

raju-bitter
  • 8,906
  • 4
  • 42
  • 53
  • You're right: `{:major 1, :minor 2, :incremental 1, :qualifier ""}`. I have 1.4.0 installed but it appears the Sublime Text 2 REPL uses its own Clojure. – cdmckay Aug 19 '12 at 20:10
  • I've had a similar experience in the past, when I had multiple versions of Clojure on my machine. – raju-bitter Aug 19 '12 at 20:18
  • Yeah it looks like the SublimeREPL plugin runs `lein repl` which is loading up Clojure 1.2.1 for some reason. – cdmckay Aug 19 '12 at 20:22
  • Ok, for people curious as to why the version of `lein repl` is different than your `clj` version: https://github.com/technomancy/leiningen/issues/337 and http://stackoverflow.com/questions/10135440/lein-clojure-1-3-vs-clojure-1-2-1 – cdmckay Aug 19 '12 at 20:28
4

Looks like you have your answer, but here are a few other interesting points:

java (all versions) and clojure's (>1.3.0) default behaviour differ in their behaviour wrt overflow

in java

(Long.MAX_VALUE + 1) == Long.MIN_VALUE
(Integer.MAX_VALUE + 1) == Integer.MIN_VALUE

// cast required to avoid promoting to int
(Byte.MAX_VALUE + (byte)1) == Byte.MIN_VALUE 

This is because arithmetic wraps by default on the jvm

in clojure (>1.3.0)

(inc Long.MAX_VALUE) 
   => ArithmeticOverflow

(inc Integer/MAX_VALUE) 
   => a long with value Integer/MAX_VALUE + 1
(int (inc Integer/MAX_VALUE)) 
   => IllegalArgumentException Value 
      out of range for int: 2147483648 

clojure does have versions of some ops that behave like java

(unchecked-inc Long.MAX_VALUE) => Long.MIN_VALUE

You can make the unchecked operations the default by setting *unchecked-math* to true

(set! *unchecked-math* true)
(inc Long/MAX_VALUE) 
    => (Long.MIN_VALUE)
(int (inc Integer/MAX_VALUE)) 
    => (Integer.MIN_VALUE) of type Integer

There are lots of other interesting (unchecked-*) operations.

sw1nn
  • 7,278
  • 1
  • 26
  • 36
1

As of version 1.3.0 Clojure uses Longs for all primitive numbers. you just need to use larger numbers to get the overflow.

 (def max-plus-one (+ Long/MAX_VALUE one))
Arthur Ulfeldt
  • 90,827
  • 27
  • 201
  • 284
  • The strange thing is that (class 1) or (type 1) seems to return 'java.lang.integer' instead of long in cdmckay's case. If he's running Clojure 1.4.0, that shouldn't happen. – raju-bitter Aug 19 '12 at 18:47