Forum: JRuby java.math.BigDecimal support in JRuby

Posted by 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
Posted by 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
Posted by 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.*().
Posted by 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.
Posted by 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))
Posted by 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.
Posted by 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.
Posted by 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).
Posted by 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
Posted by 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.
Posted by 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.
Posted by 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!
Posted by 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 (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.