This project has moved and is read-only. For the latest updates, please go here.

Confusion about BigRational and Double

Jun 23, 2010 at 9:22 PM

I have the following code:

var q = new BigRational(new BigInteger(22233), new BigInteger(100));
var x = (double)q;
var r = new BigRational(222.33d);
var d = (double)r;

Basically I'm trying to round-trip 222.33.

When I print this out:

 

Console.Out.WriteLine(q);
Console.Out.WriteLine(x);
Console.Out.WriteLine(r);
Console.Out.WriteLine(d);

 

I get:

22233/100
222.33
7822541446510019/845271249817064394163743655866426570430155721657794435404737134442678244090759775159067609420251500
79031989211405886211756095204296859600862365540703323053418694398408134669970428282282305684838772653137901446636845
024987821414350380272583623832617294363807973376
9.25447475967388E-256

The creation of r based on x is what's throwing me. My expectation of creating BigRational off of a "222.33" double value would be to have "22233/100" show up. But I'm getting this extremely small number.

Is this a bug with BigRational, or is my expectation of what BigRational should do with doubles wrong?

Thanks,

Jason Bock

 
Jun 23, 2010 at 9:25 PM

I should note that decimal works as I would expect. If I do this:

Console.Out.WriteLine(new BigRational(new decimal(222.33)));

I get this:

22233/100

Jun 25, 2010 at 5:03 PM

Your intuition about what should be happening is correct, there is indeed a bug in the constructor that takes a double,  if you change it to this it should work for you:

        // BigRational(Double)
        public BigRational(Double value) {
            if (Double.IsNaN(value)) {
                throw new ArgumentException("Argument is not a number", "value");
            }
            else if (Double.IsInfinity(value)) {
                throw new ArgumentException("Argument is infinity", "value");
            }

            bool isFinite;
            int sign;
            int exponent;
            ulong significand;
            SplitDoubleIntoParts(value, out sign, out exponent, out significand, out isFinite);

             if (significand == 0) {
                this = BigRational.Zero;
                return;
            }
           
            m_numerator = significand;
            m_denominator = BigInteger.One;

            if (exponent > 0) {
                m_numerator = m_numerator << exponent;           
            }
            else if (exponent < 0) {
                m_denominator = m_denominator << -exponent;
            }
            if (sign < 0) {
                m_numerator = BigInteger.Negate(m_numerator);
            }
            Simplify();
        }

 

Jun 26, 2010 at 6:11 PM

Yep, that's fixes it - thanks.

Jun 28, 2010 at 2:46 AM

I should note that while this does fix things, the output for a BigRational when given a number like 222.33 is 7822541446510019/35184372088832. Using a Decimal gives 22233/100, which seems to be a more efficient (and precise) representation. I realize this has to do with the fact with how a double is parsed in BigRational but I'm wondering if this is something that could be shored up in the future? Or if I'm completely off-base, then never mind :).

Regards,

Jason

Jun 29, 2010 at 7:15 PM
Edited Jun 29, 2010 at 7:16 PM

Actually that imprecision isn't coming from how BigRational parses the double, it is coming from how double stores the 222.33 value.  The problem here is that double is imprecise so it rounds the 222.33 value to something it can represent before it is ever even given to the BigRational constructor.  In other words the BigRational is exactly representing the value of the double passed to it, the problem you see here is the value of the double is not exactly 222.33 

Jun 29, 2010 at 7:42 PM

I was guessing that due to the way the floating-point number is stored and represented had something to do with :). Thanks for the clarification, though.

Sep 27, 2010 at 8:41 PM

Thanks for the bug fix - it bit me too.

Any idea when this will make it into the source tree?

Dec 22, 2012 at 1:55 AM

Note... as of today this fix has still not made it into the BigRational class.

Jun 12, 2014 at 6:19 PM
I saw the work item first (https://bcl.codeplex.com/workitem/13051).

Ran into the same thing and fixed it. My fix is much more efficient than the fix above because it counts trailing zeros in the significand so that the initial conversion never needs simplification.

I attached a file for comparison.