How to store a big float64 in a string without overflow? - go

func main() {
target := 20190201518310870.0
fmt.Println(int64(target))
z3 := big.NewInt(int64(target))
fmt.Println(z3)
}
The result is 20190201518310872
How do I convert it and not make overflow?

The problem is that even your input target number is not equal to the constant you assign to it.
The float64 type uses the double-precision floating-point format (IEEE 754) to store the number, which has finite bits to utilize (64 bits in total, but only 53 bits are used to store the significand). This means it can roughly store ~16 digits, but your input number has 17, so it will be rounded to the nearest representable float64.
If you print target, you will see the exact number that is "transfered" to big.Int:
target := 20190201518310870.0
fmt.Printf("%f\n", target)
Outputs (try it on the Go Playground):
20190201518310872.000000
Note that it works if the input constant "fits" into float64:
target := 20190201518310.0
fmt.Printf("%f\n", target)
fmt.Println(int64(target))
z3 := big.NewInt(int64(target))
fmt.Println(z3)
Outputs (try it on the Go Playground):
20190201518310.000000
20190201518310
20190201518310
If you need to work with big numbers exactly such as 20190201518310870.0, you have to use another type to store it in the first place, e.g. string, big.Int or big.Float, but not float64.
For example:
target := "20190201518310870"
fmt.Println(target)
z3, ok := big.NewInt(0).SetString(target, 10)
fmt.Println(z3, ok)
Output (try it on the Go Playground):
20190201518310870
20190201518310870 true

Related

Golang cannot convert []byte("1575455669.4") to float64 using math.Float64frombits

I have the following code:
x := []byte("1575455669.4")
bits := binary.LittleEndian.Uint64(x)
f := math.Float64frombits(bits)
On calling fmt.Println(f) I expect 1.5754556694e+09. But instead I end up with 1.451098468672448e-47
When I try the same conversion through strconv.ParseFloat(string(x), 64) I get the correct result. What am I doing wrong here?
This:
x := []byte("1575455669.4")
will give you the (UTF-8 encoded) bytes of the "1575455669.4" string. This has nothing to do with the memory representation of the floating point number 1575455669.4 which uses the IEEE 754 standard. But what you do next would assume just that:
bits := binary.LittleEndian.Uint64(x)
f := math.Float64frombits(bits)
You have a number given as its base-10 string representation, you have to use strconv.ParseFloat(string(x), 64) to parse it and have it as a float64.

How can I truncate float64 number to a particular precision?

I want to truncate 1.234567 into a 3-fraction digit floating point number, but the result is not what I want.
E.g: 1.234567 => 1.234
package main
import (
"strconv"
"fmt"
)
func main() {
f := 1.234567
fmt.Println(strconv.FormatFloat(f, 'f', 3, 64)) //1.235
fmt.Printf("%.3f", f) //1.235
}
Can anyone tell me how to do this in Go?
The naive way (not always correct)
For truncation, we could take advantage of math.Trunc() which throws away the fraction digits. This is not exactly what we want, we want to keep some fraction digits. So in order to achieve what we want, we may first multiply the input by a power of 10 to shift the wanted fraction digits to the "integer" part, and after truncation (calling math.Trunc() which will throw away the remaining fraction digits), we can divide by the same power of 10 we multiplied in the beginning:
f2 := math.Trunc(f*1000) / 1000
Wrapping this into a function:
func truncateNaive(f float64, unit float64) float64 {
return math.Trunc(f/unit) * unit
}
Testing it:
f := 1.234567
f2 := truncateNaive(f, 0.001)
fmt.Printf("%.10f\n", f2)
Output:
1.2340000000
So far so good, but note that we perform arithmetic operations inside truncateNaive() which may result in unwanted roundings, which could alter the output of the function.
For example, if the input is 0.299999999999999988897769753748434595763683319091796875 (it's representable by a float64 value exactly, see proof), the output should be 0.2999000000, but it will be something else:
f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncateNaive(f, 0.001)
fmt.Printf("%.10f\n", f2)
Output:
0.3000000000
Try these on the Go Playground.
This wrong output is probably not acceptable in most cases (except if you look at it from a way that the input is very close to 0.3–difference is less than 10-16–to which the output is 0.3...).
Using big.Float
To properly truncate all valid float64 values, the intermediate operations must be precise. To achieve that, using a single float64 is insufficient. There are ways to split the input into 2 float64 values and perform operations on them (so precision is not lost) which would be more efficient, or we could use a more convenient way, big.Float which can be of arbitrary precision.
Here's the "transcript" of the above truncateNaive() function using big.Float:
func truncate(f float64, unit float64) float64 {
bf := big.NewFloat(0).SetPrec(1000).SetFloat64(f)
bu := big.NewFloat(0).SetPrec(1000).SetFloat64(unit)
bf.Quo(bf, bu)
// Truncate:
i := big.NewInt(0)
bf.Int(i)
bf.SetInt(i)
f, _ = bf.Mul(bf, bu).Float64()
return f
}
Testing it:
f := 1.234567
f2 := truncate(f, 0.001)
fmt.Printf("%.10f\n", f2)
f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncate(f, 0.001)
fmt.Printf("%.10f\n", f2)
Output is now valid (try it on the Go Playground):
1.2340000000
0.2990000000
You need to truncate decimals manually, either on string level or with math.Floor like https://play.golang.org/p/UP2gFx2iFru.

Rounding value half-up to 2 decimal places in Go

Rounding positive value (example here: 1.015) half-up to 2 decimal places using math.Round() in Go:
fmt.Println(math.Round(1.015*100) / 100)
Go Playground
I got: 1.02. That's correct.
But when I employed a function to do the same job:
func RoundHalfUp(x float64) float64 {
return math.Round(x*100) / 100
}
Go Playground
I got 1.01.
What's wrong with the RoundHalfUp function?
The Go Programming Language
Specification
Constants
Numeric constants represent exact values of arbitrary precision and do
not overflow.
Implementation restriction: Although numeric constants have arbitrary
precision in the language, a compiler may implement them using an
internal representation with limited precision. That said, every
implementation must:
Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed
binary exponent of at least 16 bits.
Round to the nearest representable constant if unable to represent a floating-point or complex constant due to limits on precision.
These requirements apply both to literal constants and to the result
of evaluating constant expressions.
Constant expressions
Constant expressions may contain only constant operands and are
evaluated at compile time.
Constant expressions are always evaluated exactly; intermediate values
and the constants themselves may require precision significantly
larger than supported by any predeclared type in the language.
Implementation restriction: A compiler may use rounding while
computing untyped floating-point or complex constant expressions; see
the implementation restriction in the section on constants. This
rounding may cause a floating-point constant expression to be invalid
in an integer context, even if it would be integral when calculated
using infinite precision, and vice versa.
Implement the RoundHalfUp function like the Go compiler does for math.Round(1.015*100) / 100. 1.015*100 is a untyped floating-point constant expression. Use the math/big package with at least 256 bits of precision. Go float64 (IEEE-754 64-bit floating-point) has 53 bits of precision.
For example, with 256 bits of precision (constant expression),
package main
import (
"fmt"
"math"
"math/big"
)
func RoundHalfUp(x string) float64 {
// math.Round(x*100) / 100
xf, _, err := big.ParseFloat(x, 10, 256, big.ToNearestEven)
if err != nil {
panic(err)
}
xf100, _ := new(big.Float).Mul(xf, big.NewFloat(100)).Float64()
return math.Round(xf100) / float64(100)
}
func main() {
fmt.Println(RoundHalfUp("1.015"))
}
Playground: https://play.golang.org/p/uqtYwP4o22B
Output:
1.02
If we only use 53 bits of precision (float64):
xf, _, err := big.ParseFloat(x, 10, 53, big.ToNearestEven)
Playground: https://play.golang.org/p/ejz-wkuycaU
Output:
1.01

Strange loss of precision multiplying big.Float

If you parse a string into a big.Float like f.SetString("0.001"), then multiply it, I'm seeing a loss of precision. If I use f.SetFloat64(0.001), I don't lose precision. Even doing a strconv.ParseFloat("0.001", 64), then calling f.SetFloat() works.
Full example of what I'm seeing here:
https://play.golang.org/p/_AyTHJJBUeL
Expanded from this question: https://stackoverflow.com/a/47546136/105562
The difference in output is due to imprecise representation of base 10 floating point numbers in float64 (IEEE-754 format) and the default precision and rounding of big.Float.
See this simple code to verify:
fmt.Printf("%.30f\n", 0.001)
f, ok := new(big.Float).SetString("0.001")
fmt.Println(f.Prec(), ok)
Output of the above (try it on the Go Playground):
0.001000000000000000020816681712
64 true
So what we see is that the float64 value 0.001 is not exactly 0.001, and the default precision of big.Float is 64.
If you increase the precision of the number you set via a string value, you will see the same output:
s := "0.001"
f := new(big.Float)
f.SetPrec(100)
f.SetString(s)
fmt.Println(s)
fmt.Println(BigFloatToBigInt(f))
Now output will also be the same (try it on the Go Playground):
0.001
1000000000000000

How to parse long hexadecimal string into uint

I'm pulling in data that is in long hexadecimal string form which I need to convert into decimal notation, truncate 18 decimal places, and then serve up in JSON.
For example I may have the hex string:
"0x00000000000000000000000000000000000000000000d3c21bcecceda1000000"
At first I was attempting to use ParseUint(), however since the highest it supports is int64, my number ends up being way too big.
This example after conversion and truncation results in 10^6.
However there are instances where this number can be up to 10^12 (meaning pre truncation 10^30!).
What is the best strategy to attack this?
Use math/big for working with numbers larger than 64 bits.
From the Int.SetString example:
s := "d3c21bcecceda1000000"
i := new(big.Int)
i.SetString(s, 16)
fmt.Println(i)
https://play.golang.org/p/vf31ce93vA
The math/big types also support the encoding.TextMarshaler and fmt.Scanner interfaces.
For example
i := new(big.Int)
fmt.Sscan("0x000000d3c21bcecceda1000000", i)
Or
i := new(big.Int)
fmt.Sscanf("0x000000d3c21bcecceda1000000", "0x%x", i)

Resources