Search code examples
javadecimalbigintegerbigdecimalfractions

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

Solution

  • 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.