Ruby / Numbers

From WhyNotWiki
Jump to: navigation, search

Contents

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

Part of: [Facets (category)]

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

sprintf

A shorthand notation: format % value

irb -> "%.20f" % (1.8 + 0.1)
    => "1.90000000000000013323"

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. Retrieved on 2007-05-11 11:18.


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+030
My 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.


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). Retrieved on 2007-05-11 11:18.


http://www.ruby-forum.com/topic/48754. Retrieved on 2007-05-11 11:18.


> 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


Problem

You’re doing high-precision arithmetic, and floating-point numbers are not precise enough.

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"

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.


http://pleac.sourceforge.net/pleac_ruby/numbers.html. Retrieved on 2007-05-11 11:18.


...

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

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)

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}"

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

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)

...

Comparing Floating-Point Numbers

Comparing Floating-Point Numbers (http://ajax.stealthsettings.com/tutorial/comparing-floating-point-numbers.html). Retrieved on 2007-05-11 11:18.


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.9000000000000001332267629550187848508358001708984375000

Both 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-16

This 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)    # => false

The 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
Ads
Personal tools