Colin Law wrote in post #970536:
> The more I think about it the more I think that the ruby
> implementation of BigDecimal is flawed. BigDecimal('1') /
> BigDecimal('3') should be stored as a rational (ie as two values, 1
> and 3 to be divided). Otherwise it seems to me that it is not 'proper'
> BigDecimal.  I have been unable to find documentation that fully
> defines what the result of 1/3 should be.

I think differently here (but I may be mistaken ...).

BigDecimal, for me, as the name implies, guarantees an "exact" 
representation of all numbers that are of the form:

[+-] (d(n)*10^n + d(n-1)*10^(n-1) + ... + d(0)*10^0 +  d(-1)*10^(-1) + 
... d(-m)*10^(-m))

with d(n), the digit n places to the left of unity and
d(-m), the digit m places to the right of the unit and the comma.
The ^ (caret) represents the "to the power of" function (e.g.
10^3 == 1000).

E.g. in the decimal number "3004.5006"
d(3) = 3
d(2) = 0
d(1) = 0
d(0) = 4
d(-1) = 5
d(-2) = 0
d(-3) = 0
d(-4) = 6

>> a = BigDecimal.new("3004.5006")
=> #<BigDecimal:b72706c0,'0.30045006E4',8(12)>

These numbers form a (mathematical) field for the operators:
+, -, *  , but NOT with the division operator.

The nice thing about BigDecimal is that it will automatically
expand it's precision if needed to make sure the representation remains
correct when using the functions: +, -, *. There is always a finite (and 
exact) precision that is sufficient to represent the result of

  a [+-*] b

Let's try it:

>> a = BigDecimal.new("1000000000000000000",12)
=> #<BigDecimal:b725ae60,'0.1E19',4(24)>
# (precision automatically larger than the 12 I requested)

>> b = BigDecimal.new("0.00000000000000003", 12)
=> #<BigDecimal:b72320b4,'0.3E-16',4(24)>
# (precision automatically larger than the 12 I requested)

>> sum = a+b
=> #<BigDecimal:b722254c,'0.1000000000 0000000000 0000000000 
000003E19',40(56)>
# nice, precision is enlarged to represent the _exact_ result :-)

>> difference = a-b
=> #<BigDecimal:b721ee9c,'0.9999999999 9999999999 9999999999 
99997E18',40(56)>
# nice, precision is enlarged to represent the _exact_ result :-)

>> product = a*b
=> #<BigDecimal:b721c494,'0.3E2',4(16)>
# nice, precision is reduced because more is not required :-)

But for the division ...
>> division = a/b
=> #<BigDecimal:b721aa7c,'0.3333333333 3333333333 3333333333 3333333333 
3333333E35',48(56)>
>> division = a/b

BigDecimal gives it a shot, takes a "large" precision ... but in a
"Decimal" representation (Sum of d(i).10^i), there is no way to
represent this exactly. So there is no limited value of precision
that will allow the exact representation. Otherwise said, the function
division '/' is not part of the field, it can produce results that
are outside the set of decimals (even if the 2 operands are inside).

The Decimal numbers are clearly a subset of the Rational numbers.
It would be quite feasible to make a Rational class which is a field
with the operators '+', '-', '*', '/'. Of course, the Rational class
exists and does this neatly:

>> ra = Rational(1000000000000000000000000000,1)
=> Rational(1000000000000000000000000000, 1)
>> rb = Rational(2,1000000000000000000000000000)
=> Rational(1, 500000000000000000000000000)
>> ra.to_s
=> "1000000000000000000000000000"
>> rb.to_s
=> "1/500000000000000000000000000"
>> (ra+rb).to_s
=> 
"500000000000000000000000000000000000000000000000000001/500000000000000000000000000"
>> (ra-rb).to_s
=> 
"499999999999999999999999999999999999999999999999999999/500000000000000000000000000"
>> (ra*rb).to_s
=> "2"
>> (ra/rb).to_s
=> "500000000000000000000000000000000000000000000000000000"
>> (rb/ra).to_s
=> "1/500000000000000000000000000000000000000000000000000000"
>> ra.inspect
=> "Rational(1000000000000000000000000000, 1)"
>> rb.inspect
=> "Rational(1, 500000000000000000000000000)"

But then again ... sin, sqrt, etc. are not part of the field and
will bring the result outside of the BigRational set into the
set of "Real" numbers.

My conclusion:
* on the *, +, - operators, it is nice to see BigDecimal
  expand the precision of the results to stick to an
  "exact" representation and stay in the field.
* if the BigDecimal class sticks to its definition of "Decimal"
  (and not Rational) then behavior of BigDecimal on the
  division operator can be nothing else then arbitrary.

Possible solutions I see on top of my head:
A do nothing (the result of division has a "large" precision,
  but is not "exact")
B raise an exception if the representation becomes not exact
C set an instance variable in the BigDecimal object that marks
  it is "not exact" (like the "tainted" flag)
D let BigDecimal return a Rational if the result can not be
  exactly represented as a BigDecimal
E use the Rational class if you want exact divisions
...

They all have their advantages:
* A OK. Understanding the behaviour is enough. I understand
    now that +.-,* work as expected and / causes the risk
    to loose exact representation.

* B (could be Class or per object flag)
==> in bookkeeping applications, exactness is required,
    so any calculation that destroys the exactness must
    be flagged. So if you calculate the "average" profit
    over your 7 divisions, you would get an exception,
    unless you did something special and understand the risks

* C this is really unintrusive and allows one to check at the
    end if the exactness did not get lost along the road

* E this exists, use it if you want an "exact" representation
    of 1/3.

I am not in favor of D because it silently changes the class
of the result and this violates the principle of least surprise.
If you want Rational, use it from the start then.

HTH,

Peter

-- 
Posted via http://www.ruby-forum.com/.

-- 
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Talk" group.
To post to this group, send email to rubyonrails-t...@googlegroups.com.
To unsubscribe from this group, send email to 
rubyonrails-talk+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/rubyonrails-talk?hl=en.

Reply via email to