Ruby / Numbers
From WhyNotWiki
Contents |
[edit] round_to( n )
Rounds to the nearest _n_th degree.4.555.round_to(1) #=> 5.0 4.555.round_to(0.1) #=> 4.6 4.555.round_to(0.01) #=> 4.56 4.555.round_to(0) #=> 4.555# File lib/facets/core/float/round_to.rb, line 13 def round_to( n ) #n=1 return self if n == 0 (self * (1.0 / n)).round_off.to_f / (1.0 / n) end
Example: Say you have a string input and it needs to be converted to a numeric type and rounded to 2 places, in order to represent a currency...
price = price_input.to_f.round_to(0.01) # '4.555' => 4.56
[edit] sprintf
[edit] A shorthand notation: format % value
irb -> "%.20f" % (1.8 + 0.1)
=> "1.90000000000000013323"
[edit] Inaccuracy of floats
irb -> sprintf("%.20f", (0.2 - 0.05 - 0.15))
=> "0.00000000000000002776"
irb -> sprintf("%.20f", 1.9)
=> "1.89999999999999991118"
irb -> sprintf("%.20f", 1.8 + 0.1)
=> "1.90000000000000013323"
http://www.ruby-forum.com/topic/48754 Ruby Forum - Using Float For Currency.
Floating point numbers represent an extremely wide range of values - much wider than their integer counterparts. This is handled through an exponent and mantissa. For this ability, they trade off precision.
Think about the case of adding a large floating point number to a small floating point number:
irb> a = 1.0e30 => 1.0e+030 irb> b = 1.0e-30 => 1.0e-030 irb> a + b => 1.0e+030My comment:irb -> ("%.40f" % (a = 1.0e30)).rjust(80) => " 1000000000000000019884624838656.0000000000000000000000000000000000000000" irb -> ("%.40f" % (b = 1.0e-30)).rjust(80) => " 0.0000000000000000000000000000010000000000" irb -> ("%.40f" % (a - b)).rjust(80) => " 1000000000000000019884624838656.0000000000000000000000000000000000000000"While this is an extreme example, it does demonstrate the loss of precision. Essentially, in floating point arithmetic we're trying to squeeze much more out of, say 32 or 48 or 64 bits.
Integer arithmetic, on the other hand, is exact. And therefore so is fixed point arithmetic; however, fixed point doesn't enjoy the wide representation range as floats.
The bottom line is you should never use floating point when it comes to money. Eventually you're going to miss pennies. Instead represent things in the smallest denomination, such as cents, and fix it up in presentation, or use a custom Money column type, or data type.
[...] your program logic can also behave unexpectedly.
irb> 1.20 - 1.00 == 0.20 => false
One would use BigDecimal instead of Fixnum or Bignum when one is going to do some operation that might produce a fraction. If you're certain your operations will always result in whole numbers, though, I don't see
a point in dragging in BigDecimal.
[edit] BigDecimal
http://stdlib.rubyonrails.org/libdoc/bigdecimal/rdoc/index.html
Representing Numbers to Arbitrary Precision (http://ajax.stealthsettings.com/tutorial/representing-numbers-to-arbitrary-precision.html) (< 2007-05-01).
http://www.ruby-forum.com/topic/48754.
> amount = BigDecimal.new('9.756') > rounded = (amount * 100).round / 100 > printf('%.02f', rounded) > > Outputs '9.76'There is at least one point more to make about rounding: this method has a bias when floats ending with a 5 are involved. In the extreme case, when all you three-decimal currency amounts end in a 5 (like $9.755, $9.765) then all are rounded upwards. Instead, half of them should be rounded up, the other half down. A good method to do that is to round to the nearest even digit:
$9.755 -> $9.76 $9.765 -> $9.76 (not $9.77)The attached method round2 does this (although probably not very efficiently):
8.5.round2 #-> 8 9.765.round2(0.01) #-> 9.76
[edit] Problem
You’re doing high-precision arithmetic, and floating-point numbers are not precise enough.
[edit] Solution
A BigDecimal number can represent a real number to an arbitrary number of decimal places.
require 'bigdecimal' BigDecimal("10").to_s # => "0.1E2" BigDecimal("1000").to_s # => "0.1E4" BigDecimal("1000").to_s("F") # => "1000.0" BigDecimal("0.123456789").to_s # => "0.123456789E0"Compare how Float and BigDecimal store the same high-precision number:
nm = "0.123456789012345678901234567890123456789" nm.to_f # => 0.123456789012346 BigDecimal(nm).to_s # => "0.123456789012345678901234567890123456789E0"[edit] Discussion
BigDecimal numbers store numbers in scientific notation format. A BigDecimal consists of a sign (positive or negative), an arbitrarily large decimal fraction, and an arbitrarily large exponent. This is similar to the way floating-point numbers are stored, but a double- precision floating-point implementation like Ruby’s cannot represent an exponent less than Float::MIN_EXP (1021) or greater than Float::MAX_EXP (1024). Float objects also can’t represent numbers at a greater precision than Float::EPSILON, or about 2.2*10-16.
[edit]
http://pleac.sourceforge.net/pleac_ruby/numbers.html.
[edit] Making Numbers Even More Random
# from the randomr lib: # http://raa.ruby-lang.org/project/randomr/ ----> http://raa.ruby-lang.org/project/randomr/ require 'random/mersenne_twister' mers = Random::MersenneTwister.new 123456789 puts mers.rand(0) # 0.550321932544541 puts mers.rand(10) # 2[edit] Generating Biased Random Numbers
def gaussian_rand begin u1 = 2 * rand() - 1 u2 = 2 * rand() - 1 w = u1*u1 + u2*u2 end while (w >= 1) w = Math.sqrt((-2*Math.log(w))/w) [ u2*w, u1*w ] end mean = 25 sdev = 2 salary = gaussian_rand[0] * sdev + mean printf("You have been hired at \$%.2f\n", salary)[edit] Taking Logarithms
log_e = Math.log(val) log_10 = Math.log10(val) def log_base(base, val) Math.log(val)/Math.log(base) end answer = log_base(10, 10_000) puts "log10(10,000) = #{answer}"[edit] Multiplying Matrices
require 'matrix.rb' a = Matrix[[3, 2, 3], [5, 9, 8]] b = Matrix[[4, 7], [9, 3], [8, 1]] c = a * b a.row_size a.column_size c.det a.transpose[edit] Using Complex Numbers
require 'complex.rb' require 'rational.rb' a = Complex(3, 5) # 3 + 5i b = Complex(2, -2) # 2 - 2i puts "c = #{a*b}" c = a * b d = 3 + 4*Complex::I printf "sqrt(#{d}) = %s\n", Math.sqrt(d)...
[edit] Comparing Floating-Point Numbers
Comparing Floating-Point Numbers (http://ajax.stealthsettings.com/tutorial/comparing-floating-point-numbers.html).
Floating-point math is very precise but, due to the underlying storage mechanism for Float objects,not very accurate. Many real numbers (such as 1.9) can’t be represented by the floating-point standard. Any attempt to represent such a number will end up using one of the nearby numbers that does have a floating-point representation.You don’t normally see the difference between 1.9 and 1.8 + 0.1, because Float#to_s rounds them both off to “1.9″. You can see the difference by using Kernel#printf to display the two expressions to many decimal places:
printf("%.55f", 1.9) # 1.8999999999999999111821580299874767661094665527343750000 printf("%.55f", 1.8 + 0.1) # 1.9000000000000001332267629550187848508358001708984375000Both numbers straddle 1.9 from opposite ends, unable to accurately represent the number they should both equal. Note that the difference between the two numbers is precisely Float::EPSILON:
Float::EPSILON # => 2.22044604925031e-16 (1.8 + 0.1) - 1.9 # => 2.22044604925031e-16This EPSILON’s worth of inaccuracy is often too small to matter, but it does when you’re doing comparisons. 1.9+Float::EPSILON is not equal to 1.9-Float::EPSILON, even if (in this case) both are attempts to represent the same number. This is why most floating-point numbers are compared in relative terms.
The most efficient way to do a relative comparison is to see whether the two numbers differ by more than an specified error range, using code like this:
class Float def absolute_approx(other, epsilon=Float::EPSILON) return (other-self).abs <= epsilon end end (1.8 + 0.1).absolute_approx(1.9) # => true 10e10.absolute_approx(10e10+1e-5) # => falseThe default value of epsilon works well for numbers close to 0, but for larger numbers the default value of epsilon will be too small. Any other value of epsilon you might specify will only work well within a certain range.
Thus, Float#approx, the recommended solution, compares both absolute and relative magnitude. As numbers get bigger, so does the allowable margin of error for two numbers to be considered “equal.” Its default relative_epsilon allows numbers between 2 and 3 to differ by twice the value of Float::EPSILON. Numbers between 3 and 4 can differ by three times the value of Float::EPSILON, and so on.
A very small value of relative_epsilon is good for mathematical operations, but if your data comes from a real-world source like a scientific instrument, you can increase it. For instance, a Ruby script may track changes in temperature read from a thermometer that’s only 99.9% accurate. In this case, relative_epsilon can be set to 0.001, and everything beyond that point discarded as noise.
98.6.approx(98.66) # => false 98.6.approx(98.66, 0.001) # => true
