How to read percentage as decimal number? - ruby

I'm trying to find the decimal value from a percentage that a user inputs.
For example, if a user inputs "15", i will need to do a calculation of 0.15 * number.
I've tried using .to_f, but it returns 15.0:
15.to_f
#=> 15.0
I also tried to add 0. to the beginning of the percentage, but it just returns 0:
15.to_s.rjust(4, "0.").to_i
#=> 0

Divide by 100.0
The easiest way to do what you're trying to do is to divide your input value by a Float (keeping in mind the inherent inaccuracy of floating point values). For example:
percentage = 15
percentage / 100.0
#=> 0.15
One benefit of this approach (among others) is that it can handle fractional percentages just as easily. Consider:
percentage = 15.6
percentage / 100.0
#=> 0.156
If floating point precision isn't sufficient for your use case, then you should consider using Rational or BigDecimal numbers instead of a Float. Your mileage will very much depend on your semantic intent and accuracy requirements.
Caveats
Make sure you have ahold of a valid Integer in the first place. While others might steer you towards String#to_i, a more robust validation is to use Kernel#Integer so that an exception will be raised if the value can't be coerced into a valid Integer. For example:
print "Enter integer: "
percentage = Integer gets
If you enter 15\n then:
percentage.class
#=> Integer
If you enter something that can't be coerced to an Integer, like foo\n, then:
ArgumentError (invalid value for Integer(): "foo\n")
Using String#to_i is much more permissive, and can return 0 when you aren't expecting it, such as when called on nil, an empty string, or alphanumeric values that don't start with an integer. It has other interesting edge cases as well, so it's not always the best option for validating input.

I'm trying to find the amount from a percentage that a user inputs
If you retrieve the input via gets, you typically convert it to a numeric value first, e.g.
percentage = gets.to_i
#=> 15
Ruby is not aware that this 15 is a percentage. And since there's no Percentage class, you have to convert it into one of the existing numeric classes.
15% is equal to the fraction 15/100, the ratio 15:100, or the decimal number 0.15.
If you want the number as a (maybe inexact) Float, you can divide it by 100 via fdiv:
15.fdiv(100)
#=> 0.15
If you prefer a Rational you can use quo: (it might also return an Integer)
15.quo(100)
#=> (3/20)
Or maybe BigDecimal for an arbitrary-precision decimal number:
require 'bigdecimal'
BigDecimal(15) / 100
#=> 0.15e0
BigDecimal also accepts strings, so you could pass the input without prior conversion:
input = gets
BigDecimal(input) / 100
#=> 0.15e0

Related

Using Ruby, is it possible to print out BigDecimal("1") / BigDecimal("3") as 0.3333333333333... to any length?

If I do
require "bigdecimal"
"%60.50f" % [BigDecimal("1") / BigDecimal("3")]
=> " 0.33333333333333331482961625624739099293947219848633"
Is it possible to print out the result to any length with all 3s at the end? (That is 0.333333333333... to any length?)
You are observing a loss of precision there because you are formatting the BigDecimal object to a Float first (because of your f format flag) which can't represent arbitrary precision floating point numbers.
However, you can use BigDecimal#to_s to format a Bigdecimal object as a string without any loss of precision. Here, the output will use as much precision as is available in the object you are formatting.
When creating your object with BigDecimal#/, it will use some predefined precision, on my box this was 36 on Ruby 3.1. This will result in 36 decimal numbers being printed by default:
result = BigDecimal("1") / BigDecimal("3")
result.precision
# => 36
result.to_s('F')
# => "0.333333333333333333333333333333333333"
If you desire higher precision in the resulting BigDecimal object after your division, you can use BigDecimal#quo instead:
result = BigDecimal("1").quo(BigDecimal("3"), 60)
result.precision
# => 60
result.to_s('F')
# => "0.333333333333333333333333333333333333333333333333333333333333"

Float with 'unnecessary' digits

I need to make a number out of the string. to do that i use well known maneuver looking something like this:
Float(string_var) rescue nil
This works nicely, however I do have a tiny, little problem. If a string is "2.50", variable I get is 2.5. Is it even possible to create Float object with 'unnecessary' 0 digit at the end? can I literally translate "2.50" into 2.50 ?
In short, the answer is no, given the question, as any Float, when examined, will use Float's to_s function, eliciting an answer without trailing zeroes.
Float will always give you a numeric value that can be interpreted any way you wish, though. In your example, you will get a float value (given a string that is a parsable float). What you are asking then, is how to display that value with trailing zeroes. To do that, you will be turning the float value back into a string.
Easiest way to accomplish that is to use the format given by one of your respondents, namely
string_var = "2.50"
float_value = Float(string_var) rescue nil # 2.5
with_trailing_zeroes = "%0.2f" % float_value # '2.50'

string.to_f precision issue when the value trailed by two zeros

I get input string "400.00" and need to display 400.00.
Expected float_value :400.00
I used to_f to do the task since to_i will return 400 alone. The code is
x="400.00"
float_value=x.to_f
But in this case, I am getting the output as 400.0 which is not acceptable for my case.
current float_value :400.0
Both are equal and have no difference, but its not for any calculation purpose, its for some other display purpose.
Use sprintf for fomatting:
sprintf("%.2f", 400)
#=> "400.00"

using probability for rounding decimals

What might be a simple Ruby way to round numbers using probability, i.e., based on how close the value is to one boundary or the other (floor or ceiling)?
For example, given a current price value of 28.33, I need to add 0.014.
Equivalent to starting with 28.34 and needing to add 0.004, but the final value must be rounded to two decimal places(which can be provided as parameter, or fixed for now).
The final value should therefore be:
28.34 with 60% chance, since it is that much closer, OR
28.35 with 40% random chance
The reason it occured to me this could serve best is that the application is stateless and independent across runs, but still needs to approximate the net effect of accumulating the less significant digits normally rounded into oblivion (eg. micropenny values that do have an impact over time). For example, reducing a stop-loss by some variable increment every day (subtraction like -0.014 above instead).
It would be useful to extend this method to the Float class directly.
How about:
rand(lower..upper) < current ? lower.round(2) : upper.round(2)
EDIT:
The above will only work if you use Ruby 1.9.3 (due to earlier versions not supporting rand in a range).
Else
random_number = rand * (upper-lower) + lower
random_number < current ? lower.round(2) : upper.round(2)
Wound up using this method:
class Float
def roundProb(delta, prec=2)
ivalue=self
chance = rand # range 0..1, nominally averaged at 0.5
# puts lower=((ivalue + delta)*10**prec -0.5).round/10.0**prec # aka floor
# puts upper=((ivalue + delta)*10**prec +0.5).round/10.0**prec # ceiling
ovalue=((ivalue + delta)*10**prec +chance-0.5).round/10.0**prec # proportional probability
return ovalue
rescue
puts $#, $!
end
end
28.33.roundProb(0.0533)
=> 28.39
Maybe not the most elegant approach but seems to work for the general case of any precision, default 2. Even works on Ruby 1.8.7 I'm stuck with in one case, which lacks a precision parameter to round().

Convert cents into dollar string in Ruby without use of BigDecimal

I want to convert from cents to dollars correctly in Ruby. I will never have to work with fractions of cents.
Is it possible to do this correctly (without floating point errors) without having to use BigDecimal?
E.g., cents to dollars
"99" => "0.99"
"324" => "3.24"
The following seems to work, but is it correct?
(cents.to_i/100.0).to_s
Update: I noticed the line above doesn't work if cents = "10287349283923497624861294712974892742837833".
As Micheal Kohl already answered: Take a look to the money gem.
Example:
require 'money'
Money.use_i18n = false #https://stackoverflow.com/q/31133229/676874
puts Money.new( 99, 'USD')
puts Money.new(324, 'USD')
The following seems to work, but is it correct?
(cents.to_i/100.0).to_s
On the first look, it is ok, but:
cents = '10'
p (cents.to_i/100.0).to_s # -> '0.1'
You don't have 2 digits.
Alternative:
p '%.2f' % (cents.to_i/100.0) # -> '0.10'
You can consider using Rationals as well. However, I am not sure do they get converted to floats when sprintf-ed:
"%.2f" % Rational("324".to_i,100)
#=> "3.24"
"%.2f" % Rational("99".to_i,100)
#=> "0.99"
"%.2f" % Rational("80".to_i,100)
#=> "0.80"
"%.2f" % Rational("12380".to_i,100)
#=> "123.80"
If they're stings already you could use string manipulation and bypass the numeric problems completely:
# There are, of course, all sorts of ways to do this.
def add_decimal(s)
pfx = [ '0.00', '0.0', '0.' ]
if(pfx[s.length])
s = pfx[s.length] + s
else
s = s.dup
s[-2, 0] = '.'
end
s
end
add_decimal('') # "0.00"
add_decimal('1') # "0.01"
add_decimal('12') # "0.12"
add_decimal('123') # "1.23"
add_decimal('1234') # "12.34"
add_decimal('12345') # "123.45"
No precision issues, no floating point, no bignums, no Rational, nothing tricky, nothing clever. Some simple modifications would be needed to deal with negative values but that will be as simple as what's already there.
Personally I wouldn't try to re-invent this specific wheel and go with the money gem. From the docs (emphasis added):
Features
Provides a Money class which encapsulates all information about an
certain amount of money, such as its value and its currency.
Provides a Money::Currency class which encapsulates all information about a
monetary unit.
Represents monetary values as integers, in cents. This
avoids floating point rounding errors.
Represents currency as
Money::Currency instances providing an high level of flexibility.
Provides APIs for exchanging money from one currency to another.
Has
the ability to parse a money and currency strings into the
corresponding Money/Currency object.
Here's a one-line method that also simply uses string manipulation thereby completely bypassing the numeric issues:
cents.rjust(3, "0").insert(-3, ".")
These answers are fairly old, so I wanted the next person to know there's an easier way (if you're using Rails).
ActiveSupport::NumberHelper.number_to_currency(111048.fdiv(100))
There's currency and precision options. See Rails documentation
You can use fdiv for this purpose. It returns the floating point result after division of two numbers
-> price.to_i.fdiv(100)
For example: '123'.to_i.fdiv(100) -> 1.23

Resources