Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get reduced fraction from BigDecimal

I'm doing some really precise decimal calculations that I turn into reduced fractions at the end. The decimals need precision to 96 decimals.

Since the precision is so important I'm using BigDecimal and BigInteger.

The calculation of the BigDecimal always returns the correct decimal value, but my function for turning this decimal into a fraction fails for some cases

Let's say I have a BigDecimal d

d.toString() = 32.222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222223

When my function is trying to turn this into a fraction it outputs

Decimal from BigDecimal is:  
32.222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222223

// Run the BigDecimal into getFraction
Denominator before reducing:
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Numerator before reducing:
32222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222223

// Reduced fraction turns into:
-1/0


// But should output
290/9

Here's my function for reducing decimal into fraction:

static int[] getFraction(BigDecimal x) {
        BigDecimal x1 = x.stripTrailingZeros();
        //System.out.println(x.toString() + " stripped from zeroes");
        //System.out.println(x1.scale());

        // If scale is 0 or under we got a whole number fraction something/1
        if(x1.scale() <= 0) {
            //System.out.println("Whole number");
            int[] rf = { x.intValue(), 1 };
            return rf;
        }

        // If the decimal is 
        if(x.compareTo(BigDecimal.ZERO) < 0) {
            // Add "-" to fraction when printing from main function
            // Flip boolean to indicate negative decimal number
            negative = true;

            // Flip the BigDecimal
            x = x.negate();
            // Perform same function on flipped
            return getFraction(x);
        }

        // Split BigDecimal into the intval and fractional val as strings
        String[] parts = x.toString().split("\\.");

        // Get starting numerator and denominator
        BigDecimal denominator = BigDecimal.TEN.pow(parts[1].length()); 
        System.out.println("Denominator :" + denominator.toString());

        BigDecimal numerator = (new BigDecimal(parts[0]).multiply(denominator)).add(new BigDecimal(parts[1]));
        System.out.println("Numerator :" + numerator.toString());

        // Now we reduce
        return reduceFraction(numerator.intValue(), denominator.intValue());
    }

    static int[] reduceFraction(int numerator, int denominator) {
        // First find gcd
        int gcd = BigInteger.valueOf(numerator).gcd(BigInteger.valueOf(denominator)).intValue(); 
        //System.out.println(gcd);


        // Then divide numerator and denominator by gcd
        int[] reduced = { numerator / gcd, denominator / gcd };

        // Return the fraction
        return reduced;
    }

If anyone would clarify if I have made any mistakes, I would greatly appreciate it!

** UPDATE **

Changed reduceFraction function: Now returns a String[] instead of int[]


static String[] reduceFraction(BigDecimal numerator, BigDecimal denominator) {
        // First find gcd
        BigInteger nu = new BigInteger(numerator.toString());
        BigInteger de = new BigInteger(denominator.toString());
        BigInteger gcd = nu.gcd(de);

        // Then divide numerator and denominator by gcd
        nu = nu.divide(gcd);
        de = de.divide(gcd);
        String[] reduced = { nu.toString(), de.toString() };

        // Return the fraction
        return reduced;
    }

getFraction returns:

// Now we reduce, send BigDecimals for numerator and denominator
        return reduceFraction(num, den);

instead of

// Now we reduce
        return reduceFraction(numerator.intValue(), denominator.intValue());

Still gets wrong answer from function

Output fraction now is

// Gcd value
gcd = 1

// Fraction is then:
32222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222223/1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000


//gcd Value should be:
gcd = 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

// Whit this gcd the fraction reduces to: 
290/9
like image 515
DonnumS Avatar asked Jan 30 '26 06:01

DonnumS


2 Answers

You seem to be making this much harder than it needs to be. Here is my initial attempt:

public static BigInteger[] toRational(BigDecimal decimal)
{
    int scale = decimal.scale();
    if (scale <= 0) {
        return new BigInteger[]{decimal.toBigInteger(), BigInteger.ONE};
    } else {
        BigInteger denominator = BigInteger.TEN.pow(scale);
        BigInteger numerator = decimal.unscaledValue();
        BigInteger d = numerator.gcd(denominator);
        return new BigInteger[]{numerator.divide(d), denominator.divide(d)};
    }
}

The rational number is always returned in lowest terms. Note that if decimal is 0 then 0/1 is returned as the rational. If decimal is negative then the rational is returned with the numerator negative.

like image 183
President James K. Polk Avatar answered Feb 01 '26 20:02

President James K. Polk


// Now we reduce
return reduceFraction(numerator.intValue(), denominator.intValue());

Well, this must fail in this case, because neither the numerator or denominator can fit into an int here.

The numerator becomes -1908874353, and the denominator becomes 0 after you call intValue() on them. You must carry with BigIntegers until the end of the computation.

Before converting them to int or long, if you must do so, you can check whether they can be converted to those types without loss of precision by checking them against Integer.MIN_VALUE, Integer.MAX_VALUE, Long.MIN_VALUE, and Long.MAX_VALUE.

like image 34
Bernardo Sulzbach Avatar answered Feb 01 '26 18:02

Bernardo Sulzbach



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!