Convert String to Float::INFINITY - ruby

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

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

Accidental Type conversion in Ruby with strings

I'm trying to solve a challenge where you take in a string of words but return the longest word in the string. My strategy is to break the string into an array of individual words and search the array. However, I'm getting a type conversion error. What is causing the type conversion error? This is particularly strange to me because I don't actually see any type conversion happening here.
def LongestWord(sen)
sen1 = sen.split("/\W+/")
grt = 0
sen1.each do |i|
if sen1[i].length > sen1[grt].length # Type conversion error
grt = i
end
end
sen1[grt]
end
# keep this function call here
puts LongestWord(STDIN.gets)
The type conversion is caused by the array entry i being converted (probably unsuccessfully) into an integer (though I suppose it could be ruby trying to convert the array into a hash, and use i as a key to the hash).
Your misunderstanding is that you think you're getting the array's indices passed into the block for each. What is passed in to that block is each individual value in the array. I.e., if your string sen is 'this is a silly string', then the values passed are 'this', 'is', 'a', 'silly', and 'string'.
You get the error because, when the code is running, i is the first value of sen1, which results in sen1['some string'] being evaluated.
An array can't have a string index, only a hash can, resulting in the Type error.
Meditate on this:
def longest_word(sen)
sen1 = sen.split # => ["foo", "barbaz"]
grt = 0
sen1.each do |i|
i # => "foo"
sen1 # => ["foo", "barbaz"]
sen1[i] # =>
sen1[grt] # =>
sen1[i].length # =>
sen1[grt].length # =>
if sen1[i].length > sen1[grt].length #Type conversion error
grt = i # =>
end
end
sen1[grt]
end
# keep this function call here
longest_word('foo barbaz')
Breaking it down further, here's the offending problem:
sen1 = 'foo barbaz'.split
sen1['foo'] # =>
# ~> TypeError
# ~> no implicit conversion of String into Integer
You don't see the type conversion, but it is there. In more than one place.
As Derrell Durrett pointed out in his answer, your are assuming (wrongly) that the index of the array is passed to the block, not its elements.
Then you write if sen1[i].length > sen1[grt].length. Let's consider the string is 'this is a silly string'. The first element is 'this' and what you are trying to do is if sen1['this'].length > sen1[0].length. As Ruby arrays always have integer indexes, Ruby tries to convert 'this' to an integer in order to find the element at the specified position. Of course this fails!
But your code is not that far from being right. A few small changes and it will run perfectly well:
def longest_word(sen)
sen1 = sen.split(" ")
grt = 0
sen1.each_index do |i|
if sen1[i].length > sen1[grt].length
grt = i
end
end
sen1[grt]
end
puts LongestWord(STDIN.gets)
Now you'd be passing the indexes with sen1.each_index and it'd be working fine.
Notice that I changed the name of your method to longest_word. This is much better, in fact, because this first capital letter is reserved to constants and class names.
I also would like to point that you are not using a good Ruby style. This could be written like this:
def longest_word(str)
str.split(" ").max_by{ |s| s.length }
end
and the result would be the same.

math expression Randomly rounding up

In the code below 2/5 results to 0.0 instead of 0.4 which should be correct answer
require 'rubygems'
require 'dentaku'
expression = '2/5'
calculator = Dentaku::Calculator.new
result = calculator.evaluate(expression).to_f
puts "#{result}"
I am using dentaku to evaluate the the math expression the documentation for this gem can be found here: https://github.com/rubysolo/dentaku/blob/master/README.md
Your first stop when searching for an answer to this should have been the official documentation for /:
fix / numeric → numeric_result
Performs division: the class of the resulting object depends on the class of numeric and on the magnitude of the result. It may return a Bignum.
For example:
3/2 # => 1
(3/2).class # => Fixnum
3/2.0 # => 1.5
(3/2.0).class # => Float
Ruby will return a Float if either value is a Float:
3.0/2 # => 1.5
(3.0/2).class # => Float
As far as I can tell, Dentaku treats division of two integers as integer division. Try 2/5.0.
Please try expression = '2.0/5.0' instead, maybe your numbers are considered as integers (you want floats) because of the missing ".0" and so you get an integer result of zero

Lazy-evaluation of a "#{}"-string in ruby

I started to put print-statements throughout my code. So as not to clutter up the output, I did something like:
dputs LEVEL, "string"
where LEVEL is 0 for errors, 1 for important .. 5 for verbose and is compared to DEBUG_LEVEL. Now my problem is, that in a statement like:
dputs 5, "#{big_class.inspect}"
the string is always evaluated, also if I set DEBUG_LEVEL to 1. And this evaluation can take a long time. My favourite solution would be something like:
dputs 5, '#{big_class.inspect}'
and then evaluate the string if desired. But I don't manage to get the string in a form I can evaluate. So the only think I could come up with is:
dputs( 5 ){ "#{big_class.inspect}" }
but this looks just ugly. So how can I evaluate a '#{}' string?
You could do this by having dputs use sprintf (via %). That way it can decide not to build the interpolated string unless it knows it's going to print it:
def dputs(level, format_str, *vars)
puts(format_str % vars) if level <= LEVEL
end
LEVEL = 5
name = 'Andrew'
dputs 5, 'hello %s', name
#=> hello Andrew
Or, as you suggest, you can pass a block which would defer the interpolation till the block actually runs:
def dputs(level, &string)
raise ArgumentError.new('block required') unless block_given?
puts string.call if level <= LEVEL
end
I think it's of no value whatsoever, but I just came up with:
2.3.1 :001 > s = '#{a}'
=> "\#{a}"
2.3.1 :002 > a = 1
=> 1
2.3.1 :003 > instance_eval s.inspect.gsub('\\', '')
=> "1"
2.3.1 :004 > s = 'Hello #{a} and #{a+1}!'
=> "Hello \#{a} and \#{a+1}!"
2.3.1 :005 > instance_eval s.inspect.gsub('\\', '')
=> "Hello 1 and 2!"
Don't use that in production :)
I don't think you can dodge the ugly there. The interpolation happens before the call to dputs unless you put it inside a block, which postpones it until dputs evaluates it. I don't know where dputs comes from, so I'm not sure what its semantics are, but my guess is the block would get you the lazy evaluation you want. Not pretty, but it does the job.
OK, obviously I was just too lazy. I thought there must be a more clean way to do this, Ruby being the best programming language and all ;) To evaluate a string like
a = '#{1+1} some text #{big_class.inspect}'
only when needed, I didn't find a better way than going through the string and eval all "#{}" encountered:
str = ""
"#{b}\#{}".scan( /(.*?)(#\{[^\}]*\})/ ){
str += $1
str += eval( $2[2..-2] ).to_s
}
if you're not into clarity, you can get rid of the temporary-variable str:
"#{b}\#{}".scan( /(.*?)(#\{[^\}]*\})/ ).collect{|c|
c[0] + eval( c[1][2..-2] ).to_s
}.join
The String.scan-method goes through every '#{}'-block, as there might be more than one, evaluating it (the 2..-2 cuts out the "#{" and "}") and putting it together with the rest of the string.
For the corner-case of the string not ending with a '#{}'-block, an empty block is added, just to be sure.
But well, after being some years in Ruby, this still feels clunky and C-ish. Perhaps it's time to learn a new language!

How do I round a float to a specified number of significant digits in Ruby?

It would be nice to have an equivalent of R's signif function in Ruby.
For example:
>> (11.11).signif(1)
10
>> (22.22).signif(2)
22
>> (3.333).signif(2)
3.3
>> (4.4).signif(3)
4.4 # It's usually 4.40 but that's OK. R does not print the trailing 0's
# because it returns the float data type. For Ruby we want the same.
>> (5.55).signif(2)
5.6
There is probably better way, but this seems to work fine:
class Float
def signif(signs)
Float("%.#{signs}g" % self)
end
end
(1.123).signif(2) # => 1.1
(11.23).signif(2) # => 11.0
(11.23).signif(1) # => 10.0
Here's an implementation that doesn't use strings or other libraries.
class Float
def signif(digits)
return 0 if self.zero?
self.round(-(Math.log10(self).ceil - digits))
end
end
I don't see anything like that in Float. Float is mostly a wrapper for the native double type and given the usual binary/decimal issues, I'm not that surprised that Float doesn't allow you to manipulate the significant digits.
However, BigDecimal in the standard library does understand significant digits but again, I don't see anything that allows you to directly alter the significant digits in a BigDecimal: you can ask for it but you can't change it. But, you can kludge around that by using a no-op version of the mult or add methods:
require 'bigdecimal'
a = BigDecimal.new('11.2384')
a.mult(1, 2) # the result is 0.11E2 (i.e. 11)
a.add(0, 4) # the result is 0.1124E2 (i.e. 11.24)
The second argument to these methods:
If specified and less than the number of significant digits of the result, the result is rounded to that number of digits, according to BigDecimal.mode.
Using BigDecimal will be slower but it might be your only choice if you need fine grained control or if you need to avoid the usual floating point problems.
Some of the previous answers and comments have alluded to this solution but this is what worked for me:
# takes in a float value and returns another float value rounded to
# given significant figures.
def round_to_sig_figs(val, sig_figs)
BigDecimal.new(val, sig_figs).to_f
end
You are probably looking for Ruby's Decimal.
You could then write:
require 'decimal/shortcut'
num = 1.23541764
D.context.precision = 2
num_with_2_significant_digits = +D(num.to_s) # => Decimal('1.2')
num_with_2_significant_digits.to_f # => 1.2000000000000002
Or if you prefer to use the same syntax add this as a function to class Float like this:
class Float
def signif num_digits
require 'decimal/shortcut'
D.context.precision = num_digits
(+D(self.to_s)).to_f
end
end
Usage would then be the same, i.e.
(1.23333).signif 3
# => 1.23
To use it, install the gem
gem install ruby-decimal
#Blou91's answer is nearly there, but it returns a string, instead of a float. This below works for me:
(sprintf "%.2f", 1.23456).to_f
So as a function,
def round(val, sig_figs)
(sprintf "%.#{sig_figs}f", val).to_f
end
Use sprintf if you want to print trailing zeros
2.0.0-p353 :001 > sprintf "%.3f", 500
=> "500.000"
2.0.0-p353 :002 > sprintf "%.4f", 500
=> "500.0000"
2.0.0-p353 :003 >

Resources