Forum: JRuby java.math.BigDecimal support in JRuby

1bc80e2eee2adeaa8bb577798d92e9d0?d=identicon&s=25 Anton Arhipov (Guest)
on 2009-11-13 11:59
(Received via mailing list)
Hello!


I'm evaluating JRuby as an alternative for defining business rules that
are
written by the business user. I was able to define my own syntax for the
rules DSL and created the editor for the language.

But math is the last issue where I'm stuck at the moment.

Ruby's 4.5 is a Float type. But I'm working with monetary values and due
to
the precision issues I'd like it to be java.math.BigDecimal
automagically.
So the question is, would it be possible, if I'm writing 6 * 7.1 it is
automatically translated to java.math.BigDecimal(6) *
java.math.BigDecimal(7.1)  in a particular module? Or is there a
metaprogramming trick for this problem?


Here's what I have tried.

irb(main):001:0> require 'java'
=> false
irb(main):002:0> a = java.math.BigDecimal.new 7.1
=> #<Java::JavaMath::BigDecimal:
0x932fe>
irb(main):003:0> puts a
7.0999999999999996447286321199499070644378662109375
=> nil
irb(main):004:0> b = java.math.BigDecimal.new 6
=> #<Java::JavaMath::BigDecimal:0x1e9d9b1>
irb(main):005:0> class Java::JavaMath::BigDecimal
irb(main):006:1>    def * (o)
irb(main):007:2>      self.multiply(o)
irb(main):008:2>    end
irb(main):009:1> end
=> nil
irb(main):010:0> c = a * b
=> #<Java::JavaMath::BigDecimal:0xe85825>
irb(main):011:0> puts c
42.5999999999999978683717927196994423866271972656250
=> nil
irb(main):012:0> a = java.math.BigDecimal.new "7.1"
=> #<Java::JavaMath::BigDecimal:0xad339b>
irb(main):013:0> c = a * b
=> #<Java::JavaMath::BigDecimal:0x14c4d61>
irb(main):014:0> puts c
42.6
=> nil


Thx!

Anton

--
Anton Arhipov
http://arhipov.blogspot.com
22785d4dbf585723bf60458ece0170e1?d=identicon&s=25 Joseph Athman (Guest)
on 2009-11-13 21:28
(Received via mailing list)
So are you saying that you want a user to be able to write math
statements
in your DSL and have them translated in to big decimals?  Or you want
every
time you write a float literal in your Ruby code to have JRuby magically
convert it to use a big decimal?

Joe
B252649cb7e16bde9f42809f78e1c9ee?d=identicon&s=25 Craig Taverner (craig-taverner)
on 2009-11-13 22:35
(Received via mailing list)
Here is a crazy idea - how about overriding Float.*():
  class Float
    def *(other)

java.math.BigDecimal.new(self).multiply(java.math.BigDecimal.new(other))
    end
  end

Then typing 7.1*6 would convert both to BigDecimal before multiplying.

You might have to do this for Fixnum too, since your example of '6 *
7.1'
would call Fixnum.*().
1bc80e2eee2adeaa8bb577798d92e9d0?d=identicon&s=25 Anton Arhipov (Guest)
on 2009-11-13 23:53
(Received via mailing list)
Hello Joe,

the perfect case would be really that if I write a float literal then it
would convert it to a big decimal.

7.1, it gets translated to java.math.BigDecimal.new "7.1" , not to
java.math.BigDecimal.new 7.1 as it looses the precision.
1bc80e2eee2adeaa8bb577798d92e9d0?d=identicon&s=25 Anton Arhipov (Guest)
on 2009-11-13 23:56
(Received via mailing list)
Hello Craig!

I came up with almost the same solution at the end of the day. just
slightly
different:

java.math.BigDecimal.new(self.to_s).multiply(java.math.BigDecimal.new(other.to_s))
B252649cb7e16bde9f42809f78e1c9ee?d=identicon&s=25 Craig Taverner (craig-taverner)
on 2009-11-14 01:12
(Received via mailing list)
Cool.

Pity about the need for to_s. One would think that since to_s handles
the
lack of precision, BigDecimal should be able to do the same with a raw
Float, without needing Float.to_s to do it.
1bc80e2eee2adeaa8bb577798d92e9d0?d=identicon&s=25 Anton Arhipov (Guest)
on 2009-11-14 01:26
(Received via mailing list)
yeah, it looks odd, but that's how it is.

I'm wondering if it would be possible to somehow override the initialize
method of a class so that it would actually return the other class
instance,
e.g.

require 'java'

class Float
   def initialize
      java.math.BigDecimal.new self
   end
end

it maybe looks even odder, but if it worked this way all the float
literals
in my program could be converted to decimals right at the creation
point,
without all the nasty method definitions for every single class.
B252649cb7e16bde9f42809f78e1c9ee?d=identicon&s=25 Craig Taverner (craig-taverner)
on 2009-11-14 01:35
(Received via mailing list)
I suspect you would need to look into the parsing code of JRuby, and
probably modify some internals, so your custom version makes BigDecimals
instead of Float and Fixnum. I've not looked into that code myself, so I
have no good advice. (obviously such a customized JRuby would start
failing
Ruby tests, but if the only use case is the DSL, that should not be a
problem).
22785d4dbf585723bf60458ece0170e1?d=identicon&s=25 Joseph Athman (Guest)
on 2009-11-14 02:43
(Received via mailing list)
Do you have an example of where just using normal ruby numbers does not
work
correctly?

arthman:~ jjathman$ jirb
irb(main):001:0> 7.1 * 6
=> 42.6
irb(main):002:0>

Seems fine to me and doesn't lose precision...at least when it gets
printed
it doesn't look as though it has lost precision.

Joe
22785d4dbf585723bf60458ece0170e1?d=identicon&s=25 Joseph Athman (Guest)
on 2009-11-14 04:16
(Received via mailing list)
Maybe this could get you started on overriding the methods in Float in
an
easier way.


require 'java'
class Float
  method_hash = {
    :* => :multiply,
    :- => :subtract,
    :+ => :add
  }

  method_hash.each_pair do |float_method, bd_method|
    define_method(float_method.to_sym) do |other|
      other_bd = java.math.BigDecimal.new(other.to_s)
      java.math.BigDecimal.new(self.to_s).send(bd_method, other_bd)
    end
  end
end

puts (7.1 * 6.0).class
puts (5.0 - 1.1).class
puts (7.1 + 6.0).class


outputs:
Java::JavaMath::BigDecimal
Java::JavaMath::BigDecimal
Java::JavaMath::BigDecimal



For the simple cases this will override the way Float works and use the
BigDecimal equivalent.  This won't always work though, for divide
BigDecimal
will throw exceptions for division like 1.0 / 3.0 will throw an
exception
since there is no exact representation of this number.
1bc80e2eee2adeaa8bb577798d92e9d0?d=identicon&s=25 Anton Arhipov (Guest)
on 2009-11-14 23:02
(Received via mailing list)
I can think of a case, when one "value" (say a bigdecimal) comes via the
context from java into jruby script, and I want it to be used in some
calculations.

So it will be: value * 7.1.It you try to wrap 7.1 directly to bigdecimal
and
print it out, you'll get 7.09999....

If the type is Float and is represented in machine word, then it is
inevitable that there will be a loss of precision.
1bc80e2eee2adeaa8bb577798d92e9d0?d=identicon&s=25 Anton Arhipov (Guest)
on 2009-11-14 23:03
(Received via mailing list)
That's a cool snippet! Thx Joe! it will help me a lot!
Bec38d63650c8912b6ba9b557fb953b9?d=identicon&s=25 Roger Pack (rogerdpack)
on 2011-02-24 01:07
I think the only options in ruby today are
BigDecimal.new "7.1"
and
7.1.to_bd # make your own method
Cheers!
-r
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.