12

So computers use Two's complement to internally represent signed integers. I.e., -5 is represented as ^5 + 1 = "1111 1011".

However, trying to print the binary representation, e.g. the following code:

var i int8 = -5
fmt.Printf("%b", i)

Outputs -101. Not quite what I'd expect. Is the formatting different or is it not using Two's complement after all?

Interestingly, converting to an unsigned int results in the "correct" bit pattern:

var u uint8 = uint(i)
fmt.Printf("%b", u)

Output is 11111011 - exactly the 2s complement of -5.

So it seems to me the value is internally the really using Two's complement, but the formatting is printing the unsigned 5 and prepending a -.

Can somebody clarify this?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
joerx
  • 2,028
  • 1
  • 16
  • 18
  • 2
    I am not sure why anyone considers this "weird." No matter what number base you use, a negative number is still negative. If you asked for it in base 8, 12 or 16 I'd expect the same thing. – Zan Lynx Jun 04 '16 at 01:10
  • Well, I didn't say weird. I was just trying to understand 2s complement and the results were not what I was expecting. – joerx Jun 06 '16 at 04:20
  • 1
    I don't think you can really hope to understand 2's complement by playing with a number formatting function written in a high-level language. – Jonathon Reinhart Jun 06 '16 at 04:24
  • 2
    What do you suggest instead? – joerx Jun 06 '16 at 04:28

3 Answers3

11

I believe the answer lies in how the fmt module formats binary numbers, rather than the internal format.

If you take a look at fmt.integer, one of the very first actions that the function does is to convert the negative signed integer to a positive one:

   165      negative := signedness == signed && a < 0
   166      if negative {
   167          a = -a
   168      }

There's then logic to append - in front of the string that's output here.

IOW -101 really is - appended to 5 in binary.

Note: fmt.integer is called from pp.fmtInt64 in print.go, itself called from pp.printArg in the same function.

Frederik Deweerdt
  • 4,943
  • 2
  • 29
  • 31
0

Here is a method without using unsafe:

package main

import (
   "fmt"
   "math/bits"
)

func unsigned8(x uint8) []byte {
   b := make([]byte, 8)
   for i := range b {
      if bits.LeadingZeros8(x) == 0 {
         b[i] = 1
      }
      x = bits.RotateLeft8(x, 1)
   }
   return b
}

func signed8(x int8) []byte {
   return unsigned8(uint8(x))
}

func main() {
   b := signed8(-5)
   fmt.Println(b) // [1 1 1 1 1 0 1 1]
}

In this case you could also use [8]byte, but the above is better if you have a positive integer, and want to trim the leading zeros.

https://golang.org/pkg/math/bits#RotateLeft

Zombo
  • 1
  • 62
  • 391
  • 407
  • Some things are best kept to yourself, but other things like this you should just pretend never existed – ardnew Jun 28 '23 at 23:51
-1

Unsafe pointers must be used to correctly represent negative numbers in binary format.

package main

import (
    "fmt"
    "strconv"
    "unsafe"
)

func bInt8(n int8) string {
    return strconv.FormatUint(uint64(*(*uint8)(unsafe.Pointer(&n))), 2)
}

func main() {
    fmt.Println(bInt8(-5))
}

Output

11111011
Soslan
  • 102
  • 3