Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #73470 > unrolled thread

python 3.44 float addition bug?

Started byFraserL <fraser.long+usenet@NOSPAMgmail.com>
First post2014-06-20 19:57 -0500
Last post2014-06-26 07:53 +0200
Articles 20 on this page of 24 — 15 participants

Back to article view | Back to comp.lang.python


Contents

  python 3.44 float addition bug? FraserL <fraser.long+usenet@NOSPAMgmail.com> - 2014-06-20 19:57 -0500
    Re: python 3.44 float addition bug? FraserL <fraser.long+usenet@gmail.com> - 2014-06-20 20:11 -0500
      Re: python 3.44 float addition bug? Gary Herron <gary.herron@islandtraining.com> - 2014-06-20 18:19 -0700
    Re: python 3.44 float addition bug? Gary Herron <gary.herron@islandtraining.com> - 2014-06-20 18:07 -0700
    Re: python 3.44 float addition bug? Chris Angelico <rosuav@gmail.com> - 2014-06-21 11:33 +1000
    Re: python 3.44 float addition bug? INADA Naoki <songofacandy@gmail.com> - 2014-06-21 10:06 +0900
    Re: python 3.44 float addition bug? Grant Edwards <invalid@invalid.invalid> - 2014-06-21 14:25 +0000
    Re: python 3.44 float addition bug? Ned Deily <nad@acm.org> - 2014-06-21 12:24 -0700
      Re: python 3.44 float addition bug? buck <workitharder@gmail.com> - 2014-06-23 17:55 -0700
        Re: python 3.44 float addition bug? Chris Angelico <rosuav@gmail.com> - 2014-06-24 13:26 +1000
        Re: python 3.44 float addition bug? Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2014-06-24 17:30 +1200
        Re: python 3.44 float addition bug? Steven D'Aprano <steve@pearwood.info> - 2014-06-24 06:34 +0000
    Re: python 3.44 float addition bug? Maciej Dziardziel <fiedzia@gmail.com> - 2014-06-25 14:12 -0700
      Re: python 3.44 float addition bug? Steven D'Aprano <steve@pearwood.info> - 2014-06-26 02:56 +0000
        Re: python 3.44 float addition bug? Chris Angelico <rosuav@gmail.com> - 2014-06-26 13:13 +1000
          Re: python 3.44 float addition bug? Steven D'Aprano <steve@pearwood.info> - 2014-06-26 04:17 +0000
            Re: python 3.44 float addition bug? Chris Angelico <rosuav@gmail.com> - 2014-06-26 14:41 +1000
        Re: python 3.44 float addition bug? Ben Finney <ben@benfinney.id.au> - 2014-06-26 13:39 +1000
          Re: python 3.44 float addition bug? Steven D'Aprano <steve@pearwood.info> - 2014-06-26 09:15 +0000
            Re: python 3.44 float addition bug? Chris Angelico <rosuav@gmail.com> - 2014-06-26 19:38 +1000
              Re: python 3.44 float addition bug? Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-27 02:51 +0000
                Re: python 3.44 float addition bug? Chris Angelico <rosuav@gmail.com> - 2014-06-27 13:24 +1000
                Re: python 3.44 float addition bug? Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-27 10:18 -0600
        Re: python 3.44 float addition bug? Stefan Behnel <stefan_ml@behnel.de> - 2014-06-26 07:53 +0200

Page 1 of 2  [1] 2  Next page →


#73470 — python 3.44 float addition bug?

FromFraserL <fraser.long+usenet@NOSPAMgmail.com>
Date2014-06-20 19:57 -0500
Subjectpython 3.44 float addition bug?
Message-ID<XnsA35313E634BA0fraserlonggmailcom34@216.196.109.145>
I think I found a strange bug in python 3.4.1, 

fresh install of

 https://www.python.org/ftp/python/3.4.1/python-3.4.1.amd64.msi 

and a fresh install of 
https://www.python.org/ftp/python/2.7.7/python-2.7.7.amd64.msi
to compare it to.

#test code
z = 0.01
p = 0.0
for x, y in enumerate(range(1, 20)):
    p += z
    print(p)
#end 


3.4.1 output:

0.01
0.02
0.03
0.04
0.05
0.060000000000000005
0.07
0.08
0.09
0.09999999999999999
0.10999999999999999
0.11999999999999998
0.12999999999999998
0.13999999999999999
0.15
0.16
0.17
0.18000000000000002
0.19000000000000003


2.7.7 output:

0.01
0.02
0.03
0.04
0.05
0.06
0.07
0.08
0.09
0.1
0.11
0.12
0.13
0.14
0.15
0.16
0.17
0.18
0.19


I'm not hugely accustomed to Python, but this seems crazy to me.

[toc] | [next] | [standalone]


#73471

FromFraserL <fraser.long+usenet@gmail.com>
Date2014-06-20 20:11 -0500
Message-ID<XnsA3531656DD168fraserlonggmailcom34@216.196.109.145>
In reply to#73470
Ok I've seen https://docs.python.org/2/tutorial/floatingpoint.html now 
thanks to Yhg1s on #python

I bet you get this kind of thing a lot, sorry :-/


FraserL <fraser.long+usenet@NOSPAMgmail.com> wrote in 
news:XnsA35313E634BA0fraserlonggmailcom34@216.196.109.145:

> I think I found a strange bug in python 3.4.1, 
...
> 
> #test code
> z = 0.01
> p = 0.0
> for x, y in enumerate(range(1, 20)):
>     p += z
>     print(p)
> #end 
> 
> 
> 3.4.1 output:
> 
...
> 0.05
> 0.060000000000000005
> 0.07
...
> 
> 
> I'm not hugely accustomed to Python, but this seems crazy to me.
> 

[toc] | [prev] | [next] | [standalone]


#73472

FromGary Herron <gary.herron@islandtraining.com>
Date2014-06-20 18:19 -0700
Message-ID<mailman.11176.1403313571.18130.python-list@python.org>
In reply to#73471
On 06/20/2014 06:11 PM, FraserL wrote:
> Ok I've seen https://docs.python.org/2/tutorial/floatingpoint.html now
> thanks to Yhg1s on #python
>
> I bet you get this kind of thing a lot, sorry :-/

Yes, often, but it's not a problem. :-)


>
>
> FraserL <fraser.long+usenet@NOSPAMgmail.com> wrote in
> news:XnsA35313E634BA0fraserlonggmailcom34@216.196.109.145:
>
>> I think I found a strange bug in python 3.4.1,
> ...
>> #test code
>> z = 0.01
>> p = 0.0
>> for x, y in enumerate(range(1, 20)):
>>      p += z
>>      print(p)
>> #end
>>
>>
>> 3.4.1 output:
>>
> ...
>> 0.05
>> 0.060000000000000005
>> 0.07
> ...
>>
>> I'm not hugely accustomed to Python, but this seems crazy to me.
>>

[toc] | [prev] | [next] | [standalone]


#73473

FromGary Herron <gary.herron@islandtraining.com>
Date2014-06-20 18:07 -0700
Message-ID<mailman.11175.1403313432.18130.python-list@python.org>
In reply to#73470

On 06/20/2014 05:57 PM, FraserL wrote:
> I think I found a strange bug in python 3.4.1,


No, this is not a bug in Python.  There is a limitation of floating 
point arithmetic in *all* languages, on *all* computers.   Python 3 may 
be the first to let you see this limitation, but it's always been there.

See https://docs.python.org/2/tutorial/floatingpoint.html for more details.

Gary Herron



>
> fresh install of
>
>   https://www.python.org/ftp/python/3.4.1/python-3.4.1.amd64.msi
>
> and a fresh install of
> https://www.python.org/ftp/python/2.7.7/python-2.7.7.amd64.msi
> to compare it to.
>
> #test code
> z = 0.01
> p = 0.0
> for x, y in enumerate(range(1, 20)):
>      p += z
>      print(p)
> #end
>
>
> 3.4.1 output:
>
> 0.01
> 0.02
> 0.03
> 0.04
> 0.05
> 0.060000000000000005
> 0.07
> 0.08
> 0.09
> 0.09999999999999999
> 0.10999999999999999
> 0.11999999999999998
> 0.12999999999999998
> 0.13999999999999999
> 0.15
> 0.16
> 0.17
> 0.18000000000000002
> 0.19000000000000003
>
>
> 2.7.7 output:
>
> 0.01
> 0.02
> 0.03
> 0.04
> 0.05
> 0.06
> 0.07
> 0.08
> 0.09
> 0.1
> 0.11
> 0.12
> 0.13
> 0.14
> 0.15
> 0.16
> 0.17
> 0.18
> 0.19
>
>
> I'm not hugely accustomed to Python, but this seems crazy to me.

[toc] | [prev] | [next] | [standalone]


#73474

FromChris Angelico <rosuav@gmail.com>
Date2014-06-21 11:33 +1000
Message-ID<mailman.11177.1403314412.18130.python-list@python.org>
In reply to#73470
On Sat, Jun 21, 2014 at 10:57 AM, FraserL
<fraser.long+usenet@nospamgmail.com> wrote:
> #test code
> z = 0.01
> p = 0.0
> for x, y in enumerate(range(1, 20)):
>     p += z
>     print(p)
> #end

General tip when you think you've found a bug: Cut out everything that
isn't part of it. In this case, the enumerate has nothing to do with
what you're seeing, which is an artifact of floating-point arithmetic,
so a more classic loop header would simply be:

for i in range(19):

(Or some people will use _ to emphasize that the iterated-over values
are being ignored.)

The smaller you can make your test code, the more likely that people
will be able to see what's going on.

Also, when you're looking at how things print out, consider looking at
two things: the str() and the repr(). Sometimes just "print(p)"
doesn't give you all the info, so you might instead want to write your
loop thus:

z = 0.01
p = 0.0
for i in range(19):
    p += z
    print(str(p) + " -- " + repr(p))

Sometimes you can get extra clues that way, although in this instance
I think you won't.

ChrisA

[toc] | [prev] | [next] | [standalone]


#73475

FromINADA Naoki <songofacandy@gmail.com>
Date2014-06-21 10:06 +0900
Message-ID<mailman.11178.1403315448.18130.python-list@python.org>
In reply to#73470
Read this: https://docs.python.org/3.4/tutorial/floatingpoint.html

On Sat, Jun 21, 2014 at 9:57 AM, FraserL
<fraser.long+usenet@nospamgmail.com> wrote:
> I think I found a strange bug in python 3.4.1,
>
> fresh install of
>
>  https://www.python.org/ftp/python/3.4.1/python-3.4.1.amd64.msi
>
> and a fresh install of
> https://www.python.org/ftp/python/2.7.7/python-2.7.7.amd64.msi
> to compare it to.
>
> #test code
> z = 0.01
> p = 0.0
> for x, y in enumerate(range(1, 20)):
>     p += z
>     print(p)
> #end
>
>
> 3.4.1 output:
>
> 0.01
> 0.02
> 0.03
> 0.04
> 0.05
> 0.060000000000000005
> 0.07
> 0.08
> 0.09
> 0.09999999999999999
> 0.10999999999999999
> 0.11999999999999998
> 0.12999999999999998
> 0.13999999999999999
> 0.15
> 0.16
> 0.17
> 0.18000000000000002
> 0.19000000000000003
>
>
> 2.7.7 output:
>
> 0.01
> 0.02
> 0.03
> 0.04
> 0.05
> 0.06
> 0.07
> 0.08
> 0.09
> 0.1
> 0.11
> 0.12
> 0.13
> 0.14
> 0.15
> 0.16
> 0.17
> 0.18
> 0.19
>
>
> I'm not hugely accustomed to Python, but this seems crazy to me.
> --
> https://mail.python.org/mailman/listinfo/python-list



-- 
INADA Naoki  <songofacandy@gmail.com>

[toc] | [prev] | [next] | [standalone]


#73477

FromGrant Edwards <invalid@invalid.invalid>
Date2014-06-21 14:25 +0000
Message-ID<lo44l7$c36$1@reader1.panix.com>
In reply to#73470
On 2014-06-21, FraserL <fraser.long+usenet@NOSPAMgmail.com> wrote:

> I'm not hugely accustomed to Python, but this seems crazy to me.

Both are producing the same floating point numbers, Python just
changed the way they're printed.  One version doesn't show you all the
digits, the other does.

-- 
Grant

[toc] | [prev] | [next] | [standalone]


#73482

FromNed Deily <nad@acm.org>
Date2014-06-21 12:24 -0700
Message-ID<mailman.11179.1403378678.18130.python-list@python.org>
In reply to#73470
In article 
<CAPTjJmrkPd5K__h9Qg12Q+AraFZVaN6eGUdTmEDGe2ccAqEmRw@mail.gmail.com>,
 Chris Angelico <rosuav@gmail.com> wrote:
> Also, when you're looking at how things print out, consider looking at
> two things: the str() and the repr(). Sometimes just "print(p)"
> doesn't give you all the info, so you might instead want to write your
> loop thus:
> 
> z = 0.01
> p = 0.0
> for i in range(19):
>     p += z
>     print(str(p) + " -- " + repr(p)) 
> Sometimes you can get extra clues that way, although in this instance
> I think you won't.

Actually, I think this is one case where you would get extra clues (or 
extra headscratching) if you run the code with various releases of 
Python.

$ python2.6 b.py
0.01 -- 0.01
0.02 -- 0.02
0.03 -- 0.029999999999999999
0.04 -- 0.040000000000000001
0.05 -- 0.050000000000000003
0.06 -- 0.060000000000000005
0.07 -- 0.070000000000000007
0.08 -- 0.080000000000000002
0.09 -- 0.089999999999999997
0.1 -- 0.099999999999999992
0.11 -- 0.10999999999999999
0.12 -- 0.11999999999999998
0.13 -- 0.12999999999999998
0.14 -- 0.13999999999999999
0.15 -- 0.14999999999999999
0.16 -- 0.16
0.17 -- 0.17000000000000001
0.18 -- 0.18000000000000002
0.19 -- 0.19000000000000003

$ python2.7 b.py
0.01 -- 0.01
0.02 -- 0.02
0.03 -- 0.03
0.04 -- 0.04
0.05 -- 0.05
0.06 -- 0.060000000000000005
0.07 -- 0.07
0.08 -- 0.08
0.09 -- 0.09
0.1 -- 0.09999999999999999
0.11 -- 0.10999999999999999
0.12 -- 0.11999999999999998
0.13 -- 0.12999999999999998
0.14 -- 0.13999999999999999
0.15 -- 0.15
0.16 -- 0.16
0.17 -- 0.17
0.18 -- 0.18000000000000002
0.19 -- 0.19000000000000003

$ python3.4 b.py
0.01 -- 0.01
0.02 -- 0.02
0.03 -- 0.03
0.04 -- 0.04
0.05 -- 0.05
0.060000000000000005 -- 0.060000000000000005
0.07 -- 0.07
0.08 -- 0.08
0.09 -- 0.09
0.09999999999999999 -- 0.09999999999999999
0.10999999999999999 -- 0.10999999999999999
0.11999999999999998 -- 0.11999999999999998
0.12999999999999998 -- 0.12999999999999998
0.13999999999999999 -- 0.13999999999999999
0.15 -- 0.15
0.16 -- 0.16
0.17 -- 0.17
0.18000000000000002 -- 0.18000000000000002
0.19000000000000003 -- 0.19000000000000003

What's going on here is that in Python 2.7 the repr() of floats was 
changed to use the minimum number of digits to accurately roundtrip the 
number under correct rounding.  For compatibility reasons, the str() 
representation was not changed for 2.7.  But in Python 3.2, str() was 
changed to be identical to repr() for floats.  It's important to keep in 
mind that the actual binary values stored in float objects are the same 
across all of these releases; only the representation of them as decimal 
characters varies.

https://docs.python.org/2.7/whatsnew/2.7.html#other-language-changes

http://bugs.python.org/issue9337

-- 
 Ned Deily,
 nad@acm.org

[toc] | [prev] | [next] | [standalone]


#73524

Frombuck <workitharder@gmail.com>
Date2014-06-23 17:55 -0700
Message-ID<f33df2e7-12db-4732-967d-73dee3e71f3a@googlegroups.com>
In reply to#73482
It used to be that the best way to compare floating point numbers while disregarding the inherent epsilon was to use `str(x) == str(y)`. It looks like that workaround doesn't work anymore in 3.4.

What's the recommended way to do this now?

>>> format(.01 + .01 + .01 + .01 + .01 + .01, 'g') == format(.06, 'g')
True


On Saturday, June 21, 2014 12:24:24 PM UTC-7, Ned Deily wrote:
> In article 
> 
> <CAPTjJmrkPd5K__h9Qg12Q+AraFZVaN6eGUdTmEDGe2ccAqEmRw@mail.gmail.com>,
> 
>  Chris Angelico <rosuav@gmail.com> wrote:
> 
> > Also, when you're looking at how things print out, consider looking at
> 
> > two things: the str() and the repr(). Sometimes just "print(p)"
> 
> > doesn't give you all the info, so you might instead want to write your
> 
> > loop thus:
> 
> > 
> 
> > z = 0.01
> 
> > p = 0.0
> 
> > for i in range(19):
> 
> >     p += z
> 
> >     print(str(p) + " -- " + repr(p)) 
> 
> > Sometimes you can get extra clues that way, although in this instance
> 
> > I think you won't.
> 
> 
> 
> Actually, I think this is one case where you would get extra clues (or 
> 
> extra headscratching) if you run the code with various releases of 
> 
> Python.
> 
> 
> 
> $ python2.6 b.py
> 
> 0.01 -- 0.01
> 
> 0.02 -- 0.02
> 
> 0.03 -- 0.029999999999999999
> 
> 0.04 -- 0.040000000000000001
> 
> 0.05 -- 0.050000000000000003
> 
> 0.06 -- 0.060000000000000005
> 
> 0.07 -- 0.070000000000000007
> 
> 0.08 -- 0.080000000000000002
> 
> 0.09 -- 0.089999999999999997
> 
> 0.1 -- 0.099999999999999992
> 
> 0.11 -- 0.10999999999999999
> 
> 0.12 -- 0.11999999999999998
> 
> 0.13 -- 0.12999999999999998
> 
> 0.14 -- 0.13999999999999999
> 
> 0.15 -- 0.14999999999999999
> 
> 0.16 -- 0.16
> 
> 0.17 -- 0.17000000000000001
> 
> 0.18 -- 0.18000000000000002
> 
> 0.19 -- 0.19000000000000003
> 
> 
> 
> $ python2.7 b.py
> 
> 0.01 -- 0.01
> 
> 0.02 -- 0.02
> 
> 0.03 -- 0.03
> 
> 0.04 -- 0.04
> 
> 0.05 -- 0.05
> 
> 0.06 -- 0.060000000000000005
> 
> 0.07 -- 0.07
> 
> 0.08 -- 0.08
> 
> 0.09 -- 0.09
> 
> 0.1 -- 0.09999999999999999
> 
> 0.11 -- 0.10999999999999999
> 
> 0.12 -- 0.11999999999999998
> 
> 0.13 -- 0.12999999999999998
> 
> 0.14 -- 0.13999999999999999
> 
> 0.15 -- 0.15
> 
> 0.16 -- 0.16
> 
> 0.17 -- 0.17
> 
> 0.18 -- 0.18000000000000002
> 
> 0.19 -- 0.19000000000000003
> 
> 
> 
> $ python3.4 b.py
> 
> 0.01 -- 0.01
> 
> 0.02 -- 0.02
> 
> 0.03 -- 0.03
> 
> 0.04 -- 0.04
> 
> 0.05 -- 0.05
> 
> 0.060000000000000005 -- 0.060000000000000005
> 
> 0.07 -- 0.07
> 
> 0.08 -- 0.08
> 
> 0.09 -- 0.09
> 
> 0.09999999999999999 -- 0.09999999999999999
> 
> 0.10999999999999999 -- 0.10999999999999999
> 
> 0.11999999999999998 -- 0.11999999999999998
> 
> 0.12999999999999998 -- 0.12999999999999998
> 
> 0.13999999999999999 -- 0.13999999999999999
> 
> 0.15 -- 0.15
> 
> 0.16 -- 0.16
> 
> 0.17 -- 0.17
> 
> 0.18000000000000002 -- 0.18000000000000002
> 
> 0.19000000000000003 -- 0.19000000000000003
> 
> 
> 
> What's going on here is that in Python 2.7 the repr() of floats was 
> 
> changed to use the minimum number of digits to accurately roundtrip the 
> 
> number under correct rounding.  For compatibility reasons, the str() 
> 
> representation was not changed for 2.7.  But in Python 3.2, str() was 
> 
> changed to be identical to repr() for floats.  It's important to keep in 
> 
> mind that the actual binary values stored in float objects are the same 
> 
> across all of these releases; only the representation of them as decimal 
> 
> characters varies.
> 
> 
> 
> https://docs.python.org/2.7/whatsnew/2.7.html#other-language-changes
> 
> 
> 
> http://bugs.python.org/issue9337
> 
> 
> 
> -- 
> 
>  Ned Deily,
> 
>  nad@acm.org

[toc] | [prev] | [next] | [standalone]


#73528

FromChris Angelico <rosuav@gmail.com>
Date2014-06-24 13:26 +1000
Message-ID<mailman.11208.1403580389.18130.python-list@python.org>
In reply to#73524
On Tue, Jun 24, 2014 at 10:55 AM, buck <workitharder@gmail.com> wrote:
> It used to be that the best way to compare floating point numbers while disregarding the inherent epsilon was to use `str(x) == str(y)`.

Who said that?

ChrisA

[toc] | [prev] | [next] | [standalone]


#73530

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2014-06-24 17:30 +1200
Message-ID<c0sgo1F17rmU1@mid.individual.net>
In reply to#73524
buck wrote:
> What's the recommended way to do this now?
> 
>>>>format(.01 + .01 + .01 + .01 + .01 + .01, 'g') == format(.06, 'g')

There's no recommended way. What you're asking for can't be
done. Whatever trick you come up with, there will be cases
where it doesn't work.

Why do you think you want to compare floats for equality?
The best thing to do will depend on the answer to that.

-- 
Greg

[toc] | [prev] | [next] | [standalone]


#73531

FromSteven D'Aprano <steve@pearwood.info>
Date2014-06-24 06:34 +0000
Message-ID<53a91be3$0$11121$c3e8da3@news.astraweb.com>
In reply to#73524
On Mon, 23 Jun 2014 17:55:50 -0700, buck wrote:

> It used to be that the best way to compare floating point numbers while
> disregarding the inherent epsilon was to use `str(x) == str(y)`. It
> looks like that workaround doesn't work anymore in 3.4.

What inherent epsilon? Can you explain what you mean?


> What's the recommended way to do this now?
> 
>>>> format(.01 + .01 + .01 + .01 + .01 + .01, 'g') == format(.06, 'g')
> True


That's equivalent to doing an implicit round. If all you want to do is 
round your calculations, then explicitly round them:

py> x = sum([0.01]*6)
py> y = 0.06
py> round(x, 12) == round(y, 12)
True

Not that I'm recommending that you do it this way, but an explicit round 
is better than using string formatting.

See also this:

http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#73592

FromMaciej Dziardziel <fiedzia@gmail.com>
Date2014-06-25 14:12 -0700
Message-ID<645be4a7-2e1b-44a4-9c45-9184c6df5518@googlegroups.com>
In reply to#73470
On Saturday, June 21, 2014 1:57:19 AM UTC+1, FraserL wrote:
> I'm not hugely accustomed to Python, but this seems crazy to me.

Floating points values use finite amount of memory, and  cannot accurately represent infinite amount of numbers, they are only approximations. This is limitation of float type and applies to any languages that uses types supported directly by cpu.
To deal with it you can either use decimal.Decimal type that operates using decimal system and saves you from such surprises (but it will be much slower and incompatible with c code that doesn't handle decimals or converts them to floats) or use epsilon for every comparison and rounding/formatting with limited   precision for displaying.

Few more details are here: http://floating-point-gui.de/errors/comparison/

[toc] | [prev] | [next] | [standalone]


#73601

FromSteven D'Aprano <steve@pearwood.info>
Date2014-06-26 02:56 +0000
Message-ID<53ab8bc4$0$11121$c3e8da3@news.astraweb.com>
In reply to#73592
On Wed, 25 Jun 2014 14:12:31 -0700, Maciej Dziardziel wrote:

> Floating points values use finite amount of memory, and  cannot
> accurately represent infinite amount of numbers, they are only
> approximations. This is limitation of float type and applies to any
> languages that uses types supported directly by cpu. To deal with it you
> can either use decimal.Decimal type that operates using decimal system
> and saves you from such surprises

That's a myth. decimal.Decimal *is* a floating point value, and is 
subject to *exactly* the same surprises as binary floats, except for one: 
which Decimal, you can guarantee that any decimal string you enter will 
appear exactly the same (up to the limit of the current precision).

For example:

py> x = Decimal(1)/Decimal(23)
py> x
Decimal('0.04347826086956521739130434783')
py> x*23 == 1
True
py> sum( [x]*23 ) == 1  # Surprise!
False

py> (Decimal(19)/Decimal(17))*Decimal(17) == 19  # Surprise!
False



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#73602

FromChris Angelico <rosuav@gmail.com>
Date2014-06-26 13:13 +1000
Message-ID<mailman.11250.1403752434.18130.python-list@python.org>
In reply to#73601
On Thu, Jun 26, 2014 at 12:56 PM, Steven D'Aprano <steve@pearwood.info> wrote:
> That's a myth. decimal.Decimal *is* a floating point value, and is
> subject to *exactly* the same surprises as binary floats, except for one:
> which Decimal, you can guarantee that any decimal string you enter will
> appear exactly the same (up to the limit of the current precision).

The important difference is that the issues with decimal floats come
where humans are comfortable seeing them. If you divide 1 by 3, you
get 0.333333333 and can understand that adding three of those together
won't quite make 1.0, because you can see that you shortened it. If
you divide 11 by 10, it's not obvious that that repeats.

ChrisA

[toc] | [prev] | [next] | [standalone]


#73605

FromSteven D'Aprano <steve@pearwood.info>
Date2014-06-26 04:17 +0000
Message-ID<53ab9ee6$0$11121$c3e8da3@news.astraweb.com>
In reply to#73602
On Thu, 26 Jun 2014 13:13:45 +1000, Chris Angelico wrote:

> On Thu, Jun 26, 2014 at 12:56 PM, Steven D'Aprano <steve@pearwood.info>
> wrote:
>> That's a myth. decimal.Decimal *is* a floating point value, and is
>> subject to *exactly* the same surprises as binary floats, except for
>> one: which Decimal, you can guarantee that any decimal string you enter
>> will appear exactly the same (up to the limit of the current
>> precision).
> 
> The important difference is that the issues with decimal floats come
> where humans are comfortable seeing them. If you divide 1 by 3, you get
> 0.333333333 and can understand that adding three of those together won't
> quite make 1.0, because you can see that you shortened it. If you divide
> 11 by 10, it's not obvious that that repeats.

I'm not sure if you're agreeing with me or disagreeing with me.

"Repeats" is a property of a number *in a specific base*, not of the 
number itself. So 1/3 does not repeat in base 3, where it would be 
written as the terminating trinary number 0.1. Likewise, 11/10 repeats in 
base 2, but not in base 10.

What I am I saying is that regardless of whether you use binary floats or 
base-10 Decimals, not all rational numbers x/y can be represented 
exactly. I certainly wasn't saying that the same rationals are inexact in 
both bases, just that the surprise "x/y is not exact" occurs whether you 
have binary or decimal floating point numbers.

Likewise for all other floating point issues, except the surprise "this 
base-2 float is not exactly equal to the base-10 number I typed". Because 
Decimal is base-10, what you type is what you get.


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#73606

FromChris Angelico <rosuav@gmail.com>
Date2014-06-26 14:41 +1000
Message-ID<mailman.11252.1403757673.18130.python-list@python.org>
In reply to#73605
On Thu, Jun 26, 2014 at 2:17 PM, Steven D'Aprano <steve@pearwood.info> wrote:
> I'm not sure if you're agreeing with me or disagreeing with me.
>
> "Repeats" is a property of a number *in a specific base*, not of the
> number itself. So 1/3 does not repeat in base 3, where it would be
> written as the terminating trinary number 0.1. Likewise, 11/10 repeats in
> base 2, but not in base 10.
>
> What I am I saying is that regardless of whether you use binary floats or
> base-10 Decimals, not all rational numbers x/y can be represented
> exactly. I certainly wasn't saying that the same rationals are inexact in
> both bases, just that the surprise "x/y is not exact" occurs whether you
> have binary or decimal floating point numbers.
>
> Likewise for all other floating point issues, except the surprise "this
> base-2 float is not exactly equal to the base-10 number I typed". Because
> Decimal is base-10, what you type is what you get.

Broadly agreeing, but with the caveat that, as you say, "repeats"
depends on the base - and there's one base that most humans use, and
it's not the base that IEEE floats use. So when I said that 11/10
repeats, I mean that it repeats in binary, which causes precision
problems with binary floats; but everyone who's done basic work with
vulgar and decimal fractions understands that 1/3 can't be perfectly
represented in decimal.

So the problems with floating point representations are indeed common
to float and decimal.Decimal, but they're more surprising with float
because humans aren't used to tenths repeating.

ChrisA

[toc] | [prev] | [next] | [standalone]


#73603

FromBen Finney <ben@benfinney.id.au>
Date2014-06-26 13:39 +1000
Message-ID<mailman.11251.1403753977.18130.python-list@python.org>
In reply to#73601
Steven D'Aprano <steve@pearwood.info> writes:

> On Wed, 25 Jun 2014 14:12:31 -0700, Maciej Dziardziel wrote:
>
> > Floating points values use finite amount of memory, and cannot
> > accurately represent infinite amount of numbers, they are only
> > approximations. This is limitation of float type and applies to any
> > languages that uses types supported directly by cpu. To deal with it
> > you can either use decimal.Decimal type that operates using decimal
> > system and saves you from such surprises
>
> That's a myth. decimal.Decimal *is* a floating point value

That's misleading: Decimal uses *a* floating-point representation, but
not the one commonly referred to. That is, Decimal does not use IEEE-754
floating point.

> and is subject to *exactly* the same surprises as binary floats,

Since those “surprises” are the ones inherent to *decimal*, not binary,
floating point, I'd say it's also misleading to refer to them as
“exactly the same surprises”. They're barely surprises at all, to
someone raised on decimal notation.

This makes the Decimal functionality starkly different from the built-in
‘float’ type, and it *does* save you from the rather-more-surprising
behaviour of the ‘float’ type. This is not mythical.

-- 
 \     “Guaranteed to work throughout its useful life.” —packaging for |
  `\                                          clockwork toy, Hong Kong |
_o__)                                                                  |
Ben Finney

[toc] | [prev] | [next] | [standalone]


#73610

FromSteven D'Aprano <steve@pearwood.info>
Date2014-06-26 09:15 +0000
Message-ID<53abe4ad$0$11121$c3e8da3@news.astraweb.com>
In reply to#73603
On Thu, 26 Jun 2014 13:39:23 +1000, Ben Finney wrote:

> Steven D'Aprano <steve@pearwood.info> writes:
> 
>> On Wed, 25 Jun 2014 14:12:31 -0700, Maciej Dziardziel wrote:
>>
>> > Floating points values use finite amount of memory, and cannot
>> > accurately represent infinite amount of numbers, they are only
>> > approximations. This is limitation of float type and applies to any
>> > languages that uses types supported directly by cpu. To deal with it
>> > you can either use decimal.Decimal type that operates using decimal
>> > system and saves you from such surprises
>>
>> That's a myth. decimal.Decimal *is* a floating point value
> 
> That's misleading: Decimal uses *a* floating-point representation, but
> not the one commonly referred to. That is, Decimal does not use IEEE-754
> floating point.

You're technically correct, but only by accident.

IEEE-754 covers both binary and decimal floating point numbers:

http://en.wikipedia.org/wiki/IEEE_floating_point


but Python's decimal module is based on IEEE-854, not 754.

http://en.wikipedia.org/wiki/IEEE_854-1987

So you're right on a technicality, but wrong in the sense of knowing what 
you're talking about *wink*


>> and is subject to *exactly* the same surprises as binary floats,
> 
> Since those “surprises” are the ones inherent to *decimal*, not binary,
> floating point, I'd say it's also misleading to refer to them as
> “exactly the same surprises”. They're barely surprises at all, to
> someone raised on decimal notation.

Not at all. They are surprises to people who are used to *mathematics*, 
fractions, rational numbers, the real numbers, etc. It is surprising that 
the rational number "one third" added together three times should fail to 
equal one. Ironically, binary float gets this one right:

py> 1/3 + 1/3 + 1/3 == 1
True
py> Decimal(1)/3 + Decimal(1)/3 + Decimal(1)/3 == 1
False


but for other rationals, that is not necessarily the case.

It is surprising when x*(y+z) fails to equal x*y + x*z, but that can 
occur with both binary floats and Decimals.

It is surprising when (x + y) + z fails to equal x + (y + z), but that 
can occur with both binary floats and Decimals.

It is surprising when x != 0 and y != 0 but x*y == 0, but that too can 
occur with both binary floats and Decimals. 

And likewise for most other properties of the rationals and reals, which 
people learn in school, or come to intuitively expect. People are 
surprised when floating-point arithmetic fails to obey the rules of 
mathematical arithmetic.

If anyone is aware of a category of surprise which binary floats are 
prone to, but Decimal floats are not, apart from the decimal-
representation issue I've already mentioned, I'd love to hear of it. But 
I doubt such a thing exists.

Decimal in the Python standard library has another advantage, it supports 
user-configurable precisions. But that doesn't avoid any category of 
surprise, it just mitigates against being surprised as often.


> This makes the Decimal functionality starkly different from the built-in
> ‘float’ type, and it *does* save you from the rather-more-surprising
> behaviour of the ‘float’ type. This is not mythical.

It simply is not true that Decimal avoids the floating point issues that 
"What Every Computer Scientist Needs To Know About Floating Point" warns 
about:

http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

It *cannot* avoid them, because Decimal is itself a floating point 
format, it is not an infinite precision number type like 
fractions.Fraction.

Since Decimal cannot avoid these issues, all we can do is push the 
surprises around, and hope to have less of them, or shift them to parts 
of the calculation we don't care about. (Good luck with that.) Decimal, 
by default, uses 28 decimal digits of precision, about 11 or 12 more 
digits than Python floats are able to provide. So right away, by shifting 
to Decimal you gain precision and hence might expect fewer surprises, all 
else being equal.

But all else isn't equal. The larger the base, the larger the "wobble". 
See Goldberg above for the definition of wobble, but it's a bad thing. 
Binary floats have the smallest wobble, which is to their advantage.

If you stick to trivial calculations using nothing but trivially "neat" 
decimal numbers, like 0.1, you may never notice that Decimal is subject 
to the same problems as float (only worse, in some ways -- Decimal 
calculations can fail in some spectacularly horrid ways that binary 
floats cannot). But as soon as you start doing arbitrary calculations, 
particularly if they involve divisions and square roots, things are no 
longer as neat and tidy.

Here's an error that *cannot* occur with binary floats: the average of 
two numbers x and y is not guaranteed to lie between x and y!


py> from decimal import *
py> getcontext().prec = 3
py> x = Decimal('0.516')
py> y = Decimal('0.518')
py> (x + y) / 2
Decimal('0.515')


Ouch!



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#73613

FromChris Angelico <rosuav@gmail.com>
Date2014-06-26 19:38 +1000
Message-ID<mailman.11254.1403775938.18130.python-list@python.org>
In reply to#73610
On Thu, Jun 26, 2014 at 7:15 PM, Steven D'Aprano <steve@pearwood.info> wrote:
> Here's an error that *cannot* occur with binary floats: the average of
> two numbers x and y is not guaranteed to lie between x and y!
>
>
> py> from decimal import *
> py> getcontext().prec = 3
> py> x = Decimal('0.516')
> py> y = Decimal('0.518')
> py> (x + y) / 2
> Decimal('0.515')
>
>
> Ouch!

But what you're looking at is also a problem with intermediate
rounding, as the sum of .516 and .518 can't be represented in 3
digits. One rule of thumb that I learned back in my earliest coding
days was that your intermediate steps should have significantly more
precision than your end result; so if you want an end result with a
certain precision (say, 3 decimal digits), you should calculate with a
bit more. Of course, "a bit" is nearly impossible to define [1], but
if you're mostly adding and subtracting, or multiplying by smallish
constants, 1-2 extra digits' worth of precision is generally enough.
Or just give yourself lots of room, like using double-precision for
something like the above example. Compare this:

>>> from decimal import *
>>> getcontext().prec = 4
>>> x = Decimal('0.516')
>>> y = Decimal('0.519')
>>> avg = (x + y) / 2
>>> getcontext().prec = 3
>>> avg + 0
Decimal('0.518')
>>> (x + y) / 2
Decimal('0.52')

Doing the intermediate calculation with precision 3 exhibits the same
oddity Steven mentioned (only the other way around - result is too
high), but having a little extra room in the middle means the result
is as close to the correct answer as can be represented (0.517 would
be equally correct). With floating point on an 80x87, you can do this
with 80-bit FPU registers; I don't know of a way to do so with Python
floats, but (obviously) it's pretty easy with Decimal.

ChrisA

[1] Thank you, smart-aleck up the back, I am fully aware that "a bit"
is exactly one binary digit. That's not enough for a decimal float.
You've made your point, now shut up. :)

[toc] | [prev] | [next] | [standalone]


Page 1 of 2  [1] 2  Next page →

Back to top | Article view | comp.lang.python


csiph-web