--------------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; name oney.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename oney.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; name oney_test.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename oney_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--