patrick-fitzgerald
patrick-fitzgerald

Reputation: 2669

Dealing with floating point number precision in Go arithmetic?

I'm interested in a way to accurately subtract 2 float's in Go.

I've tried to use the math/big library but I can't get an accurate result.

I've used the big.js library in Javascript which solves this problem. Is there a similar library/method for Go arithmetic?

    package main

    import (
        "fmt"
        "math/big"
    )

    func main() {
        const prec = 200
        a := new(big.Float).SetPrec(prec).SetFloat64(5000.0)
        b := new(big.Float).SetPrec(prec).SetFloat64(4000.30)
        result := new(big.Float).Sub(a, b)
        fmt.Println(result)
    }
    Result: 999.6999999999998181010596454143524169921875

https://play.golang.org/p/vomAr87Xln

Upvotes: 4

Views: 17497

Answers (3)

GoForth
GoForth

Reputation: 656

The accepted answer is not accurate. I just ran into this issue and after experimenting with multiple float values and operations I have concluded that you absolutely cannot rely on float precision like this in terms of integer digits or digits after the decimal point. I was able to break the consistency no matter my approach using either addition, subtraction, or multiplication. This is true regardless of whether you use float64 type or big.Float type. You simply will not have consistent accuracy to a large number of integer or decimal places.

For this reason, API's that deal with large precision numbers, such as Etherscan always return integers in string form and you MUST do the same. You will have to rely on the big.Int type exclusively and then display the desired decimal value at the very end of your process (i.e. when printing to console or displaying on a browser).

Again, when printing you cannot do mathematical operations to display the decimal point correctly as this re-introduces the float issue either in Go or in browser-side JavaScript. Therefore, when printing you must manipulate the string and manually insert the decimal point at the proper place within that string. You must avoid the float type at all costs at all parts of your stack.

Upvotes: 2

dirk
dirk

Reputation: 502

Maybe try github.com/shopspring/decimal.

It calls itself a package for "arbitrary-precision fixed-point decimal numbers in go".

NOTE: can "only" represent numbers with a maximum of 2^31 digits after the decimal point.

Upvotes: 2

peterSO
peterSO

Reputation: 166588

Package big

import "math/big"

func (*Float) String

func (x *Float) String() string

String formats x like x.Text('g', 10). (String must be called explicitly, Float.Format does not support %s verb.)

Use string input and round the output, for example,

package main

import (
    "fmt"
    "math/big"
)

func main() {
    const prec = 200
    a, _ := new(big.Float).SetPrec(prec).SetString("5000")
    b, _ := new(big.Float).SetPrec(prec).SetString("4000.30")
    result := new(big.Float).Sub(a, b)
    fmt.Println(result)
    fmt.Println(result.String())
}

Output:

999.6999999999999999999999999999999999999999999999999999999995
999.7

For decimal, by definition, binary floating-point numbers are an approximation. For example, the decimal number 0.1 cannot be represented exactly, it is approximately 1.10011001100110011001101 * (2**(-4)).

You are already used to this sort of thing since you know about repeating decimals, an approximation for rational numbers: 1 / 3 = .333... and 3227 / 555 = 5.8144144144....

See What Every Computer Scientist Should Know About Floating-Point Arithmetic.

Upvotes: 14

Related Questions