How to change a float64 number to uint64 in a right way?

10.7k views Asked by At
package main

func main() {
    var n float64 = 6161047830682206209
    println(uint64(n))
}

The output will be:

6161047830682206208

It looks like that when float64 change to uint64, the fraction is discarded.

2

There are 2 answers

0
icza On BEST ANSWER

The problem here is the representation of constants and floating point numbers.

Constants are represented in arbitrary precision. Floating point numbers are represented using the IEEE 754 standard.

Spec: Constants:

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

Spec: Numeric types:

float64     the set of all IEEE-754 64-bit floating-point numbers

In IEEE 754 the double precision which is using 64 bits (float64 in Go) 53 bits are used to store the digits. This means the max digits (max number) that can be represented is the number of digits of 2<<52 which is (1 bit is for sign):

2<<52        : 9007199254740992
Your constant: 6161047830682206209

15.95 digits to be precise (16 digits, but not all values that you can describe with 16 digits, only up to 9007199254740992).

The integer constant you try to put into a variable of type float64 simply does not fit into 52 bits so it has to be rounded and digits (or bits) will be cut off (lost).

You can verify this by printing the original n float64 number:

var n float64 = 6161047830682206209
fmt.Printf("%f\n", n)
fmt.Printf("%d\n", uint64(n))

Output:

6161047830682206208.000000
6161047830682206208

The problem is not with conversion, the problem is that the float64 value you try to convert is already not equal to the constant you tried to assign to it.

Just for curiosity:

Try the same with a much bigger number: +500 compared to the first const:

n = 6161047830682206709 // +500 compared to first!
fmt.Printf("%f\n", n2)
fmt.Printf("%d\n", uint64(n2))

Output still the same (the last digits / bits are cut off, +500 included!):

6161047830682206208.000000
6161047830682206208

Try a smaller number whose digits can be represented precisely using 52 bits (less than ~16 digits):

n = 7830682206209
fmt.Printf("%f\n", n)
fmt.Printf("%d\n", uint64(n))

Output:

7830682206209.000000
7830682206209

Try it on the Go Playground.

0
AJR On

The problem is not with the conversion but that the number is too large an integer to be stored exactly as a float64. The assignment to n results in some loss of significance.

If you think about it, your number (about 6e18) is very close to the largest uint64 (2^64-1 or about 18e18). A uint64 uses all of it's 64 bits to store an integer but a float64 must use some of its 64 bits to store an exponent so it's going to have less bits to remember the mantissa.

In summary, if you assign a uint64 (or integer constant) larger than about 10^15 to a float64 and back to a uint64 you will get a close, but probably not exactly the same, value.

[BTW Icza's answer is good and correct. I hope this answer is simple summary.]