Ruby BigDecimal - Having Conceptual Problems - ruby

I am trying to transfer money from one "account" to another:
puts ("\nTransfer how much?")
require 'bigdecimal'
amount = gets.chomp
amount = BigDecimal(amount) #<--Does BigDecimal have to work with strings???
puts ("\nTransfer to which account?")
acct_to = gets.chomp.to_i #<--Accounts are numbered previously for the user.
acct_to = acct_to - 1 #<--Since the account numbers are stored in an array...
#having a problem with #{amount} in the string below. It is showing something like
#1.2E0???
puts ("\nTransfer #{amount} from #{names[acct_from]} to #{names[acct_to]}? [1 - yes] / [2 - no]")
#Makes transfer==============================
e = gets.chomp.to_i
if e == 1
puts ("\nTransferring!")
sum1 = 0
sum2 = 0
sum1 = BigDecimal(ac[names[acct_from]].to_s) #<-- ac is a hash
sum2 = BigDecimal(ac[names[acct_to]].to_s)
ac[names[acct_from]] = sum1 - amount
ac[names[acct_to]] = sum2 + amount
puts ("\n#{names[acct_from]}'s new balance is #{ac[names[acct_from]]}.")
puts ("\n#{names[acct_to]}'s new balance is #{ac[names[acct_to]]}.")
end
end
Ok, I have this working really well with numbers operating as floats; however, as you know, floats are causing problems.
Please help me get an introductory grasp of how bigdecimal works.
Also, if you are really awesome, help me get it to work in this specific situation.

First, if it is working with floats, you can get it to work with BigDecimal as well, and you should, because of the obvious reasons.
So, to answer your first question in the comments to your code: Yes, BigDecimal instantiation has to work with strings, the reason is quite obvious: Stringified number values are not prone to any inaccuracies and do not share the limits of float representation:
# Think of this number
float = 23.12323423142342348273498721348923748712340982137490823714089374
# Ruby will truncate its precision to 17, because Float's are not made for precise, but for fast calculation
float #=> 23.123234231423424
# Now, if BigDecimal would initialize with a float value, the precision would get lost on the way, too. Therefore, BigDecimal needs strings
big_decimal_from_float = BigDecimal.new(23.12323423142342348273498721348923748712340982137490823714089374.to_s)
big_decimal_from_string = BigDecimal.new("23.12323423142342348273498721348923748712340982137490823714089374")
# Now you'll see that the BigDecimal initialized "with a float value" will have lost some precision
To answer your second question, 1.2E0 is just Scientific Notation for 1.2. BigDecimal always uses Scientific Notation since it is intented for use in really precise calculations used in science and financial math.
To comment on your example, using BigDecimal is surely the right way to go, but you have to use it throughout and store your values accordingly. That means that if you write to an SQL database, you will have to use a decimal format with the right precision. Also, all instantiations thereform have to be instances of BigDecimal and never Float. One float in your entire finance application can rain on your parade if you intend to do financial math with really tiny fractures or high values.
To relieve you of some of the pitfalls of money handling, have a look at the Exchange Gem. I wrote it to have a way of representing money in a ruby application using BigDecimal and ISO4217 compatible instantiation. It may help you handling money throughout an application and avoid some of the pitfalls involved.

I suggesting you to use this gem: github/RubyMoney/money
Read a bit more about it. It is working out-of-the-box. It use nor float, nor BigDecimal, but just integers. So no precision loss at all.

Related

How to read percentage as decimal number?

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

String to BigNum and back again (in Ruby) to allow circular shift

As a personal challenge I'm trying to implement the SIMON block cipher in Ruby. I'm running into some issues finding the best way to work with the data. The full code related to this question is located at: https://github.com/Rami114/Personal/blob/master/Simon/Simon.rb
SIMON requires both XOR, shift and circular shift operations, the last of which is forcing me to work with BigNums so I can perform the left circular shift with math rather than a more complex/slower double loop on byte arrays.
Is there a better way to convert a string to a BigNum and back again.
String -> BigNum (where N is 64 and pt is a string of plaintext)
pt = pt.chars.each_slice(N/8).map {|x| x.join.unpack('b*')[0].to_i(2)}.to_a
So I break the string into individual characters, slice into N-sized arrays (the word size in SIMON) and unpack each set into a BigNum. That appears to work fine and I can convert it back.
Now my SIMON code is currently broken, but that's more the math I think/hope and not the code. The conversion back is (where ct is an array of bignums representing the ciphertext):
ct.map { |x| [x.to_s(2).rjust(128,'0')].pack('b*') }.join
I seem to have to right-justify pad the string as bignums are of undefined width so I have no leading 0s. Unfortunately the pack requires the defined with to have sensible output.
Is this a valid method of conversion? Is there a better way? I'm not sure on either count and hoping someone here can help out.
E: For #torimus, the circular shift implementation I'm using (From link above)
def self.lcs (bytes, block_size, shift)
((bytes << shift) | (bytes >> (block_size - shift))) & ((1<< block_size)-1)
end
If you would be equally happy with unpack('B*') with msb first binary numbers (which you could well be if all your processing is circular), then you could also use .unpack('Q>') instead of .unpack('B*')[0].to_i(2) for generating pt:
pt = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890!#"
# Your version (with 'B' == msb first) for comparison:
pt_nums = pt.chars.each_slice(N/8).map {|x| x.join.unpack('B*')[0].to_i(2)}.to_a
=> [8176115190769218921, 8030025283835160424, 7668342063789995618, 7957105551900562521,
6145530372635706438, 5136437062280042563, 6215616529169527604, 3834312847369707840]
# unpack to 64-bit unsigned integers directly
pt_nums = pt.unpack('Q>8')
=> [8176115190769218921, 8030025283835160424, 7668342063789995618, 7957105551900562521,
6145530372635706438, 5136437062280042563, 6215616529169527604, 3834312847369707840]
There are no native 128-bit pack/unpacks to return in the other direction, but you can use Fixnum to solve this too:
split128 = 1 << 64
ct = pt # Just to show round-trip
ct.map { |x| [ x / split128, x % split128 ].pack('Q>2') }.join
=> "\x00\x00\x00\x00\x00\x00\x00\x00qwertyui . . . " # truncated
This avoids a lot of the temporary stages on your code, but at the expense of using a different byte coding - I don't know enough about SIMON to comment whether this is adaptable to your needs.

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

Order of operations question in Ruby

I'm initializing an instance of a class that tests the equality of two formulas.
The formula's calculated values are in fact equal:
RubyChem::Chemical.new("SOOS").fw
=> 96.0
RubyChem::Chemical.new("OSSO").fw
= 96.0
When I created a new class to check the equality of these two instances I'm a bit surprised by the results:
x = RubyChem::BalanceChem.new("SOOS","OSSO")
x.balanced
=>false
y = RubyChem::BalanceChem.new("SOOS","SOOS")
y.balanced
=> true
the RubyChem::BalanceChem initialize method is here:
def initialize(formula1, formula2)
#balanced = RubyChem::Chemical.new(formula1).fw == RubyChem::Chemical.new(formula2).fw
end
Why doesn't ruby fetch the fw values for formula1 and formula2 and check the equality of those values? What are the order of operations in Ruby and what is Ruby doing? I can see I lack an understanding of this issue. How can I make this work? Thank you in advance.
Ruby 1.8 has a bug when converting floats to string. Sometimes the given string not a good representation of the float. Here is an example with 0.56:
0.5600000000000005.to_s == 0.56.to_s #=> true
# should have returned false, since:
0.5600000000000005 == 0.56 #=> false
This explains why two apparently identical results are not actually identical.
You probably want to do compare within a certain margin of error, do some rounding before doing a comparison, or use exact types like BigDecimal or Rational.
You probably do not want to check floating point numbers for equality. Instead, you should compare deltas.
Try this in irb:
x = 1.000001
y = 1.0
x == y
(x-y).abs < 0.00001
So, you find a delta like 0.00001 that you feel would handle any variation in floating point arithmetic, and use it that way. You should never == floats.
This is likely another problem caused by floating point precision.
I can assure you that those values calculated before the equality is evaluated.
See the Ruby's operator precedence

Resources