0.06 == 0.06 returns false in Ruby?

W

William James

2 Always use
(x-y).abs < (x.abs + y.abs) * Float::EPSILON)
as a test for equality.

Better than the first proposal, but won't work if the rounding error
gets too large after a complex computation.
In addition, (1) and (2) suffer from the problem that x==y and y==z do
not imply x==z.

How about
(a - b).abs / [a, b].map{|x| a.abs}.min < Float::EPSILON
?

def eq( a, b )
(a - b).abs / [a, b].map{|x| a.abs}.min < Float::EPSILON
end

DATA.each{|s|
strings = s.chomp.split(";")
floats = strings.map{|s| eval(s) }
puts strings.join( " == " ) +
" : #{floats[0]==floats[1]} #{ eq( *floats ) }"
}

__END__
(0.05+0.01);0.06
(0.34+0.01);0.35
9.123456789;9.1234567891
9.123456789012345;9.1234567890123451
9.12345678901234e-9;9.123456789012341e-9

=== output ===
(0.05+0.01) == 0.06 : false true
(0.34+0.01) == 0.35 : false true
9.123456789 == 9.1234567891 : false false
9.123456789012345 == 9.1234567890123451 : false true
9.12345678901234e-9 == 9.123456789012341e-9 : false true
 
L

Logan Capaldo

A warning on things like BigDecimal.
Unless I'm mistaken it's still stored in twos compliment, which means
you'll still end up with the same sort of floating point problems
(albeit further down), and the same numbers that you can't express
exactly with a normal float, can't be expressed exactly with a
BigDecimal.
Um, 2's compliment is not a cause of floating point error. IEEE
floating point numbers do not use two's complement, they have a sign
bit. Two's complement is just a way of encoding the sign of a value.
You'd think some fixed point math libraries would help, but be
careful, because many of those _also_ store in twos compliment.
Again, the use of two's complement or lack thereof is totally irrelevant.
 
P

pstickne

I am in the Sophomore year of a CS degree at Washing State University
and I am currently enrolled in a mandatory Numerical Computing class.
While it is very much more CS-than-math it does, of course, cover
topics like FP implementations and related issues.
 
P

Peña, Botp

From: Robert Klemme [mailto:[email protected]]=20
# Well, you could provide your formula as strings and convert it to
# something that creates BigDecimals along the way, like
# irb(main):015:0> "0.01+0.05".gsub(%r{\d+(?:\.\d*)?},=20
# "BigDecimal.new('\\&')")
# =3D> "BigDecimal.new('0.01')+BigDecimal.new('0.05')"
# irb(main):016:0> eval("0.01+0.05".gsub(%r{\d+(?:\.\d*)?},
# "BigDecimal.new('\\&')"))
# =3D> #<BigDecimal:7ff6dd60,'0.6E-1',4(12)>
# note this is of course not a proper solution since the RX does not
# match all valid floats

:)
on my case, i just want ruby to default to bigdeci (or whatever i want) =
instead of float. meaning, i choose any math processor i want. is that =
possible now, or in near future of ruby?

kind regards -botp
 
K

Kyle Schmitt

Um, 2's compliment is not a cause of floating point error. IEEE
floating point numbers do not use two's complement, they have a sign
bit. Two's complement is just a way of encoding the sign of a value.

Used the wrong term :p sue me. I guess that's why I should lookup
stuff before responding ;)
Anyway it's all based on the fact that you can't store some numbers
exactly as sum of different xes for 2**(-x).

--Kyle
 
R

Robert Klemme

Used the wrong term :p sue me. I guess that's why I should lookup
stuff before responding ;)
Anyway it's all based on the fact that you can't store some numbers
exactly as sum of different xes for 2**(-x).

BigDecimals do *not* use binary representation. That's why they are
called Big/Decimal/.

If you want to give a warning then it should be that for *any*
representation (binary, decimal, hex, octal whatever) there are
fractional numbers that cannot be represented properly with that
representation. You have to be aware of this for *all* representation
types. The reason people are surprised is typically because we enter
float numbers in decimal but systems use binary representation (which is
standardized btw.) internally and that has other numbers that it cannot
properly represent. BigDecimal helps because it uses the same
representation internally that we are used to use for numbers - decimal
digits.

Kind regards

robert
 
M

Matthias Wächter

Hi,
Try converting to strings e.g:
irb(main):004:0> (0.05 + 0.01).to_s == 0.06.to_s
=> true

I think what puzzles is while 0.05+0.01 == 0.06 returns false, the
strings _do_ match.

def float_check(a,b)
p [a==b,a.to_s == b.to_s]
end

float_check(0.06,0.06) # -> [true,true]
float_check(0.05+0.01,0.06) # -> [false,true]


Most other programming languages either have no format-less .to_s
function (like in C) or return values at inspection time so that
people get aware of the issue more easily. In Python it is obvious
that 0.05+0.01 == 0.06 is false.

$ python0.059999999999999998

I don't understand why Ruby 'rounds' values with .inspect (and
to_s) opposed to Marshal.dump. Compare its output with the one
Python gives (this code is for Ruby 1.8.6):

[0.05,0.01,0.05+0.01,0.06].each do |v|
p Marshal.dump(v).sub(/....([^\000]*).*/,'\1')
end

Output:

"0.050000000000000003"
"0.01"
"0.060000000000000005"
"0.059999999999999998"


Did I miss something here, is there a way to make Float#to_s (and,
thus, Float#inspect) give the same output as Marshal?

- Matthias
 
S

Stefan Rusterholz

Matthias said:
I don't understand why Ruby 'rounds' values with .inspect (and
.to_s) opposed to Marshal.dump. Compare its output with the one
Python gives (this code is for Ruby 1.8.6):

Because for an *inspection* a precision of 5, 6 places are sufficient.
Inspect isn't used for printing values other than debugging etc.
In the opposite, having 40+ places for every float in your debug output
would be rather annoying and almost always of little help.
If python choses to be annoying, be it so, I'm happy ruby doesn't.
Use sprintf/String#% if you want to specify the precision you want your
output.
Marshal.dump on the other hand is serializing, of course you don't want
your floats truncated just because you're serializing.
As for having a way: you can always open the Float class and define
to_s to your liking. Same for inspect.

Regards
Stefan
 
M

M. Edward (Ed) Borasky

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Stefan said:
Marshal.dump on the other hand is serializing, of course you don't want
your floats truncated just because you're serializing.
Excuse me -- does Marshal.dump serialize floats in text or in binary?
I'd hope it was binary. A *YAML* serializer would have to do it in text,
although they could obviously encode binary in base 64 or hex.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFG3FVV8fKMegVjSM8RAlq1AKCCJbOHo7WXyZQUSwt2IWHb0rRiCwCfbYpU
JabnGpU8CXJgl/J1oeEgruE=
=GUe1
-----END PGP SIGNATURE-----
 
J

Joel VanderWerf

M. Edward (Ed) Borasky said:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


Excuse me -- does Marshal.dump serialize floats in text or in binary?
I'd hope it was binary. A *YAML* serializer would have to do it in text,
although they could obviously encode binary in base 64 or hex.

The number itself is in acsii:

irb(main):006:0> x = 1.234e120
=> 1.234e+120
irb(main):007:0> Marshal.dump(x)
=> "\004\bf\0371.2340000000000001e+120\000@`"

Maybe this is for compatibility, in case ruby is running on some oddball
system without the standard float formats?
 
D

Daniel Berger

Unfortunately, there is no easy solution to this problem.

<snip>

Here's my naive, DWIM approach:

class Float
def ==(other)
self.to_s == other.to_s
end
end

Regards,

Dan
 
M

Michael Ulm

Daniel said:
<snip>

Here's my naive, DWIM approach:

class Float
def ==(other)
self.to_s == other.to_s
end
end

Oh yes, nice one. This fulfills

'from x==y and y==z follows x==z'

Unfortunately it does not fulfill some other properties one
wants to have; e.g. it violates

'from x==y follows x+z==y+z'

for example
x, y = 1.0/3.0, 0.333333333333333
x.to_s == y.to_s # => true
(x+x).to_s == (y+x).to_s # => false

In particular, you don't have

'from x==y follows x-y==0'

for example
x, y = 0.6, 6 * 0.1
x.to_s == y.to_s # => true
(x - y).to_s == 0.to_s # => false

HTH,

Michael
 
P

Peña, Botp

From: Michael Ulm [mailto:[email protected]]=20
# Daniel Berger wrote:
# > class Float
# > def =3D=3D(other)
# > self.to_s =3D=3D other.to_s
# > end
# > end
# Oh yes, nice one. This fulfills=20
# 'from x=3D=3Dy and y=3D=3Dz follows x=3D=3Dz'
# Unfortunately it does not fulfill some other properties one

w a little help fr bigdecimal, a little goes a long long way
class Float
def to_dec
BigDecimal.new(self.to_s)
end
end =3D> nil
class Fixnum
def to_dec
BigDecimal.new(self.to_s)
end
end
=3D> nil
x=3D0.06.to_dec
y=3D0.05.to_dec + 0.01.to_dec
x=3D=3Dy =3D> true
z=3D0.02.to_dec+0.04.to_dec
y=3D=3Dz =3D> true
z=3D=3Dy =3D> true
x=3D=3Dz =3D> true
z=3D=3Dx
=3D> true

# wants to have; e.g. it violates
# 'from x=3D=3Dy follows x+z=3D=3Dy+z'
x+z =3D=3D y+z =3D> true
(x+z).to_s =3D=3D (y+z).to_s =3D> true
puts (x+z)
0.12E0
=3D> nil

# for example
# In particular, you don't have
#=20
# 'from x=3D=3Dy follows x-y=3D=3D0'
x=3D=3Dy =3D> true
x-y =3D=3D 0
=3D> true

=20
# for example
#=20
# > x, y =3D 0.6, 6 * 0.1
# > x.to_s =3D=3D y.to_s # =3D> true
# > (x - y).to_s =3D=3D 0.to_s # =3D> false
x, y =3D 0.6.to_dec, 6 * 0.1.to_dec
(x - y) =3D=3D 0 =3D> true
(x - y) =3D=3D 0.to_dec =3D> true
(x - y).to_s =3D=3D 0.to_dec.to_s
=3D> true

kind regards -botp
 
M

Michael Ulm

Peña said:
From: Michael Ulm [mailto:[email protected]]
# Daniel Berger wrote:
# > class Float
# > def ==(other)
# > self.to_s == other.to_s
# > end
# > end
# Oh yes, nice one. This fulfills
# 'from x==y and y==z follows x==z'
# Unfortunately it does not fulfill some other properties one

w a little help fr bigdecimal, a little goes a long long way
class Float
def to_dec
BigDecimal.new(self.to_s)
end
end => nil
class Fixnum
def to_dec
BigDecimal.new(self.to_s)
end
end
=> nil
x=0.06.to_dec
y=0.05.to_dec + 0.01.to_dec
x==y => true
z=0.02.to_dec+0.04.to_dec
y==z => true
z==y => true
x==z => true
z==x
=> true
--snip--

However (among other problems):
x = (1.0/3.0).to_dec
y = 1.0.to_dec / 3.0.to_dec
x == y # => false

HTH,

Michael
 
P

Peña, Botp

From: Michael Ulm [mailto:[email protected]]=20
# However (among other problems):
# > x =3D (1.0/3.0).to_dec

foul.. ^^^^^^^^^ is a float op and not a bigdecimal/to_dec. use atomic =
operations comparison, pls :)


# > y =3D 1.0.to_dec / 3.0.to_dec

that is more like it

# > x =3D=3D y # =3D> false

of course.

so consider eg,

irb(main):057:0> x =3D 1.0.to_dec / 3.0.to_dec
=3D> #<BigDecimal:b7d87acc,'0.3333333333 333333E0',16(24)>
irb(main):058:0> y =3D 2.0.to_dec / 6.0.to_dec
=3D> #<BigDecimal:b7d7d874,'0.3333333333 333333E0',16(24)>
irb(main):059:0> x =3D=3D y
=3D> true

kind regards -botp
 
M

Michael Ulm

Peña said:
From: Michael Ulm [mailto:[email protected]]
# However (among other problems):
# > x = (1.0/3.0).to_dec

foul.. ^^^^^^^^^ is a float op and not a bigdecimal/to_dec. use atomic operations comparison, pls :)
--snip--

What about
x = 1.0.to_dec / 3.0.to_dec
y = x + x + x
y == 1.0.to_dec # => false

?

HTH,

Michael


--
Michael Ulm
R&D Team
ISIS Information Systems Austria
tel: +43 2236 27551-542, fax: +43 2236 21081
e-mail: (e-mail address removed)
Visit our Website: www.isis-papyrus.com

---------------------------------------------------------------
This e-mail is only intended for the recipient and not legally
binding. Unauthorised use, publication, reproduction or
disclosure of the content of this e-mail is not permitted.
This email has been checked for known viruses, but ISIS accepts
no responsibility for malicious or inappropriate content.
---------------------------------------------------------------
 
P

Peña, Botp

From: Michael Ulm [mailto:[email protected]]=20
# > x =3D 1.0.to_dec / 3.0.to_dec
# > y =3D x + x + x
# > y =3D=3D 1.0.to_dec # =3D> false

hmm, i thought this was already answered by others.. gotta check..

irb(main):023:0* x=3D1.0.to_dec/3.0.to_dec
=3D> #<BigDecimal:b7e45e14,'0.3333333333 333333E0',16(24)>
irb(main):024:0> x+x+x =3D=3D 1.0.to_dec
=3D> true

kind regards -botp
 
M

Michael Ulm

Peña said:
From: Michael Ulm [mailto:[email protected]]
# > x = 1.0.to_dec / 3.0.to_dec
# > y = x + x + x
# > y == 1.0.to_dec # => false

hmm, i thought this was already answered by others.. gotta check..

irb(main):023:0* x=1.0.to_dec/3.0.to_dec
=> #<BigDecimal:b7e45e14,'0.3333333333 333333E0',16(24)>
irb(main):024:0> x+x+x == 1.0.to_dec
=> true

Strange, I tested this on two different machines and both gave false
to your last expression. Did you modify BigDecimal#== ?

Regards,

Michael
 
M

Martin Jansson

This is why I prefer to use fixed decimal. In your own private code,
it's easy enough to implement. You just decide how many "decimals" your
int's should have.

Getting correct programs using floats are really hard. Heck, MS Excell
don't even get Sum and Mean right. (Microsoft, usually, use naïve
algorithm's that don't take care of underflow and rounding errors).
 
P

Peña, Botp

From: Michael Ulm [mailto:[email protected]]=20
# > From: Michael Ulm [mailto:[email protected]]=20
# > irb(main):023:0* x=3D1.0.to_dec/3.0.to_dec
# > =3D> #<BigDecimal:b7e45e14,'0.3333333333 333333E0',16(24)>
# > irb(main):024:0> x+x+x =3D=3D 1.0.to_dec
# > =3D> true
#=20
# Strange, I tested this on two different machines and both gave false
# to your last expression. Did you modify BigDecimal#=3D=3D ?

yes. i just used one of the codes posted here.. not sure if it was even =
your suggested code though. nonetheless, what were you expecting? that =
1/3 can be represented exactly in decimal? :)
but i really want a way to override float literals, so i do not litter =
all those to_decs :)) Of course, floats has a lot of merits, but it =
gives too many surprises fr just a few dec samples. businesswise, it has =
little use.

kind regards -botp
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top