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
on 2009-11-13 11:59
on 2009-11-13 21:28
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
on 2009-11-13 22:35
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.*().
on 2009-11-13 23:53
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.
on 2009-11-13 23:56
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))
on 2009-11-14 01:12
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.
on 2009-11-14 01:26
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.
on 2009-11-14 01:35
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).
on 2009-11-14 02:43
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
on 2009-11-14 04:16
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.
on 2009-11-14 23:02
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.
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
Log in with Google account | Log in with Yahoo account
No account? Register here.