Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Go compiler's evaluation differ for constant expression and other expression

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)

}
like image 463
IhtkaS Avatar asked Sep 12 '16 06:09

IhtkaS


1 Answers

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

like image 71
icza Avatar answered Nov 12 '22 06:11

icza