begin
i += 1
puts “step” + i.to_s + " befre adding is " + temp.to_s
temp += 0.1
puts “step” + i.to_s + " after adding is " + temp.to_s
puts
end while temp < 98.6
[root@localhost tmp]# ruby t5.rb
step1 befre adding is 98.4
step1 after adding is 98.5
step2 befre adding is 98.5
step2 after adding is 98.6
OK I think the result is pretty right.
Now I change temp’s original value to 98.3:
case two
[root@localhost tmp]# cat t5.rb
temp = 98.3
i = 0
begin
i += 1
puts “step” + i.to_s + " befre adding is " + temp.to_s
temp += 0.1
puts “step” + i.to_s + " after adding is " + temp.to_s
puts
end while temp < 98.6
[root@localhost tmp]# ruby t5.rb
step1 befre adding is 98.3
step1 after adding is 98.4
step2 befre adding is 98.4
step2 after adding is 98.5
step3 befre adding is 98.5
step3 after adding is 98.6
step4 befre adding is 98.6
step4 after adding is 98.7
The output of step4 let me crazy.
Why the loop doesn’t break after step3?
Because after step3 temp’s value is 98.6, the loop condition was
checked, and the loop should be end.
Since I think case one is right, so case two get wrong result. Why?
Thank you.
That also explains why floats behave strangely in general, for the
newbies out
there. When we see 98.3, we see 98 and 3/10ths. But the computer doesn’t
use
10ths any more than it uses 10s, 100s, etc. It uses binary, powers of
two. So
it converts those 3/10ths to some fraction of a power of two.
Unless, of course, you’re on a mainframe. Then you can do packed decimal
arithmetic… But it’ll be slower and perversely hard to work with for
other
reasons.
So my compromise is, when I want to work with some fraction of a number,
and I
want it to be as precise as possible (with no regard for performance), I
make
it a Rational as long as I can get away with. I can always call to_f on
the
end result.
I might be Doing It Wrong, though. Maybe BigDecimal is the way to go?
BigDecimal is probably easier to code with, but can kill performance as
has
been mentioned previously. Recalling the old Fortran days, we always
had a
constant called EPS we used in these kind of situations. EPS (short for
epsilon) was set to a small number, usually 1e-7. Anytime we needed to
compare two floating point values, we would not compare them directly,
but
rather compare the absolute value of their difference to EPS.
In the original poster’s code, here is how we would have dealt with the
issue:
eps = 1e-7
temp = 98.3
i = 0
begin
i += 1
puts “step” + i.to_s + " befre adding is " + temp.to_s
temp += 0.1
puts “step” + i.to_s + " after adding is " + temp.to_s
puts “temp - 98.6: #{temp-98.6}”
puts #end while temp < 98.6
end while (temp - 98.6) < -eps
Having to remember to do this all the time is difficult and annoying.
So my compromise is, when I want to work with some fraction of a number, and I
want it to be as precise as possible (with no regard for performance), I make
it a Rational as long as I can get away with. I can always call to_f on the
end result.
I might be Doing It Wrong, though. Maybe BigDecimal is the way to go?
Another possibility is using a small, but >0, epsilon and check if the
distance between the number and the limit is less than its value. Ruby
does this automatically for you, if you use its #step functions:
98.3.step(98.6, 0.1).to_a
=> [98.3, 98.4, 98.5, 98.6]
98.4.step(98.6, 0.1).to_a
=> [98.4, 98.5, 98.6]
(98.3…98.6).step(0.1).to_a
=> [98.3, 98.4, 98.5, 98.6]
(98.4…98.6).step(0.1).to_a
=> [98.4, 98.5, 98.6]
Maybe the general advice here is, don’t do it yourself, if Ruby offers a
better way itself.
So my compromise is, when I want to work with some fraction of a number,
and I
want it to be as precise as possible (with no regard for performance), I
make
it a Rational as long as I can get away with. I can always call to_f on
the
end result.
I might be Doing It Wrong, though. Maybe BigDecimal is the way to go?
I think it is. Rational makes sense if you’re starting from an integer
division. But the OP is starting from a float (98.3), so BigDecimal is
probably the way to go.