--------------040807010102050900020906
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Andreas Schwarz wrote:

> pmak / aaanime.net wrote:
>
>>     source['salary']  ource['salary'].to_f -
>> encumbered['salary'].to_f
>
>
> Don't use Float for monetary values. Here's why: 
> http://www-106.ibm.com/developerworks/java/library/j-jtp0114/
> The BigDecimal class is what you need.
>
Or something like the attached.


-- 
Best regards,

Alexey Verkhovsky

Ruby Forum: http://ruby-forum.org        (moderator)
RForum:     http://rforum.andreas-s.net  (co-author)
Instiki:    http://instiki.org           (maintainer)


--------------040807010102050900020906
Content-Type: text/plain;
 nameoney.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filenameoney.rb"

class Money

  MAX_AMOUNT  e15 - 1 unless defined? MAX_AMOUNT

  def initialize(amount, rounding_unit  )
    raise "Rounding unit cannot be zero or negative" if rounding_unit < 
    amount  mount.to_f * 100.0 unless amount.kind_of? Numeric
    @amount  ound(amount.round, rounding_unit)
    raise "Amount is too big: '#{amount}'" if MAX_AMOUNT < @amount.abs
  end
  
  def to_f
    @amount / 100.0
  end
  
  def to_cents
    @amount
  end
  
  def to_s
    '%.2f' % self.to_f
  end
  
  def another)
    another.kind_of? Money and @amount another.to_cents
  end
  
  # basic arithmetic operations
  def *(factor) (@amount * factor).cents; end
  def /(factor) (@amount / factor).cents ;end
  def +(arg) (@amount + arg.to_cents).cents; end
  def -(arg) (@amount - arg.to_cents).cents; end
  
  private

  def round(number, rounding_unit)
    (number + rounding_unit / 2).divmod(rounding_unit)[0] * rounding_unit
  end

end

class Numeric
  def cents() Money.new(self); end
  def dollars() Money.new(self * 100); end
  alias :cent :cents
  alias :dollar :dollars
end

--------------040807010102050900020906
Content-Type: text/plain;
 nameoney_test.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filenameoney_test.rb"

#!/usr/bin/env ruby -w
require File.expand_path(File.dirname(__FILE__) + '/../../lib/app')
require 'test/unit'

class MoneyTest < Test::Unit::TestCase
  
  def test_new_from_int
    assert_equal 500, Money.new(500).to_cents
  end

  def new_from_float
    assert_equal 10239, Money.new(10239.49).to_cents
  end
  
  def test_new_from_string
    assert_equal 123456789, Money.new('1234567.89').to_cents
  end
  
  def test_new_from_another_money
    assert_equal 54321, Money.new(Money.new(54321)).to_cents
  end
  
  def test_shortcuts
    assert_equal Money.new(500), 5.dollars
    assert_equal Money.new(297), 297.cents
    assert_equal Money.new(100), 1.dollar
    assert_equal Money.new(1), 1.cent
  end

  def test_amount_limits
    fourteen_nines  '9' * 14).to_i

    assert_equal(fourteen_nines, Money.new('999999999999.99').to_cents)
    assert_equal(-fourteen_nines, Money.new('-999999999999.99').to_cents)

    assert_raises(RuntimeError) { Money.new(1e15) }
    assert_raises(RuntimeError) { Money.new(-1e15) }
  end

  def test_rounding
    # rounding down
    assert_equal 0.cents, 0.004999.dollars
    # rounding up
    assert_equal 1.cent, 0.005001.dollars
    # negative, rounding down
    assert_equal(-1.cent, -0.005001.dollars)
    # negative, rounding up
    assert_equal 0.cents, -0.004999.dollars
    # rounding up a negative to the nearest full dollar
    assert_equal 0.cents, Money.new(-50, 100)
    # rounding down a negative to the nearest full dollar
    assert_equal -1.dollar, Money.new(-51, 100)
    # zero rounding unit
    assert_raise(RuntimeError) { Money.new(50, 0) }
    assert_raise(RuntimeError) { Money.new(0, 0) }
    # negative rounding unit
    assert_raise(RuntimeError) { Money.new(0, -1) }
  end

  def test_rounding_to_explicit_rounding_unit
    assert_equal 1.10.dollars, Money.new(112.4999, 5)
    assert_equal 1.15.dollars, Money.new(112.5, 5)
    assert_equal 55.cents, Money.new(56.25, 5)
  end

  def test_multiply
    assert_equal 10.cents, 2.cents * 5
    assert_equal(-27.34.dollars, -13.67.dollars * 2)
    assert_equal 0.cents, 100.dollars * 0
  end

  def test_add
    assert_equal 10.03.dollars, 10.dollars + 3.cents
    assert_equal(-35.dollars, -20.dollars + -15.dollars)
  end
  
  def test_subtract
    assert_equal 9.99.dollars, 10.dollars - 1.cent
  end
  
  def test_divide
    assert_equal 2.50.dollars, 5.dollars / 2
    assert_raises(ZeroDivisionError) { 0.cents / 0 }
  end
  
  def test_to_f
    assert_in_delta 2.50, 250.cents.to_f, 0.001
    assert_in_delta -999999999999.99, -99999999999999.cents.to_f, 0.001
    assert_in_delta 999999999999.99, 99999999999999.cents.to_f, 0.001
    assert_equal 2.50, 2.502689.dollars.to_f
  end

  def test_to_cents
    assert_equal -99999999999999, -99999999999999.cents.to_cents
    assert_equal 99999999999999, 99999999999999.cents.to_cents
  end

  def test_to_s
    assert_equal '1234567.89', 1234567.89.dollars.to_s
    assert_equal '1.23', 1.23.dollars.to_s
    assert_equal '1.20', 1.20.dollars.to_s
    assert_equal '1.00', 1.dollars.to_s
    assert_equal '0.00', 0.dollars.to_s
    assert_equal '-1.00', -1.dollars.to_s
    assert_equal '-1.20', -1.20.dollars.to_s
    assert_equal '-1.23', -1.23.dollars.to_s
    assert_equal '-1234567.89', -1234567.89.dollars.to_s
    
    assert_equal '999999999999.99', 99999999999999.cents.to_s
    assert_equal '-999999999999.99', -99999999999999.cents.to_s
    assert_equal '-100000000000.00', -10000000000000.cents.to_s
  end
  
  def test_equality
    hundred  00.dollars

    assert hundred hundred
    assert hundred 10000.cents
    assert !(hundred 0.cents)
    assert !(hundred 'a string')
    assert !(hundred 100.00)
    assert !(hundred 10000)
    assert !(hundred nil)

    assert hundred ! .cent
    assert hundred ! il
    assert !(hundred ! 00.dollars)
    assert !(hundred ! undred)
  end

end

--------------040807010102050900020906--