floating point problem

1,720 views
Skip to first unread message

GGGO

unread,
Feb 3, 2012, 7:35:55 PM2/3/12
to golang-nuts
package main

import "fmt"

func main() {
x := 0.07
x1 := 0.07
x2 := 0.07
total := x+x1+x2

tax := total*0.05

final := total+tax

fmt.Println(total) // print 0.21000000000000002, I expect 0.21
fmt.Println(tax) // print 0.010500000000000002, I expect 0.0105
fmt.Println(final) // print 0.22050000000000003, I expect 0.2205
}

I really don't get it, what's happens here ? Results are wrong.

Thanks !

Nigel Tao

unread,
Feb 3, 2012, 7:39:40 PM2/3/12
to GGGO, golang-nuts
On 4 February 2012 11:35, GGGO <ggco...@gmail.com> wrote:
> I really don't get it, what's happens here ? Results are wrong.

http://floating-point-gui.de/

John Asmuth

unread,
Feb 3, 2012, 7:40:42 PM2/3/12
to golan...@googlegroups.com
Now you understand why the whole fixed-point decimal thread came about :)

Dave Cheney

unread,
Feb 3, 2012, 7:46:46 PM2/3/12
to GGGO, golang-nuts
You shouldn't be using floats to represent money, for the exact reason you have discovered.

Sent from my iPhone

GGGO

unread,
Feb 3, 2012, 7:57:14 PM2/3/12
to golang-nuts
Ok ! Thank you very much !
What do you advise me to use when working with money ?
I would like to have the possibility to round my tax with 2 decimals,
is it possible with functions in Math package ?
I come from PHP, I never encounter this problem before, but I would
like to find a solution in go.

Thanks.

On Feb 3, 7:46 pm, Dave Cheney <d...@cheney.net> wrote:
> You shouldn't be using floats to represent money, for the exact reason you have discovered.
>
> Sent from my iPhone
>

John Asmuth

unread,
Feb 3, 2012, 8:00:15 PM2/3/12
to golan...@googlegroups.com
the math package is for float64s.

You should probably just use a uint32 (or a uint64 if you're rich), and have it count pennies.

GGGO

unread,
Feb 3, 2012, 9:15:19 PM2/3/12
to golang-nuts
I can't change my schema's database(my data is DECIMAL(10,4)), so I
patch it with this :
tax := total*0.05
taxstring := fmt.Sprintf("%.2f",tax) // and it round for me here
tax,_ = strconv.ParseFloat(taxstring,64) // I don't know if it's very
very dangerous to ignore error returned here, but my data is supposed
to be always well formated.

I apply this on all float64 value.
Maybe it could result performance problem but it works for rounding
and keep decimal to what you want.
Any feedback on this ?

Thanks.

John Asmuth

unread,
Feb 3, 2012, 9:49:39 PM2/3/12
to golan...@googlegroups.com
taxstring := fmt.Sprintf("%.2f",tax) // and it round for me here 

I don't believe it rounds, here; I think it truncates. So, 1.1999999999999999998 becomes 1.19 instead of 1.2.

Is the issue that you need to convert to and from a string conveniently?

type Cents int64

func ParseCentsFromDollars(dollars string) {
  // too lazy to write this in this post
}
func (c Cents) DollarsString() string {
  return fmt.Sprintf("%d.%d2", c / 100, c % 100)
}

I might have my printf verb wrong - the %d2 is supposed to be a 0-padded two digit number.

GGGO

unread,
Feb 3, 2012, 10:46:13 PM2/3/12
to golang-nuts
It rounds,
tax := 1.1999999999999999998
taxstring := fmt.Sprintf("%.2f",tax) // print 1.20
fmt.Println(taxstring)

The doc doesn't say it, but fmt.Printf or fmt.Sprintf round if use
with a format like %.2f (2 decimals)

My main problem was to calculate my first tax, round it before apply
another tax on my first result.
I couldn't round properly with a floating value like
0.010500000000000002, it's why I need fixed-value. (of course I could
make a mathematical function to parse it, but it forces me to control
how many decimal I suppose to have )

My hope was to use my data from database without formatted them before
use.
For another implementation, I will use Cents, or maybe in future
release of Go will be a Decimal type :-D

I like your DollarsString method, fmt package so powerfull.

Thanks.

John Asmuth

unread,
Feb 3, 2012, 11:04:18 PM2/3/12
to golan...@googlegroups.com
On Friday, February 3, 2012 9:49:39 PM UTC-5, John Asmuth wrote:
  return fmt.Sprintf("%d.%d2", c / 100, c % 100)

The correct verb would be %02d, just in case anyone is watching. 

fmt.Sprintf("%d.%02d", c / 100, c % 100)

Hotei

unread,
Feb 3, 2012, 10:03:51 PM2/3/12
to golang-nuts


On Feb 3, 9:15 pm, GGGO <ggcod...@gmail.com> wrote:
> I can't change my schema's database(my data is DECIMAL(10,4)), so I
> patch it with this :
> tax := total*0.05
> taxstring := fmt.Sprintf("%.2f",tax) // and it round for me here
> tax,_ = strconv.ParseFloat(taxstring,64) // I don't know if it's very
> very dangerous to ignore error returned here, but my data is supposed
> to be always well formated.
>
> I apply this on all float64 value.
> Maybe it could result performance problem but it works for rounding
> and keep decimal to what you want.
> Any feedback on this ?

Feedback - well - it depends on if you're working for a bank or not.
If you are, the advice to use int64 is worth considering. If it's you
own money and you don't mind the rounding errors you will get now and
then I guess your method would be fine. For instance what's the tax
on $0.39 ? In my state it would still be 1 cent, not the rounded 2.
If you're OCD level is up there then you could always use a lookup
table with 100 entries and fill in the exact values for the tax
charged. (Some places with really weird tax rates, like 6.35% this
might be the only reasonable way to know you're getting it right - ie
use the paper tables the state provides to populate your lookup
function and you can stop worrying, plus it's probably a few
nanoseconds faster ;-)

Hotei


tux21b

unread,
Feb 3, 2012, 11:51:35 PM2/3/12
to golan...@googlegroups.com
You should probably just use a uint32 (or a uint64 if you're rich), and have it count pennies.

Why do programmers hate floating point arithmetic so much? float is probably perfectly fine
here.

It's true that you might get lots of rounding errors when you use floating point arithmetic, but
the relative error is less than 1.1e-16 per operation. So unless you are planning to do a lot
of calculations or dealing with really huge/small values, float64 should be perfectly fine; for
nearly all applications involving money, the absolute error will stay below 10e-2, so you will
always get exact results when you print 2 decimal places only.

I don't like the idea of using uints for concurrency. First, you might easily forget to divide
by 100 while doing your math and second, once your numbers are getting big, the uint
will wrap around and might loose 2^64 instantly. With floating point values on the other
hand, the error is always relative and extremely small compared to the amount. But both
data types are quite large, so I'm quite sure that you wont exceed either.

And when you really have to do a lot of calculations, than you can always estimate the
absolute error and switch to more complex formulas which aren't that prone to rounding
errors.

-christoph

John Asmuth

unread,
Feb 4, 2012, 12:00:29 AM2/4/12
to golan...@googlegroups.com
Call it a love of scaleable code.

spir

unread,
Feb 4, 2012, 8:42:33 AM2/4/12
to golan...@googlegroups.com
On 02/04/2012 05:51 AM, tux21b wrote:
> Why do programmers hate floating point arithmetic so much? float
> is probably perfectly fine
> here.

The issue is not with a floating point format, but base 2 exponents
instead of base 10 (or a multiple of 10).

Denis

GGGO

unread,
Feb 4, 2012, 8:56:10 AM2/4/12
to golang-nuts
How do programmers store they data with a decimal in the database if
data type has to be a integer ?
It forces to have a second column to store decimal, no ? So how banks
do ! o_0

Thanks.

tux21b

unread,
Feb 4, 2012, 9:28:48 AM2/4/12
to golan...@googlegroups.com
On Saturday, February 4, 2012 2:42:33 PM UTC+1, spir wrote:

The issue is not with a floating point format, but base 2 exponents
instead of base 10 (or a multiple of 10).

I know. And that's also the reason why you can't store some decimal values exactly. But the
the conversation (and the error you get during the operations) is a) relative and b) negligible
small (< 1.1e16). So don't bother with that.

Also, it's normally much easier to prove that the absolute error is always below 1e-2 (which
means that all printed amounts will be exact) than dealing with ints (and uint is also a bad
choice imho, since you can't detect programming errors which might lead to negative values).

-christoph

Russ Cox

unread,
Feb 7, 2012, 5:06:30 PM2/7/12
to golan...@googlegroups.com
On Fri, Feb 3, 2012 at 21:49, John Asmuth <jas...@gmail.com> wrote:
>> taxstring := fmt.Sprintf("%.2f",tax) // and it round for me here
>
>
> I don't believe it rounds, here; I think it truncates. So,
> 1.1999999999999999998 becomes 1.19 instead of 1.2.

It rounds correctly.

Reply all
Reply to author
Forward
0 new messages