Why is `9.3 == 9.3.to_d` false? - ruby

I just ran into an interesting case during TDD:
Failure/Error: expect(MoneyManager::CustomsCalculator.call(price: 31, weight: 1.12)).to eq 9.3
expected: 9.3
got: 0.93e1
I investigated further and found:
require 'bigdecimal'
=> true
2.4.2 :005 > require 'bigdecimal/util'
=> true
...
2.4.2 :008 > 1 == 1.to_d
=> true
2.4.2 :009 > 2 == 2.to_d
=> true
2.4.2 :010 > 2.0 == 2.0.to_d
=> true
2.4.2 :011 > 1.3 == 1.3.to_d
=> true
2.4.2 :012 > 9.3 == 9.3.to_d
=> false
Why is 9.3 == 9.3.to_d false?
PS, I am well aware of what a Float and a BigDecimal is, but I'm delightfully puzzled by this particular behavior.

This is not really a "ruby problem". This is a floating point representation of numbers problem.
You cannot reliably perform an equality check between floating point numbers and the "exact" value (as represented by BigDecimal).
BigDecimal.new(9.3, 2) is exact. 9.3 is not.
9.3 * 100 #=> 930.0000000000001
1.3 * 100 #=> 130.0
That's just how binary floating point numbers work. They are (sometimes) an inexact representation of the "true" value.
You can either:
Compare like-for-like (bigdecimal1 == bigdecimal2, or float1 == float2). But also note that comparing float1 == float2 is also unreliable if you're performing different calculations to get those values!! Or,
Check that the values are equal within an error bound (e.g. in rspec terms, expect(value1).to be_within(1e-12).of(value2)).

Edited due to Eric comment above
You could use the nature of float and compare it to to a limit you propose which would return either true or false reliably.
(bigdecimal-float).abs < comparison_limit
In your example that would be (I have added () to improve readability):
((9.3.to_d)-9.3).abs < 0.000001 <-- watch out for the limit!
Which yields true and can be used for testing.
Edit based on Eric's (thank you for it) comment.
It is important to always check the limits of the tolerance when comparing the two numbers.
You could do it the following way:
9.3.next_float
which would give you
9.300000000000002
so your tolerance should be
0.000000000000002
Note: watch out for the step:
9.3.next_float.next_float
=> 9.300000000000004
Now the code looks differently:
((9.3.to_d)-9.3).abs < 0.000000000000002

Related

Ruby not allowing me to compare values

I made the code below, but when I run it, including if I pick the correct number, the console prints the debug. Why is this? What am I doing wrong?
puts 'Welcome to the number guessing game. I will pick a number between 1-100. It will be your job to guess the number. If you are incorrect, I will tell you if your guess is higher or lower than my number, I will let you know.'
puts "Time to guess!"
mine = (rand(1..100))
puts mine
grabber = gets.chomp!
if mine == grabber
puts 'That\'s it!'
else
print 'debug'
end
You're comparing values of two different types. gets returns a string, rand(1..100) returns an integer. You can't compare them directly. You need to convert them to the same type, either both integer or both string.
Try using to_i on the string to convert it to an integer:
if mine == grabber.to_i
As #meagar said, you are comparing different types (a string from gets and a number from rand) which will always returning false.
That being said, you have a couple of different ways to coerce/convert datatypes in ruby.
The most common one, as #maeger showed, is using to_i, however it can lead to some strange behaviours as any string that isn't easily parsed as an integer will return 0.
2.5.3 :001 > 'potato'.to_i
=> 0
2.5.3 :002 > '0xff'.to_i
=> 0
If you want to avoid this you can use Integer(arg), this is actually a method defined in Kernel that will do its best to verify if the string is actually convertible into an integer and if it fails it will raise an ArgumentError.
2.5.3 :001 > Integer('potato')
=> ArgumentError (invalid value for Integer(): 'potato')
2.5.3 :002 > Integer('2')
=> 2
2.5.3 :003 > Integer('0xff') # Hexadecimal
=> 255
2.5.3 :004 > Integer('0666') # Octal
=> 438
2.5.3 :005 > Integer('0b1110') # Binary
=> 14

Why does random work like this in Ruby?

I was trying to be clever about deterministically picking random stuff, and found this:
irb(main):011:0> Random.new(Random.new(1).rand + 1).rand == Random.new(1).rand
=> true
irb(main):012:0> Random.new(Random.new(5).rand + 1).rand == Random.new(5).rand
=> false
irb(main):013:0> Random.new(Random.new(5).rand + 5).rand == Random.new(5).rand
=> true
For a second, I thought "wow, maybe that's a property of random number generators", but Python and C# fail to reproduce this.
You’re probably going to be disappointed with this one. Let’s take a look at the output of rand:
irb(main):001:0> Random.rand
0.5739704645347423
It’s a number in the range [0, 1). Random.new accepts an integer seed.
irb(main):002:0> Random.new(5.5) == Random.new(5)
true
Mystery solved!

How to check if a value is included between two other values?

I'm trying to express a condition like this:
if 33.75 < degree <= 56.25
# some code
end
But Ruby gives this error:
undefined method `<=' for true:TrueClass
I'm guessing that one way to do it is something like:
if 33.75 < degree and degree <= 56.25
# code
end
But there is no another, easier way?
Ruby also has between?:
if value.between?(lower, higher)
There are many ways of doing the same things in Ruby.
You can check if value is in the range by use of following methods,
14.between?(10,20) # true
(10..20).member?(14) # true
(10..20).include?(14) # true
But, I would suggest using between than member? or include?. You can find more about it here.
You can express a <= x <= b as (a..b).include? x and a <= x < b as (a...b).include? x.
>> (33.75..56.25).include? 33.9
=> true
>> (33.75..56.25).include? 56.25
=> true
>>
>> (33.75..56.25).include? 56.55
=> false
Unfortunately, there seems no such thing for a < x <= b, a < x < b, ..
UPDATE
You can accomplish using (-56.25...-33.75).include? -degree. But it's hard to read. So I recommend you to use 33.75 < degree and degree <= 56.25.
use between? is the easiest way, I found most answers here didn't mention (ruby doc explanation is hard to understand too), using between? does INCLUDE the min and max value.
for example:
irb(main):001:0> 2.between?(1, 3)
=> true
irb(main):002:0> 3.between?(1, 3)
=> true
irb(main):003:0> 1.between?(1, 3)
=> true
irb(main):004:0> 0.between?(1, 3)
=> false
by the way, ruby doc quote:
between?(min, max) → true or false Returns false if obj <=> min is
less than zero or if anObject <=> max is greater than zero, true
otherwise.
undefined method `<=' for true:TrueClass
means that Ruby is not parsing your if-condition the way you expect it.
Using && and adding parentheses helps!
if (33.75<degree) && (degree<=56.25)
...
end
It's a bad habit to leave out parentheses -- as soon as the expression gets more difficult, you could get a surprising outcome. I've seen this many times in other people's code.
Using and instead of && in Ruby is a very bad idea, see:
https://www.tinfoilsecurity.com/blog/ruby-demystified-and-vs
http://rubyinrails.com/2014/01/30/difference-between-and-and-in-ruby/
You can also use this notation:
(1..5) === 3 # => true
(1..5) === 6 # => false
Adjust value to range?
value = 99
[[value,0].max, 5].min # 5
value = -99
[[value,0].max, 5].min # 0
value = 3
[[value,0].max, 5].min # 3

Float precision in ruby

I'm writing a ruby program that uses floats. I'm having trouble with the precision. For example
1.9.3p194 :013 > 113.0 * 0.01
# => 1.1300000000000001
and therefore
1.9.3p194 :018 > 113 * 0.01 == 1.13
# => false
This is exactly the sort of calculation my app needs to get right.
Is this expected? How should I go about handling this?
This is an inherent limitation in floating point numbers (even 0.01 doesn't have an exact binary floating point representation). You can use the technique provided by Aleksey or, if you want perfect precision, use the BigDecimal class bundled in ruby. It's more verbose, and slower, but it will give the right results:
require 'bigdecimal'
=> true
1.9.3p194 :003 > BigDecimal.new("113") * BigDecimal("0.01")
=> #<BigDecimal:26cefd8,'0.113E1',18(36)>
1.9.3p194 :004 > BigDecimal.new("113") * BigDecimal("0.01") == BigDecimal("1.13")
=> true
In calculation with float you should use sigma method - it means not to compare two values, but compare absolute difference of them with a very little value - 1e-10, for example.
((113 * 0.01) - 1.13).abs<1e-10

How do I declare NaN (not a number) in Ruby?

Also "NaN".to_f returns 0 instead of NaN.
Since Ruby 1.9.3 there is a constant to get the NaN value
Float::NAN
=> NaN
If you need to test if a number is NaN, you can use #nan? on it:
ruby-1.8.7-p352 :008 > (0/0.0).nan? #=> true
ruby-1.8.7-p352 :009 > (0/1.0).nan? #=> false
The simplest way is to use 0.0 / 0.0. "NaN".to_f doesn't work, and there's some discussion in this thread about why.
0.0 / 0.0 works for me on ruby 1.8.6.
The thread linked to by Pesto has this function, which should work on platforms where floating-point numbers are implemented according to IEEE 754:
def aNaN
s, e, m = rand(2), 2047, rand(2**52-1)+1
[sprintf("%1b%011b%052b", s,e,m)].pack("B*").unpack("G").first
end
Assigning a variable in Rails can be done thus (useful for unit-testing):
o.amount = BigDecimal.new('NaN')
expect(o.valid?).to be false

Resources