21

I'm building a Lisp, and I want 32 bit integers to automatically switch to 64 bit integers if a computation would cause them to otherwise overflow. And likewise, for 64 bit overflows, switch to arbitrarily sized integers.

The problem I have is that I don't know what the "correct" way is to detect an integer overflow.

a, b := 2147483647, 2147483647
c := a + b

How can I efficiently check if c overflowed?

I have considered always converting to 64 bit values to do the calculation, then down-sizing again afterwards when possible, but that seems expensive and memory wasteful for something that is as primitive and core to the language as basic arithmetic.

d11wtq
  • 34,788
  • 19
  • 120
  • 195
  • I suspect boxing up is your best bet. There is no mechanism in the language precisely to keep addition fast. – captncraig Nov 10 '15 at 23:43
  • 1
    I think the extra 4 bytes is near negligible in most modern systems to just use 64bit vs 32bit integers in most situations (exceptions made for large arrays, etc). As for runtime automatic detection of overflow without resorting to assembly, and if your environment doesn't throw an exception you can catch, it isn't trivial. IMHO. – Alderin Nov 10 '15 at 23:44
  • Converting to 64-bit *just for the calculation* has essentially no memory impact, since you're only holding a constant number of up-sized values at a time. This is what, for example, OpenJDK's [`Math.multiplyExact(int, int)`](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/lang/Math.java#864) does. – user2357112 Nov 11 '15 at 03:33
  • 2
    @captncraig it could be done with no overhead (except slightly uglier code). The processor already computes the overflow bit on every single addition, for free; all you need is a way to tell the compiler you're interested in it. GCC has `__builtin_add_overflow` these days, and `if(__builtin_add_overflow(a,b,c)) { ...` can compile right down to an `add` and a `jo`, or whatever for a given platform. – hobbs Nov 11 '15 at 04:22
  • @d11wtq honestly the reasonable thing to do here is to go 64-bit all the time. 32-bit CPUs are disappearing pretty quickly, and on a 64-bit machine there's no CPU cost to using 64-bit ints, and the memory cost is probably insignificant next to the overhead of other parts of your system. – hobbs Nov 11 '15 at 04:26
  • Beware of corner cases when checking for overflow too. For example, `math.MinInt8 / -1` when checking multiplication for overflow would cause an overflow itself. –  Nov 11 '15 at 04:26

2 Answers2

16

For example, to detect 32-bit integer overflow for addition,

package main

import (
    "errors"
    "fmt"
    "math"
)

var ErrOverflow = errors.New("integer overflow")

func Add32(left, right int32) (int32, error) {
    if right > 0 {
        if left > math.MaxInt32-right {
            return 0, ErrOverflow
        }
    } else {
        if left < math.MinInt32-right {
            return 0, ErrOverflow
        }
    }
    return left + right, nil
}
func main() {
    var a, b int32 = 2147483327, 2147483327
    c, err := Add32(a, b)
    if err != nil {
        // handle overflow
        fmt.Println(err, a, b, c)
    }
}

Output:

integer overflow 2147483327 2147483327 0
peterSO
  • 158,998
  • 31
  • 281
  • 276
  • 2
    Thanks! I think I might have a shorter solution, but not sure if there are edge cases where it doesn't work: `((c < a) != (b < 0))`, where `c := a + b`. – d11wtq Nov 17 '15 at 22:46
1

For 32 bit integers, the standard way is as you said, to cast to 64 bit, then down size again [1]:

package main

func add32(x, y int32) (int32, int32) {
   sum64 := int64(x) + int64(y)
   return x + y, int32(sum64 >> 31)
}

func main() {
   {
      s, c := add32(2147483646, 1)
      println(s == 2147483647, c == 0)
   }
   {
      s, c := add32(2147483647, 1)
      println(s == -2147483648, c == 1)
   }
}

However if you don't like that, you can use some bit operations [2]:

func add32(x, y int32) (int32, int32) {
   sum := x + y
   return sum, x & y | (x | y) &^ sum >> 30
}
  1. https://github.com/golang/go/blob/go1.16.3/src/math/bits/bits.go#L368-L373
  2. https://github.com/golang/go/blob/go1.16.3/src/math/bits/bits.go#L380-L387
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Zombo
  • 1
  • 62
  • 391
  • 407