Skip to content

0.1 + 0.2 != 0.3 revisited

5 messages · Simon Fear, Christian Schulz, Peter Dalgaard +1 more

#
Prompted by Peter Dalgard's recent elegant "intbin" function,
I have been playing with the extension to converting reals to binary
representation. The decimal part can be done like this:

decbase <- function(x, n=52, base=2) {
  if(n) {
    x <- x*base
    paste(trunc(x), decbase(x%%1, n-1, base), sep="")
  }
}

n=52 default because that's the number of bits in the significand of
a 64-bit float.

Now, `decbase(0.1)` is a bit curious in that the 0.1 is going to be
converted to a binary float by the interpreter ... and then re-converted
by `decbase`, so really I should
insist on character format for the number I want to convert. But
anyway I do get the right answer up to the point of truncation:
[1] "0001100110011001100110011001100110011001100110011001"
[1] "0011001100110011001100110011001100110011001100110011"
[1] "0100110011001100110011001100110011001100110011001100"

That is to say, decbase(.1) + decbase(.2) really does equal
decbase(.3). But not if R does its own arithmetic first:
[1] "0100110011001100110011001100110011001100110011001101"

What has gone on here? Why does R apparently get it's
internal representation of one of .1 or .2 "wrong" ? Does the
end of the internal binary for .1 get rounded up instead of 
truncated ? Why wouldn't that show in decbase(.1) ?  
 
Simon Fear 
Senior Statistician 
Syne qua non Ltd 
Tel: +44 (0) 1379 644449 
Fax: +44 (0) 1379 644445 
email: Simon.Fear at synequanon.com 
web: http://www.synequanon.com 
  
Number of attachments included with this message: 0 
  
This message (and any associated files) is confidential and\...{{dropped}}
#
On Fri, 6 Feb 2004 12:55:05 -0000, "Simon Fear"
<Simon.Fear at synequanon.com> wrote :
Remember that IEEE double formats are complicated, they're not fixed
point formats.

Both 0.1 and 0.2 are less than 1, so the n=52 count is wrong.  I think
0.1 would be stored as (1 + 0.6)*2^(-4) and 0.2 would be stored as (1
+ 0.6)*2^(-3), whereas 0.3 would be stored as (1 + 0.2)*2^(-2).  You
should expect 56 decimal (binary?) place accuracy on 0.1, 55 place
accuracy on 0.2, and 54 place accuracy on 0.3.  It's not surprising
weird things happen!

Duncan Murdoch
2 days later
#
Hi,

IEEE says that real numbers are normalized (a few below 10^(-16) may be 
not [gradual underflow]), so that they look like 0.1ddd2^ex. Then only 
ddd and ex are kept:
0.1 = 0.00011.. 2^0 = 0.11001100.. 2^(-3) -> (11001100.., -3)
0.2 = 0.0011..  2^0 = 0.11001100.. 2^(-2) -> (11001100.., -2)
0.3 = 0.010011..2^0 = 0.10011001.. 2^(-1) -> (10011001.., -1)
Duncan Murdoch wrote:

            
I don *not* think so: all mantissas here have *52 binary* places!
Christian Hoffmann
#
Christian Hoffmann <christian.hoffmann at wsl.ch> writes:
(53, since the leading 1 is not stored...)

Actually, in the x86 FPUs numbers get extended to 64 bits during
evaluation, so after alignment for addition, the smaller numbers may
have bits beyond the truncation point of the larger ones. These bits
disappear when the result is stored, but rounding may be affected
giving those unit-in-the-last-place differences.
#
On Mon, 09 Feb 2004 08:52:09 +0100, you wrote:

            
Right, that's pretty much what I said, since 1.6 = 1.101100...
Yes, but I was counting bits after the binary point, not bits that are
stored.  The latter is 52 for all numbers, but it translates into more
or less bits after the binary point, depending on the magnitude of the
exponent. 

You can argue that I got the exponent wrong (saying it was -4, when
you say it's -3), and I could live with that.  I was just following
the Intel convention that the mantissa is 1.dddd.. instead of
0.1dddd.. .

Duncan Murdoch