Ruby not allowing me to compare values - ruby

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

Related

Convert String to Float::INFINITY

In Ruby Float::INFINITY.to_s results in "Infinity" and "Infinity".to_f results in 0.0:
irb(main):001:0> Float::INFINITY.to_s
=> "Infinity"
irb(main):002:0> "Infinity".to_f
=> 0.0
Why is this the case? Why is the symmetry broken? In the programming language for human beings, I would expect the result of the second statement to be Float::INFINITY. Is there any way to convert a string to Float::INFINITY or -Float::INFINITY with Ruby?
Btw. maybe related: The behavior of to_json in Rails is confusing as well. I'd expect it to raise like ActiveModel::Serializer#as_json does.
irb(main):001:0> {a: Float::INFINITY}.to_json
=> "{\"a\":null}"
The way #to_f works is it tries to find a float at the beginning of the string (ignoring whitespaces). It ignores the rest. If it doesn't find anything "floaty-literal-looking", it defaults to 0.0. That's why:
''.to_f # => 0.0
'∞'.to_f # => 0.0
'foo'.to_f # => 0.0
'foo 1.23'.to_f # => 0.0
' 1.23'.to_f # => 1.23
'1.23foo'.to_f # => 1.23
The string 'Infinity' is no different from any other letter only string in that regard. This is kind of unfortunate as it breaks the symmetry. As you pointed out:
Float::INFINITY.to_s.to_f # => 0.0
But at the same time if 'Infinity' did parse to Float::INFINITY this could have lead to some fairly weird and hard to track bugs in everyday code.
Obviously don't do it, but for completeness' sake - to answer the last part:
Is there any way to convert a string to Float::INFINITY or -Float::INFINITY with Ruby?
eval('Float::INFINITY') # => Float::INFINITY
eval('-Float::INFINITY') # => -Float::INFINITY
On the question of why the design decision was made so that infinity breaks the symmetry:
Float::INFINITY.to_s.to_f # => 0.0
The reason is - it's the only way to have consistency. The promise that #to_f gives you is that it will interpret the float literal in (at the start of) the string.
Infinity isn't a literal. If you try to evaluate it, you will get:
NameError: uninitialized constant Infinity
Float::INFINITY isn't a literal either. It's the constant INFINITY nested under Float.
Why does it have to be only literals then? Well... I can make #to_s return anything:
class Float
def to_s
'foo'
end
end
42.0.to_s # => 'foo'
Obviously it's both impossible and unreasonable to expect 'foo'.to_f to return 42.0.
We can make Float::INFINITY.to_s return other stuff. The string 'Infinity' isn't in any way special. It's not a float literal like -1.23 or 9.999999999999995e+39.
You can look at it the opposite way - most floats when #to_s-ed return a string representing their literal form. It's only a happy coincidence that that's also what you need to #to_f them back. Float::INFINITY doesn't return the string version of the literal form, because it has no literal form.
That's all. While I think an awesome opportunity was missed to make ∞ "the infinity literal", they probably saved themselves a lot of headache by not adding a unicode character as a requirement to have the complete language grammar.
Can you think of any programming language that has literal syntax for infinity?
To answer on the second part of your question (the first part is brilliantly answered by #ndnenkov), and leaving aside if it is a good or bad idea (as pointed out again by #ndnenkov), a way to avoid an eval on a string can be something like this:
class String
SPECIAL_FLOATS = {
Float::INFINITY.to_s => Float::INFINITY,
(-Float::INFINITY).to_s => -Float::INFINITY
}
alias_method :super_to_f, :to_f
def to_f
if String::SPECIAL_FLOATS.key? self
return String::SPECIAL_FLOATS[self]
else
return self.super_to_f
end
end
end
"Infinity".to_f
# => Float::INFINITY
Float::INFINITY.to_s.to_f
# => Float::INFINITY
extending the String class in such a way it can handle the conversion of some special Float literals. If it is a good idea or not is a quite complex topic, and in some way it depends on your project and how you are using strings across your code.
Just for comparison, in ruby 2.5.3 on Windows (and not rails, thus require "JSON"):
{a: Float::INFINITY}.to_json
# Traceback (most recent call last):
# 3: from C:/tools/ruby25/bin/irb.cmd:19:in `<main>'
# 2: from (irb):43
# 1: from (irb):43:in `to_json'
# JSON::GeneratorError (862: Infinity not allowed in JSON)
Is there any way to convert a string to Float::INFINITY or -Float::INFINITY with Ruby?
You can use Object#const_get for it:
:001 > Float::INFINITY
=> Infinity
:002 > string = Float::INFINITY.to_s
=> "Infinity"
:003 > Object.const_get("Float::#{string.upcase}")
=> Infinity

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

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

using assert_raise within ruby testing

I'm creating a testing file for a ruby program that finds factorials of given numbers. All of my tests run well except I get an error with my string and negative number tests that should raise exceptions. I'm not entirely sure of the syntax for raise exception, I've read the docs.
this is my code for the factorial program itself, n is the number thats supposed to be passed:
if n.is_a?(Integer) == false
raise 'strings are not acceptable'
end
if n < 0
raise 'negatives are not acceptable'
end
the test case in my test file are as follows:
def test_negative
factorial(-1)
assert_raise do
end
end
def test_string
factorial('hello')
assert_raise do
end
end
both of my tests come back as errors while my other 3, that test normal numbers, come back as passed. I'm new to ruby but I would just want a pass after assert_raise do as my actual error message is in my factorial program right?
in your first test case, it will not return an error because -1 is also considered as an Integer
#irb output
2.0.0-p247 :003 > a = -1
=> -1
2.0.0-p247 :004 > a.is_a?(Integer)
=> true
and in your second case, when you pass a string, it will error even before going inside your condition as you are trying to compare string with an integer
#irb outout
2.0.0-p247 :007 > "hello" < 0
ArgumentError: comparison of String with 0 failed
from (irb):7:in `<'
from (irb):7
and off topic, you could write
if n.is_a?(Integer) == false
raise 'strings are not acceptable'
end
as (more ruby way :))
raise 'strings are not acceptable' unless n.is_a?(Integer)

Convert 9999999999999999999999.001 to "9999999999999999999999.001" in ruby

How to convert 9999999999999999999999.001 to "9999999999999999999999.001" in ruby
I have tried
>> 9999999999999999999999.001.to_s
=> "1.0e+22"
>> "%f" % 9999999999999999999999.001
=> "10000000000000000000000.000000"
Truth is you can't. The number you write is 22 digits and floats in ruby have only 15 digits precision. So when you use this variable already part of its value is kind of "lost" as it is of the class Float.
Use BigDecimal from the standard library.
1.8.7 :005 > require 'bigdecimal'
=> true
1.8.7 :006 > BigDecimal('9999999999999999999999.001')
=> #<BigDecimal:7fe0cbcead70,'0.9999999999 9999999999 99001E22',36(36)>
1.8.7 :007 > BigDecimal('9999999999999999999999.001').to_s
=> "0.9999999999999999999999001E22"
Of course, this example only shows that BigDecimal can handle numbers that big. Wherever you're initially getting your 9999999999999999999999.001 number from needs to get it into BigDecimal as soon as it's calculated / inputted.
You can not do that. The reason is simple: from the very beginning the value of the number is not going to be exactly 9999999999999999999999.001. Floats will have just 15 digits of precision.
However, you can use other type to achieve what you want:
require 'bigdecimal'
a = BigDecimal("9999999999999999999999.001")
a.to_s("F")
>> "9999999999999999999999.001"
For BigDecimal the precision is extended with the requests of bigger real numbers - no restrictions are applied.
Float is faster in calculations, because its meant to use the FPU of the processor directly, but because of that comes the restriction in the precision.
EDIT Especially for #izomorphius and his argument, just a very short code sample:
a = "34.101"
b = BigDecimal(a.to_s)
c = b ** 15
c.to_s("F")
>>> 98063348952510709441484.183684987951811295085234607613193907150561501
Now tell me how otherwise you get the last string?

How do I generate a random number in a certain range with Ruby?

I am trying to get a random year between 1900 and 1980 with Ruby.
So far I have:
puts 'the year was: ' + 1900.to_s + rand(1980).to_s
but this is just adding 1900 and a random number from 0 - 1979 together to look like 19001947.
I think I'm missing something silly but can anyone shed any light?
Ruby 1.9.3
1.9.3p0 :001 > rand(1900..1980)
=> 1946
1.9.3p0 :002 > rand(1900..1980)
=> 1929
1.9.3p0 :003 > rand(1900..1980)
=> 1934
Give this a try
(1900 + rand(81)).to_s
To be clear for passersby
First, there was an issue regarding string concatenation. The original code in the question was concatenating two strings (containing numbers) together. Example:
"1900" + "1920" = "19001920"
Second, there was an issue the range of random numbers being generated. In this case, since we only want a range of 80 years, we want to use rand(81), instead of rand(1980). We then take that result and add it to the base number, which will give us a random number between 1900 and 1980.
puts "The year was #{1900 + rand 81}"
Yes: you're getting the "string catenation" overload of +. Try (1900+rand(80)).to_s
Ruby 1.8+
(1900..1980).to_a[rand(80)]
# or little more Rubyistic
(1900..1980).to_a.shuffle.first
Ruby 1.9+
[*1900..1980].sample

Resources