格式将64位整数-1打印为golang和C之间的十六进制偏差。

I'm recently reading the effective_go document, and got shocked when reading the Print section:

var x uint64 = 1<<64 - 1
fmt.Printf("%d %x; %d %x
", x, x, int64(x), int64(x))

prints

18446744073709551615 ffffffffffffffff; -1 -1

As a C programmer the expected output of %x int64(x) should also be ffffffffffffffff, so the above output is unreasonable to me, could anyone tell why?

int64(math.MaxUint64) overflows the range of int64:

package main

import (
    "fmt"
    "math"
)

func main() {
    var x uint64 = 1<<64 - 1
    fmt.Printf("%v
", math.MaxUint64 == x) // true

    var y int64 = 1<<63 - 1
    fmt.Printf("%v
", math.MaxInt64 == y) // true

    var z int64 = int64(x) // -1
    fmt.Printf("%v
", z)
}

Playground: https://play.golang.org/p/J6buiaaZcFt

see https://golang.org/pkg/math/#pkg-constants and https://play.golang.org/p/iul2dgRbK2E

Go acts differently here than C for example. The %x verb for integers means to format the value of the number using the hexadecimal (base 16) representation, not its memory representation (which would be 2's complement). Same goes for %b for binary and %o for octal representation.

For a negative number like -255, its base16 representation is -ff.

Go is strict about types, you always have to be explicit. If you pass a signed integer, it will be formatted as a signed integer. If you want to print it as an unsigned value, you have to explicitly convert it to an unsigned value like in this example:

i := -1 // type int
fmt.Printf("%d %x %d %x", i, i, uint(i), uint(i))

Output (try it on the Go Playground):

-1 -1 4294967295 ffffffff

Note that when converting a signed value to its unsigned version (same size) it does not change the memory representation just its type, so the converted value (the unsigned result) will be the 2's complement of the signed value.

As to why this is the default for negative numbers, read the reasoning given by Rob Pike here:

Why is that not the default [the unsigned format]? Because if it were, there would be no way to print something as a negative number, which as you can see is a much shorter representation. Or to put it another way, %b %o %d %x all treat their arguments identically.

You can see how / where this is implemented in this related question: Golang: Two's complement and fmt.Printf

The C Programming Language, Second Edition

x,X int ; unsigned hexadecimal number (without a leading 0x or 0X ), using abcdef or ABCDEF for 10, ...,15.

x,X unsigned int ; unsigned hexadecimal notation (without a leading 0x or 0X ), using abcdef for 0x or ABCDEF for 0X .

The GNU C Library Reference Manual, Version 2.27

‘%x’, ‘%X’ Print an integer as an unsigned hexadecimal number. ‘%x’ uses lower-case letters and ‘%X’ uses upper-case.


I am shocked- shocked- to find that integer conversions are going on here!

In C, implicit conversions abound. In Go, by design, conversions are explicit.

In C, printf implicitly converts a signed integer to unsigned for format %x. Go fmt.Printf does not.

By design, Go is not C. Go does have similarities to C, which may trap the unwary.