IEEE 754 binary floating-point numbers imprecise for money - go

I have a problem when I use math.Floor with a floating-point variable (round down/truncate the precision part). How can I do it correctly?
package main
import (
"fmt"
"math"
)
func main() {
var st float64 = 1980
var salePrice1 = st * 0.1 / 1.1
fmt.Printf("%T:%v\n", salePrice1, salePrice1) // 179.9999
var salePrice2 = math.Floor(st * 0.1 / 1.1)
fmt.Printf("%T:%v\n", salePrice2, salePrice2) // 179
}
Playground: https://play.golang.org/p/49TjJwwEdEJ
Output:
float64:179.99999999999997
float64:179
I expect the output of 1980 * 0.1 / 1.1 to be 180, but the actual output is 179.

The original question:
Incorrect floor number in golang
I have problem when use Math.Floor with float variable (round
down/truncate the precision part). How can i do it correctly?
package main
import (
"fmt"
"math"
)
func main() {
var st float64 = 1980
var salePrice1 = st * 0.1 / 1.1
fmt.Printf("%T:%v\n", salePrice1, salePrice1)
var salePrice2 = math.Floor(st * 0.1 / 1.1)
fmt.Printf("%T:%v\n", salePrice2, salePrice2)
}
I expect the output of 1980 * 0.1 / 1.1 to be 180, but the actual
output is 179.”
Playground:
Output:
float64:179.99999999999997
float64:179
The XY problem is asking about your attempted solution rather than your actual problem: The XY Problem.
Clearly, this is a money calculation for salePrice1. Money calculations use precise decimal calculations, not imprecise binary floating-point calculations.
For money calculations use integers. For example,
package main
import "fmt"
func main() {
var st int64 = 198000 // $1980.00 as cents
fmt.Printf("%[1]T:%[1]v\n", st)
fmt.Printf("$%d.%02d\n", st/100, st%100)
var n, d int64 = 1, 11
fmt.Printf("%d, %d\n", n, d)
var salePrice1 int64 = (st * n) / d // round down
fmt.Printf("%[1]T:%[1]v\n", salePrice1)
fmt.Printf("$%d.%02d\n", salePrice1/100, salePrice1%100)
var salePrice2 int64 = ((st*n)*10/d + 5) / 10 // round half up
fmt.Printf("%[1]T:%[1]v\n", salePrice2)
fmt.Printf("$%d.%02d\n", salePrice2/100, salePrice2%100)
var salePrice3 int64 = (st*n + (d - 1)) / d // round up
fmt.Printf("%[1]T:%[1]v\n", salePrice1)
fmt.Printf("$%d.%02d\n", salePrice3/100, salePrice3%100)
}
Playground: https://play.golang.org/p/HbqVJUXXR-N
Output:
int64:198000
$1980.00
1, 11
int64:18000
$180.00
int64:18000
$180.00
int64:18000
$180.00
References:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
How should we calc money (decimal, big.Float)
General Decimal Arithmetic

Try this:
st := 1980.0
f := 0.1 / 1.1
salePrice1 := st * f
salePrice2 := math.Floor(salePrice1)
fmt.Println(salePrice2) // 180
It is a big topic:
For accounting systems: the answer is Floating point error mitigation.
(Note: one mitigation technique is to use int64, uint64, or big.Int)
And see:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
https://en.wikipedia.org/wiki/Double-precision_floating-point_format
https://en.wikipedia.org/wiki/IEEE_floating_point
Let's start with:
fmt.Println(1.0 / 3.0) // 0.3333333333333333
IEEE 754 binary representation:
fmt.Printf("%#X\n", math.Float64bits(1.0/3.0)) // 0X3FD5555555555555
IEEE 754 binary representation of 1.1:
fmt.Printf("%#X\n", math.Float64bits(1.1)) // 0X3FF199999999999A
fmt.Printf("%#X\n", math.Float64bits(st*0.1/1.1)) // 0X40667FFFFFFFFFFF
Now, let:
st := 1980.0
f := 0.1 / 1.1
IEEE 754 binary representation of f is:
fmt.Printf("%#X\n", math.Float64bits(f)) // 0X3FB745D1745D1746
And:
salePrice1 := st * f
fmt.Println(salePrice1) // 180
fmt.Printf("%#X\n", math.Float64bits(salePrice1)) // 0X4066800000000000
salePrice2 := math.Floor(salePrice1)
fmt.Printf("%#X\n", math.Float64bits(salePrice2)) // 0X4066800000000000
Working with the floating-point numbers on the computer is not same as with pen and paper (Floating-point calculation errors):
var st float64 = 1980
var salePrice1 = st * 0.1 / 1.1
fmt.Println(salePrice1) // 179.99999999999997
salePrice1 is 179.99999999999997 not 180.0 so integer value less than or equal to 179.99999999999997 is 179:
See documents for func Floor(x float64) float64:
Floor returns the greatest integer value less than or equal to x.
See:
fmt.Println(math.Floor(179.999)) // 179
fmt.Println(math.Floor(179.5 + 0.5)) // 180
fmt.Println(math.Floor(179.999 + 0.5)) // 180
fmt.Println(math.Floor(180.0)) // 180
Some relevant QAs:
Golang floating point precision float32 vs float64
How to change a float64 number to uint64 in a right way?
Golang converting float64 to int error
Is floating point math broken?
Go float comparison
What does "%b" do in fmt.Printf for float64 and what is Min subnormal positive double in float64 in binary format?
Is there any standard library to convert float64 to string with fix width with maximum number of significant digits?
fmt.Printf with width and precision fields in %g behaves unexpectedly
Why is there a difference between floating-point multiplication with literals vs. variables in Go?
Golang Round to Nearest 0.05

Related

How to keep precision for big numbers in golang when converting from float to big.Int

I have an input that could be a very big or a very small float and need to convert it to big.Int, but for some reason, there is some precision loss.
I understand that this should happen for very small numbers, but why does it happen for a big number, and how to avoid it?
https://go.dev/play/p/AySnKAikSRx
All positive integers up to 9007199254740992 can be represented in a float64 without any loss of precision. Anything higher, you run the risk of precision loss, which is happening in your case.
To give a basic idea of why..
Say we're inventing an extremely compact scheme for representing floating point numbers using the following formula:
m.mm * 10^+-e
.. where:
e = exponent, [1-9]
m.mm = mantissa [0.01-9.99]
With this, we can figure out what range of values can be represented:
lowest = 0.01 * 10^-9 = 0.00000000001
highest = 9.99 * 10^9 = 9990000000
So that's a pretty decent range of numbers.
We can represent a fair few positive integers without any difficulty, e.g.
1 = 1.00 * 10^0
2 = 2.00 * 10^0
3 = 3.00 * 10^0
⋮
10 = 1.00 * 10^1
11 = 1.10 * 10^1
12 = 1.20 * 10^1
⋮
100 = 1.00 * 10^2
101 = 1.01 * 10^2
102 = 1.02 * 10^2
⋮
999 = 9.99 * 10^2
The problem starts when we exceed 9.99 * 10^2. It's not an issue to represent 1000:
1000 = 1.00 * 10^3
But how do represent 1001? The next possible value is
1.01 * 10^3 = 1010
Which is +9 loss of precision, so we have to settle on 1.00 * 10^3 with -1 loss of precision.
The above is in essence how this plays out with float64, except in base 2 and with a 52 bit mantissa in play. With all 52 bits set, and then adding one, the value is:
1.0 * 2^53 = 9007199254740992
So all positive integers up to this value can be represented without precision loss. Integers higher than this may incur precision loss - it very much depends on the value.
Now, the value referenced in your Go code:
var x float64 = 827273999999999954
There is no way to represent this exact value as a float64.
package main
import (
"fmt"
)
func main() {
var x float64 = 827273999999999954
fmt.Printf("%f\n", x)
}
yields..
827274000000000000.000000
So essentially precision is lost by the time x is initialized. But when does that occur? If we run..
$ go build -o tmp
$ go tool objdump tmp
And search for TEXT main.main(SB), we can find the instruction:
main.go:10 0x108b654 48b840d5cba322f6a643 MOVQ $0x43a6f622a3cbd540, AX
So 0x43a6f622a3cbd540 is being set into AX - this is our float64 value.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("float: %f\n", math.Float64frombits(0x43a6f622a3cbd540))
}
prints
float: 827274000000000000.000000
So the precision has essentially been lost at compile time (which makes sense). So on the line of code with big.NewFloat(x).Int(nil), the value being passed as x is 827274000000000000.000000
how to avoid it?
With the code you've provided, there is no way.
If you're able to represent the value as an integer..
package main
import (
"fmt"
"math/big"
)
func main() {
var x uint64 = 827273999999999954
bf := (&big.Float{}).SetUint64(x)
fmt.Println(bf)
}
yields
8.27273999999999954e+17
which is the value you're expecting. Or alternatively via a string:
package main
import (
"fmt"
"math/big"
)
func main() {
var x string = "827273999999999954"
bf, ok := (&big.Float{}).SetString(x)
if !ok {
panic("failed to set string")
}
fmt.Println(bf)
}

How can I convert float(-1.0) to int -1 correctly?

my code:
step := 10.0
precision := int(math.Log10(1/step))
fmt.PrintLn(precision)
I want precision == -1 but got 0...
Float to integer conversion truncates, so if your float number is e.g. 0.99, converting it to integer will be 0 and not 1.
If you want to round to an integer, you may simply use math.Round() (which returns float64 so you still need to manually convert to int, but the result will be what you expect):
step := 10.0
precision := int(math.Log10(1 / step))
fmt.Println(precision)
precision = int(math.Round(math.Log10(1 / step)))
fmt.Println(precision)
This will output (try it on the Go Playground):
0
-1
If you want to round to a specific fraction (and not to integer), see Golang Round to Nearest 0.05.

Real numbers in Go [duplicate]

This question already has an answer here:
How to perform division in Go
(1 answer)
Closed 4 years ago.
How to work with real numbers in Go?
For instance:
(627.71/640.26)^(1/30) = 0.999340349 --> correct result
but with Go:
fmt.Print(math.Pow((627.71 / 640.26), (1 / 30))) = 1 --> wrong
Use floating-point (real), not integer, division. For example,
package main
import (
"fmt"
"math"
)
func main() {
fmt.Print(math.Pow((627.71 / 640.26), (1.0 / 30.0)))
}
Playground: https://play.golang.org/p/o7uVw9doaMu
Output:
0.999340348749526
package main
import "fmt"
func main() {
fmt.Println(1 / 30) // integer division
fmt.Println(1.0 / 30.0) // floating-point division
}
Playground: https://play.golang.org/p/VW9vilCC9M8
Output:
0
0.03333333333333333
The Go Programming Language Specification
Integer literals
Floating-point literals
Arithmetic operators
Integer operators
For two integer values x and y, the integer quotient q = x / y and
remainder r = x % y satisfy the following relationships:
x = q*y + r and |r| < |y|
with x / y truncated towards zero ("truncated division").

Go: Converting float64 to int with multiplier

I want to convert a float64 number, let's say it 1.003 to 1003 (integer type). My implementation is simply multiply the float64 with 1000 and cast it to int.
package main
import "fmt"
func main() {
var f float64 = 1.003
fmt.Println(int(f * 1000))
}
But when I run that code, what I got is 1002 not 1003. Because Go automatically stores 1.003 as 1.002999... in the variable. What is the correct approach to do this kind of operation on Golang?
Go spec: Conversions:
Conversions between numeric types
When converting a floating-point number to an integer, the fraction is discarded (truncation towards zero).
So basically when you convert a floating-point number to an integer, only the integer part is kept.
If you just want to avoid errors arising from representing with finite bits, just add 0.5 to the number before converting it to int. No external libraries or function calls (from standard library) required.
Since float -> int conversion is not rounding but keeping the integer part, this will give you the desired result. Taking into consideration both the possible smaller and greater representation:
1002.9999 + 0.5 = 1003.4999; integer part: 1003
1003.0001 + 0.5 = 1003.5001; integer part: 1003
So simply just write:
var f float64 = 1.003
fmt.Println(int(f * 1000 + 0.5))
To wrap this into a function:
func toint(f float64) int {
return int(f + 0.5)
}
// Using it:
fmt.Println(toint(f * 1000))
Try them on the Go Playground.
Note:
Be careful when you apply this in case of negative numbers! For example if you have a value of -1.003, then you probably want the result to be -1003. But if you add 0.5 to it:
-1002.9999 + 0.5 = -1002.4999; integer part: -1002
-1003.0001 + 0.5 = -1002.5001; integer part: -1002
So if you have negative numbers, you have to either:
subtract 0.5 instead of adding it
or add 0.5 but subtract 1 from the result
Incorporating this into our helper function:
func toint(f float64) int {
if f < 0 {
return int(f - 0.5)
}
return int(f + 0.5)
}
As Will mentions, this comes down to how floats are represented on various platforms. Essentially you need to round the float rather than let the default truncating behavior to happen. There's no standard library function for this, probably because there's a lot of possible behavior and it's trivial to implement.
If you knew you'd always have errors of the sort described, where you're slightly below (1299.999999) the value desired (1300.00000) you could use the math library's Ceil function:
f := 1.29999
n := math.Ceil(f*1000)
But if you have different kinds of floating error and want a more general sorting behavior? Use the math library's Modf function to separate the your floating point value by the decimal point:
f := 1.29999
f1,f2 := math.Modf(f*1000)
n := int(f1) // n = 1299
if f2 > .5 {
n++
}
fmt.Println(n)
You can run a slightly more generalized version of this code in the playground yourself.
This is probably likely a problem with floating points in general in most programming languages though some have different implementations than others. I wouldn't go into the intricacies here but most languages usually have a "decimal" approach either as a standard library or a third party library to get finer precision.
For instance, I've found the inf.v0 package largely useful. Underlying the library is a Dec struct that holds the exponents and the integer value. Therefore, it's able to hold 1.003 as 1003 * 10^-3. See below for an example:
package main
import (
"fmt"
"gopkg.in/inf.v0"
)
func main() {
// represents 1003 * 10^-3
someDec := inf.NewDec(1003, 3)
// multiply someDec by 1000 * 10^0
// which translates to 1003 * 10^-3 * 1000 * 10^0
someDec.Mul(someDec, inf.NewDec(1000, 0))
// inf.RoundHalfUp rounds half up in the 0th scale, eg. 0.5 rounds to 1
value, ok := someDec.Round(someDec, 0, inf.RoundHalfUp).Unscaled()
fmt.Println(value, ok)
}
Hope this helps!

golang - ceil function like php?

I want to return the least integer value greater than or equal to integer division. So I used math.ceil, but can not get the value I want.
package main
import (
"fmt"
"math"
)
func main() {
var pagesize int = 10
var length int = 43
d := float64(length / pagesize)
page := int(math.Ceil(d))
fmt.Println(page)
// output 4 not 5
}
http://golang.org/pkg/math/#Ceil
http://play.golang.org/p/asHta1HkO_
What is wrong?
Thanks.
The line
d := float64(length / pagesize)
transforms to float the result of the division. Since the division itself is integer division, it results in 4, so d = 4.0 and math.Ceil(d) is 4.
Replace the line with
d := float64(length) / float64(pagesize)
and you'll have d=4.3 and int(math.Ceil(d))=5.
Avoiding floating point operations (for performance and clarity):
x, y := length, pagesize
q := (x + y - 1) / y;
for x >= 0 and y > 0.
Or to avoid overflow of x+y:
q := 1 + (x - 1) / y
It's the same as the C++ version: Fast ceiling of an integer division in C / C++
Convert length and pagesize to floats before the division:
d := float64(length) / float64(pagesize)
http://play.golang.org/p/FKWeIj7of5
You can check the remainder to see if it should be raised to the next integer.
page := length / pagesize
if length % pagesize > 0 {
page++
}

Resources