By IhtkaS


2016-09-12 06:58:08 8 Comments

Why does below code fail to compile?

package main

import (
    "fmt"
    "unsafe"
)

var x int = 1

const (
    ONE     int = 1
    MIN_INT int = ONE << (unsafe.Sizeof(x)*8 - 1)
)

func main() {
    fmt.Println(MIN_INT)

}

I get an error

main.go:12: constant 2147483648 overflows int

Above statement is correct. Yes, 2147483648 overflows int (In 32 bit architecture). But the shift operation should result in a negative value ie -2147483648.

But the same code works, If I change the constants into variables and I get the expected output.

package main

import (
    "fmt"
    "unsafe"
)

var x int = 1

var (
    ONE     int = 1
    MIN_INT int = ONE << (unsafe.Sizeof(x)*8 - 1)
)

func main() {
    fmt.Println(MIN_INT)

}

1 comments

@icza 2016-09-12 07:33:37

There is a difference in evaluation between constant and non-constant expression that arises from constants being precise:

Numeric constants represent exact values of arbitrary precision and do not overflow.

Typed constant expressions cannot overflow; if the result cannot be represented by its type, it's a compile-time error (this can be detected at compile-time).

The same thing does not apply to non-constant expressions, as this can't be detected at compile-time (it could only be detected at runtime). Operations on variables can overflow.

In your first example ONE is a typed constant with type int. This constant expression:

ONE << (unsafe.Sizeof(x)*8 - 1)

Is a constant shift expression, the following applies: Spec: Constant expressions:

If the left operand of a constant shift expression is an untyped constant, the result is an integer constant; otherwise it is a constant of the same type as the left operand, which must be of integer type.

So the result of the shift expression must fit into an int because this is a constant expression; but since it doesn't, it's a compile-time error.

In your second example ONE is not a constant, it's a variable of type int. So the shift expression here may –and will– overflow, resulting in the expected negative value.

Notes:

Should you change ONE in the 2nd example to a constant instead of a variable, you'd get the same error (as the expression in the initializer would be a constant expression). Should you change ONE to a variable in the first example, it wouldn't work as variables cannot be used in constant expressions (it must be a constant expression because it initializes a constant).

Constant expressions to find min-max values

You may use the following solution which yields the max and min values of uint and int types:

const (
    MaxUint = ^uint(0)
    MinUint = 0
    MaxInt  = int(MaxUint >> 1)
    MinInt  = -MaxInt - 1
)

func main() {
    fmt.Printf("uint: %d..%d\n", MinUint, MaxUint)
    fmt.Printf("int: %d..%d\n", MinInt, MaxInt)
}

Output (try it on the Go Playground):

uint: 0..4294967295
int: -2147483648..2147483647

The logic behind it lies in the Spec: Constant expressions:

The mask used by the unary bitwise complement operator ^ matches the rule for non-constants: the mask is all 1s for unsigned constants and -1 for signed and untyped constants.

So the typed constant expression ^uint(0) is of type uint and is the max value of uint: it has all its bits set to 1. Given that integers are represented using 2's complement: shifting this to the left by 1 you'll get the value of max int, from which the min int value is -MaxInt - 1 (-1 due to the 0 value).

Reasoning for the different behavior

Why is there no overflow for constant expressions and overflow for non-constant expressions?

The latter is easy: in most other (programming) languages there is overflow. So this behavior is consistent with other languages and it has its benefits.

The real question is the first: why isn't overflow allowed for constant expressions?

Constants in Go are more than values of typed variables: they represent exact values of arbitrary precision. Staying at the word exact, if you have a value that you want to assign to a typed constant, allowing overflow and assigning a completely different value doesn't really live up to exact.

Going forward, this type checking and disallowing overflow can catch mistakes like this one:

type Char byte
var c1 Char = 'a' // OK
var c2 Char = '世' // Compile-time error: constant 19990 overflows Char

What happens here? c1 Char = 'a' works because 'a' is a rune constant, and rune is alias for int32, and 'a' has numeric value 97 which fits into byte's valid range (which is 0..255).

But c2 Char = '世' results in a compile-time error because the rune '世' has numeric value 19990 which doesn't fit into a byte. If overflow would be allowed, your code would compile and assign 22 numeric value ('\x16') to c2 but obviously this wasn't your intent. By disallowing overflow this mistake is easily caught, and at compile-time.

To verify the results:

var c1 Char = 'a'
fmt.Printf("%d %q %c\n", c1, c1, c1)

// var c2 Char = '世' // Compile-time error: constant 19990 overflows Char
r := '世'
var c2 Char = Char(r)
fmt.Printf("%d %q %c\n", c2, c2, c2)

Output (try it on the Go Playground):

97 'a' a
22 '\x16' 

To read more about constants and their philosophy, read the blog post: The Go Blog: Constants

And a couple more questions (+answers) that relate and / or are interesting:
Golang: on-purpose int overflow
How does Go perform arithmetic on constants?
Find address of constant in go
Why do these two float64s have different values?
How to change a float64 number to uint64 in a right way?
Writing powers of 10 as constants compactly

@IhtkaS 2016-09-12 07:44:04

As a follow up for the question, Can I use the second form to find the Minimum int value? Or Is there a better way to get the minimum int value?

@IhtkaS 2016-09-12 09:28:07

Just curious to know. Why do we need two different behavior? Is there a specific reason for allowing overflown value for variables but not for constants?

@Peter Gloor 2017-01-25 13:58:03

@spartan: compared to some other languages I see an advantage in this behaviour as it is always clear and you wont get strange results from some (internal) conversions you dont understand. Such problems are usually difficult to resolve and debug. I like the type system in Go and the way it deals with conversions. In case I get an overflow at compile time for fmt.Println(math.MaxUint64), where math.MaxUint64 is an untyped constant value, I simply convert it into a typed value by surrounding it with its type, in this case fmt.Println(uint64(math.MaxUint64)).

Related Questions

Sponsored Content

2 Answered Questions

5 Answered Questions

[SOLVED] Why can't I put the opening braces on the next line?

  • 2011-08-15 06:39:21
  • Anuj Verma
  • 746 View
  • 2 Score
  • 5 Answer
  • Tags:   error-handling go

2 Answered Questions

[SOLVED] How to define a function type which accepts any number of arguments in Go?

  • 2011-05-18 15:48:57
  • ceving
  • 1910 View
  • 7 Score
  • 2 Answer
  • Tags:   go

1 Answered Questions

1 Answered Questions

how to get skewness value from array in golang

1 Answered Questions

[SOLVED] Shifting bits of 64Bit Ints getting error

9 Answered Questions

[SOLVED] How does a ArrayList's contains() method evaluate objects?

2 Answered Questions

[SOLVED] Javascript bit shift to 32 bits

1 Answered Questions

4 Answered Questions

Sponsored Content