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
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
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.*().
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))
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.
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.
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.
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
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).
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.
That’s a cool snippet! Thx Joe! it will help me a lot!
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.
I think the only options in ruby today are
BigDecimal.new “7.1”
and
7.1.to_bd # make your own method
Cheers!
-r