float exactness

183 views
Skip to first unread message

Jack Li

unread,
Apr 9, 2022, 1:56:58 PM4/9/22
to golang-nuts
Hi group,

1. 
Is there a package can do exact float operations, like Decimal in Python? For example:

x := 0.1; y := 0.2; z := x + y;

z will be exact 0.3

2.
Why literal operation is exact, variable is not?

fmt.Println(0.1 + 0.2) // 0.3 exactly

fmt.Println(x + y) // 0.30000000000000004


Thanks


func main() {
x := 0.1
y := 0.2
fmt.Println("x + y  :", x   + y)
fmt.Println("literal:", 0.1 + 0.2)

x = 0.1
y = 0.35
fmt.Println("x + y  :", x   + y)
fmt.Println("literal:", 0.1 + 0.35)
}

/*
$ go build && ./main
x + y  : 0.30000000000000004
literal: 0.3
x + y  : 0.44999999999999996
literal: 0.45
*/


// Python:

// >>> 0.1 + 0.2
// 0.30000000000000004

// >>> float(Decimal('0.1') + Decimal('0.2'))
// 0.3
// >>>

// >>> 0.35 + 0.1
// 0.44999999999999996

// >>> float(Decimal('0.35') + Decimal('0.1'))
// 0.45
// >>>

Robert Engels

unread,
Apr 9, 2022, 2:23:23 PM4/9/22
to Jack Li, golang-nuts
There are several. 


As to why, read up on numerical analysis. It’s an interesting topic. 

On Apr 9, 2022, at 8:56 AM, 'Jack Li' via golang-nuts <golan...@googlegroups.com> wrote:


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/tencent_327903B1334351F91FC17D18F2C6E1937508%40qq.com.

Brian Hatfield

unread,
Apr 9, 2022, 2:46:39 PM4/9/22
to Jack Li, golang-nuts
Here's a more general explanation of why floating point operations didn't do what you expected: https://floating-point-gui.de/basic/

Jack Li

unread,
Apr 9, 2022, 2:53:14 PM4/9/22
to Robert Engels, golang-nuts
Thanks Robert,

It is great!

a, _ := decimal.NewFromString("0.1")
b, _ := decimal.NewFromString("0.2")
c := a.Add(b)
fmt.Println("decimal:", c)

a = decimal.NewFromFloat(0.1)
b = decimal.NewFromFloat(0.2)
c = a.Add(b)
fmt.Println("decimal:", c)

a, _ = decimal.NewFromString("0.1")
b, _ = decimal.NewFromString("0.35")
c = a.Add(b)
fmt.Println("decimal:", c)

a = decimal.NewFromFloat(0.1)
b = decimal.NewFromFloat(0.35)
c = a.Add(b)
fmt.Println("decimal:", c)

/*
$ go build && ./main 
decimal: 0.3
decimal: 0.3
decimal: 0.45
decimal: 0.45
*/


------------------ Original ------------------
From: "Robert Engels"<ren...@ix.netcom.com>;
Date: 2022年4月9日(星期六) 晚上10:22
To: "Jack Li"<ljh...@qq.com>;
Cc: "golang-nuts"<golan...@googlegroups.com>;
Subject: Re: [go-nuts] float exactness
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/A7E20597-1DED-4666-BAFA-7F5F79F43A0E%40ix.netcom.com.

wagner riffel

unread,
Apr 10, 2022, 9:16:21 PM4/10/22
to Jack Li, golang-nuts
On Sat Apr 9, 2022 at 3:56 PM CEST, 'Jack Li' via golang-nuts wrote:
> Why literal operation is exact, variable is not?
>
> fmt.Println(0.1 + 0.2) // 0.3 exactly
> fmt.Println(x + y) // 0.30000000000000004
>

Both aren't exact because floats can't represent 0.3 exactly, they
differ because literals and constants expressions have arbitrary
precision (http://golang.org/ref/spec/#Constants), so "0.1" and "0.2"
in "0.1 + 0.2" are exact, then when the exact "0.3" result is
converted to a float64 it becomes the closest possible, You can check
this if you ask to print with more precision

x, y := 0.1, 0.2
fmt.Printf("%.17f...\n", 0.1+0.2) // 0.29999999999999999...
fmt.Printf("%.17f...\n", x+y) // 0.30000000000000004...

https://go.dev/play/p/2qSfoCZGaD6

-w

Brian Candler

unread,
Apr 11, 2022, 7:04:16 AM4/11/22
to golang-nuts
According to the go spec:
"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"

Hence there's a difference between:
* doing an exact addition of 0.1 + 0.2 , then converting the exact value 0.3 to a float64; versus
* converting 0.1 to float64, converting 0.2 to a float64, and then adding them using float64 precision.

Sam Hughes

unread,
Apr 11, 2022, 9:39:12 AM4/11/22
to golang-nuts
Noting that what @Brian%20Candler just said is bang on, I'll add that if you use `math.Nextafter(y, math.Round(y))` or  `y - math.Nextafter(math.Mod(y, 0.01), math.Round(y))`, you can clean up a poorly represented number. The following is Brian's example, but with those two algorithms presented: https://go.dev/play/p/C8qXVOO1jPY The first one will be faster always.

An alternative is to use an integer with a packed region as integral and an 8-12 bit decimal component. Call it 9 bits. That gives you a denominator for the remainder of 1024. To extract the packed value, you can use bitshift and bitwise and ops as follows: `fmt.Printf("v is %v.%vx", v >> 9, v & 1023)`, depending. If you're intrigued, try playing with different numbers here: https://www.rapidtables.com/convert/number/decimal-to-binary.html 

You should check  "0.2" out at https://baseconvert.com/ieee-754-floating-point to understand what's happening. @bmhad threw a link at you that explains how floating point numbers work, but this lets you observe how specific numbers are stored, and why a specific number you want to store, such as 0.2, is a really, really hard.

After checking that out, you'll understand why the algorithms I suggested help, and also why they might not always arrive at the precision you want. If this concerns you, do remember that any other 

Reply all
Reply to author
Forward
0 new messages