3

Possible Duplicate:
In Ruby is there a way to overload the initialize constructor?

BigDecimal does not take a float as in initial value, so I am writing a constructor to handle it. It seems to be ignoring the initialize method and calling the default constructor.

It throws TypeError can't convert Float into String (TypeError)

The method format does work.

file BigDecimal.rb:

require 'bigdecimal'

class BigDecimal

    def initialize
        if self.class == Float
            super self.to_s
        end
    end

    def format
        sprintf("%.2f", self)
    end

end

Then, in file test.rb:

require 'BigDecimal' # => true
bd = BigDecimal.new('54.4477') # works
puts bd.format # 54.44
bd2 = BigDecimal.new(34.343) # TypeError: can't convert Float into String (TypeError)

Ruby 1.9.2

Community
  • 1
  • 1
B Seven
  • 44,484
  • 66
  • 240
  • 385
  • http://stackoverflow.com/a/3958786/643500 – Sully Jun 21 '12 at 15:56
  • You override the constructor with a version that takes no arguments. Sure you testing code works? If yes, this means that initialize is never even called (it's buggy anyway). Try subclassing BigDecimal instead. Subclassing is preferrable over monkeypatching most of the time. – Niklas B. Jun 21 '12 at 15:58

2 Answers2

7

Problems with your code:

  1. You use a monkey patch instead of inheriting, so in your initialize method, super will call the initialize method of Object, which is the super class of BigDecimal. To call the default constructor, you have to use some other method as showing below.

  2. You did not put arguments for the initialize method.

  3. BigDecimal DOES take float as constructor argument

Therefore,

  1. You can use directly the default constructor and pass a float as:

    BigDecimal.new(34.343, 5) # 5 is the precision
    
  2. You can override the constructor in this way:

NOTE: we usually alias initialize method. However in this case this does not seem to work (for some unknown reason that initialize does not get called)... So we have to alias new method which is more fundamental.

    require 'bigdecimal'

    class BigDecimal

        class << self
          alias_method :__new__, :new  #alias the original constructor so we can call later
          def new(*args)
              if args.length == 1 && args[0].is_a?(Float)
                  __new__(args[0].to_s)
              else
                  __new__(*args)
              end
          end
        end

        def format
            sprintf("%.2f", self)
        end

    end

    BigDecimal.new(12.334)
    #<BigDecimal:10a9a48,'0.12334E2',18(18)>
SwiftMango
  • 15,092
  • 13
  • 71
  • 136
5

BigDecimal does not take a float as in initial value

Are you sure?

BigDecimal.new(2.4)
#=>ArgumentError: can't omit precision for a Rational.

So you have to give a precision as second argument:

BigDecimal.new(2.4, 2)
#=> #<BigDecimal:7ff689b0f2e8,'0.24E1',18(36)>

The docs don't indicate a change between 1.9.2 and 1.9.3.

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • BigDecimal.ver => "1.0.1" . Which version do you have? – B Seven Jun 22 '12 at 13:51
  • 1.1.0. But as I said, the 1.9.2 docs already suggest the same behavior. – Michael Kohl Jun 22 '12 at 14:38
  • I wonder if the docs or wrong or if there is an issue with my installation. I guess I'll just upgrade to 1.9.3. Thanks. – B Seven Jun 22 '12 at 15:04
  • I upgraded to 1.9.3p0 and BigDecimal now takes floats in the constructor (with precision as second argument). BigDecimal.ver => 1.1.0 – B Seven Jun 23 '12 at 03:06
  • @BSeven BigDecimal does not take a float as in initial value. If you wanna pass it then convert your value into string. For ex: BigDecimal.new("1020.0"). This is right approach. Try this BigDecimal.new(1020.0, 2).to_f => 1000.0(This is wrong approach) – Ranjithkumar Ravi Nov 19 '14 at 11:11