-2

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.

peterSO
  • 158,998
  • 31
  • 281
  • 276
shizend
  • 57
  • 8
  • What is the result without using floor? – Fogmeister Sep 27 '19 at 04:39
  • 2
    *"Floor returns the greatest integer value **less than or equal** to x."* i.e. the result 179 from flooring 179.9 *is* correct (https://play.golang.com/p/gPPLz-nQIzW). If you want 180 use round or ceil instead. – mkopriva Sep 27 '19 at 04:48
  • But when you do the math with a [calculator](https://www.google.com/search?q=1980+*+0.1+%2F+1.1) the value should be 180. How can do it correctly? – shizend Sep 27 '19 at 04:50
  • In that google search you're not employing floor, it is probably rounded by default. – mkopriva Sep 27 '19 at 04:53
  • @Fogmeister result without floor is 179.999. I think it should be 180 – shizend Sep 27 '19 at 04:56
  • @shizend floor, round, and ceil are 3 different things. In your question you state you want to "round down/truncate", which is floor. So your go code is correct. In your google calculator attempt you are not using floor, you're not specifying anywhere how the value should be rounded, so, by default, i assume, the calculator uses "round" which is *not* "floor". – mkopriva Sep 27 '19 at 04:58
  • @mkopriva 1980 * 0.1 = 198 and 198 / 1.1 is 180 (without remainder) But i received 179.999... – shizend Sep 27 '19 at 05:00
  • 3
    Ah, this is due to footing point maths and rounding errors. You are right that the actual result should be 180 but there have been rounding errors in the floating point operations causing it to be 179.999. Dividing by 1.1 is the same as multiplying by 0.90909090... which is causing your issue here. You can google “golang fix floating point errors” or something. I’ll try to find something too. – Fogmeister Sep 27 '19 at 05:00
  • @Fogmeister yah, i have found some posts talk about how the float number works in C, python ... but have no idea how to fix it now. My temporary solution is using float32 to remove the epsilon ```math.Floor(float64(float32(1980) * 0.1 / 1.1))``` – shizend Sep 27 '19 at 05:05
  • 2
    @shizend you know what floor does and you know the value before floor is 179.999 yet you still named the question "Incorrect floor number in golang". Regardless you should not use floats for monetary values precisely because they are imprecise. – mkopriva Sep 27 '19 at 05:07
  • 3
    Floating-point numbers are imprecise: https://stackoverflow.com/questions/58088633/how-should-we-calc-money-decimal-big-float/58088873#58088873 – peterSO Sep 27 '19 at 05:08
  • @mkopriva thanks for your advise. I will rename the title. – shizend Sep 27 '19 at 05:10
  • @mkopriva “and you know the value before floor is 179.999” it isn’t though. Do it with pen and paper. The result of this equation is the integer 180. There is nothing wrong with the question. There is a slight misunderstanding of what is happening but there are no rules against that. You also misunderstood the problem and tried to answer in the wrong way. That isn’t the OP’s fault. – Fogmeister Sep 27 '19 at 05:11
  • @Fogmeister I know now that I've misunderstood the problem, but given the original title I wouldn't claim that it isn't OP's fault, at least not not entirely. – mkopriva Sep 27 '19 at 05:16
  • @mkopriva as you said float64 is are imprecise. Which data type should i use for monetary values? – shizend Sep 27 '19 at 05:17
  • 1
    @shizend using int64 as the amount in cents is a common approach. – mkopriva Sep 27 '19 at 05:17
  • 1
    See https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html and https://floating-point-gui.de/ – torek Sep 27 '19 at 05:34
  • Possible duplicate of https://stackoverflow.com/q/21895756/13860 – Jonathan Hall Sep 27 '19 at 08:22
  • How can i mark this question as resolved? I think i have enough information to working on it. Thank guys – shizend Sep 27 '19 at 09:31
  • @shizend just up vote and click the check mark on the correct answer. That will mark it as resolved. – Fogmeister Sep 27 '19 at 13:13

2 Answers2

3

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

peterSO
  • 158,998
  • 31
  • 281
  • 276
  • Also an excellent answer that works around the issue by avoiding floating point for money calculations. – Fogmeister Sep 27 '19 at 13:16
  • Relevant (but only slightly since Go specifies binary floating point), there is an IEEE decimal floating point standard. See https://en.wikipedia.org/wiki/Decimal_floating_point – torek Sep 27 '19 at 23:12
  • @torek: That is Mike Cowlishaw's project: [General Decimal Arithmetic](http://speleotrove.com/decimal/). – peterSO Sep 28 '19 at 00:51
  • I've been playing with decimal arithmetic (off and on) for a while: I wrote some experimental money-value handling stuff in Python back in the 2.x days with `decimal.Decimal` and saw the IBM work. Wasn't keeping up to date though; that link above is nice. – torek Sep 28 '19 at 01:03
  • @torek: Unfortunately, Intel and othere have shown no interest in a hardware implementation. – peterSO Sep 28 '19 at 01:06
  • @torek: Go is ready and waiting for you! [proposal: spec: add decimal float types (IEEE 754-2008) #19787](https://github.com/golang/go/issues/19787) – peterSO Sep 28 '19 at 01:10
2

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

wasmup
  • 14,541
  • 6
  • 42
  • 58
  • 1
    The problem is not using floor. The problem is that the answer to the question 1980 * 0.1 / 1.1 is 180. The result here is not due to using floor it is due to floating point errors. – Fogmeister Sep 27 '19 at 05:06
  • @Fogmeister: No, `salePrice1` is 179.99999999999997 not 180.0 – wasmup Sep 27 '19 at 05:14
  • 1
    Only because of floating point errors. Do it with pen and paper. You will see that it should be 180. Also... I wasn’t a silent downvoter. I added a comment. And you’re still wrong. `1980 * 0.1 = 198`. `198 / 1.1 = 180`. The fact that the code is coming out with 179.999... is due to a floating point error. – Fogmeister Sep 27 '19 at 05:15
  • @A.R: Your answer is wrong: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). – peterSO Sep 27 '19 at 08:45
  • @peterSO: Could you please elaborate, which part is wrong? this is Golang code `var st float64 = 1980 var salePrice1 = st * 0.1 / 1.1 fmt.Println(salePrice1) // 179.99999999999997` not mine. – wasmup Sep 27 '19 at 08:52
  • @peterSO: and please provide your correct answer as a new one. – wasmup Sep 27 '19 at 09:02
  • @A.R: It is not an answer because you didn't answer the question. The question is obviously about money, which uses precise decimal arithmetic. The correct answer is 180. – peterSO Sep 27 '19 at 13:14