1

I'm trying to understand the Go Sqrt implementation and can't quite comprehend what is going on with the Float64bits function. I have some test code and output below. Why does the value of ix change so drastically with this operation?

package main

import ("math"
        "fmt")

func main()  {

    var x float64 = 4
    fmt.Printf("The value of x is: %v \n", x)

    ix := math.Float64bits(x)
    fmt.Printf("The value of ix is: %v \n", ix)
    fmt.Printf("The type of ix is: %T \n", ix)

}
The value of x is: 4 
The value of ix is: 4616189618054758400 
The type of ix is: uint64 
smugcloud
  • 627
  • 1
  • 9
  • 18
  • I believe the value you're looking at is hex. If you know the formula for floating point representations (more info here https://en.wikipedia.org/wiki/IEEE_floating_point) then you could in theory apply it to that value and it would give you 4. – evanmcdonnal Jun 10 '16 at 23:12

3 Answers3

4

From the documentation, it converts the float64 into an uint64 without changing the bits, it's the way the bits are interpreted that change.

Here is the full source code of the Float64bits function:

func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }

Don't be scared by that syntax trick of using an unsafe Pointer, it's quite common in Go's source code (avoids copying the data). So, that really is that simple: take the binary data of the given float and interpret it as an unsigned integer.

The reason it changes so much is because of the representation of floating point numbers. According to the specification, a floating point number is composed of a Sign, an Exponent and a Mantissa.

On a 64 bits float, there is 1 bit for the Sign, 11 bits for the exponent and 52 bits for the mantissa.

The representation of 4 as a floating point number on 64 bits is:

0b0100 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
  SEEE EEEE EEEE MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM

It turns out that this value is 4616189618054758400 if interpreted as an unsigned integer. You'll find plenty of great tutorials on the web regarding the IEEE754 to understand fully how the above value is a representation of 4.

T. Claverie
  • 11,380
  • 1
  • 17
  • 28
2

As the documentation says, the function just interprets the data that form the float as uint64.

An IEEE 754 double has this bit layout:

SEEEEEEE EEEEMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM

These are 64 bits consisting of:

  • one sign bit S
  • exponent bits E
  • mantissa bits M

The value 4.0 equals this bit representation:

01000000 00010000 00000000 00000000 00000000 00000000 00000000 00000000

An detailed explanation why is looks this way would be too lengthy. There are some special rules regarding the mantissa which play a key role here. We can just ignore that for now, please see the linked doc if you are interested in all the dirty details of how numbers are represented in IEEE float format.

The above function does nothing else as treating these 64 bits as if they were an uint64. At the end this is just casting a bunch of bits that happen to fit into an uint64. Hence, the resulting number is totally different from the float value.

JensG
  • 13,148
  • 4
  • 45
  • 55
1

use %#X in fmt.Printf to format Hex value. and %[1] to refer to the first arg like this sample code:

package main

import "fmt"
import "math"

func main() {
    var x float64 = 4
    fmt.Println("x =", x)

    ix := math.Float64bits(x)
    fmt.Printf("bits: %#X = %[1]v %[1]T\n", ix)
}

output:

x = 4
bits: 0X4010000000000000 = 4616189618054758400 uint64

and see:

https://en.wikipedia.org/wiki/Double-precision_floating-point_format https://en.wikipedia.org/wiki/IEEE_floating_point

Why does adding 0.1 multiple times remain lossless?

golang: what does "%b" do in fmt.Printf for float64 and what is Min subnormal positive double in float64 in binary format?

I hope this helps.

Community
  • 1
  • 1