Format Double as Fraction [closed]

2020-06-17 05:07发布

问题:

Is there a library that will convert a Double to a String with the whole number, followed by a fraction?

For example

1.125 = 1 1/8

I am only looking for fractions to a 64th of an inch.

回答1:

Your problem is pretty simple, because you're assured the denominator will always divide 64. in C# (someone feel free to translate a Java version):

string ToMixedFraction(decimal x) 
{
    int whole = (int) x;
    int denominator = 64;
    int numerator = (int)( (x - whole) * denominator );

    if (numerator == 0) 
    {
        return whole.ToString();
    }
    while ( numerator % 2 == 0 ) // simplify fraction
    {
        numerator /= 2;
        denominator /=2;
    }
    return string.Format("{0} {1}/{2}", whole, numerator, denominator);
}

Bonus: Code Golf

public static string ToMixedFraction(decimal x) {
    int w = (int)x,
        n = (int)(x * 64) % 64,
        a = n & -n;
    return w + (n == 0 ? "" : " " + n / a + "/" + 64 / a);
}


回答2:

One problem you might run into is that not all fractional values can be represented by doubles. Even some values that look simple, like 0.1. Now on with the pseudocode algorithm. You would probably be best off determining the number of 64ths of an inch, but dividing the decimal portion by 0.015625. After that, you can reduce your fraction to the lowest common denominator. However, since you state inches, you may not want to use the smallest common denominator, but rather only values for which inches are usually represented, 2,4,8,16,32,64.

One thing to point out however, is that since you are using inches, if the values are all proper fractions of an inch, with a denominator of 2,4,8,16,32,64 then the value should never contain floating point errors, because the denominator is always a power of 2. However if your dataset had a value of .1 inch in there, then you would start to run into problems.



回答3:

How about org.apache.commons.math ? They have a Fraction class that takes a double.

http://commons.apache.org/math/api-1.2/org/apache/commons/math/fraction/Fraction.html

You should be able to extend it and give it functionality for the 64th. And you can also add a toString that will easily print out the whole number part of the fraction for you.

Fraction(double value, int maxDenominator) Create a fraction given the double value and maximum denominator.



回答4:

I don't necessarily agree, base on the fact that Milhous wants to cover inches up to 1/64" Suppose that the program demands 1/64" precision at all times, that should take up 6 bits of the mantissa. In a float, there's 24-6 = 18, which (if my math is right), should mean that he's got a range of +/- 262144 + 63/64"

That might be enough precision in the float to convert properly into the faction without loss.

And since most people working on inches uses denominator of powers of 2, it should be fine.

But back to the original question, I don't know any libraries that would do that.



回答5:

Function for this in a C-variant called LPC follows. Some notes:

  1. Addition to input value at beginning is to try to cope with precision issues that otherwise love to wind up telling you that 5 is 4 999999/1000000.
  2. The to_int() function truncates to integer.
  3. Language has a to_string() that will turn some floats into exponential notation.

string strfrac(float frac) {
    int main = to_int(frac + frac / 1000000.0);
    string out = to_string(main);
    float rem = frac - to_float(main);
    string rep;
    if(rem > 0 && (to_int(rep = to_string(rem)) || member(rep, 'e') == Null)) {
        int array primes = ({ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47 });
        string base;
        int exp;
        int num;
        int div;
        if(sscanf(rep, "%se%d", base, exp) == 2) {
            num = to_int(replace(base, ".", ""));
            div = to_int(pow(10, abs(exp)));
        } else {
            rep = rep[2..];
            num = to_int(rep);
            div = to_int(pow(10, strlen(rep)));
        }
        foreach(int prime : primes) {
            if(prime > num)
                break;
            while((num / prime) * prime == num && (div / prime) * prime == div) {
                num /= prime;
                div /= prime;
            }
        }
        out += " " + num + "/" + div;
    }
    return out;
}


回答6:

i wrote this for my project i hope it could be usefull:

//How to "Convert" double to fraction("a/b") - kevinlopez@unitec.edu
private boolean isInt(double number){
    if(number%2==0 ||(number+1)%2==0){
        return true;
    }
    return false;
}
private String doubleToFraction(double doub){
    //we get the whole part
    int whole = (int)doub;
    //we get the rest
    double rest = doub - (double)whole;
    int numerator=1,denominator=1;
    //if the whole part of the number is greater than 0
    //we'll try to transform the rest of the number to an Integer
    //by multiplying the number until it become an integer
    if(whole >=1){
        for(int i = 2; ; i++){
            /*when we find the "Integer" number(it'll be the numerator)
             * we also found the denominator(i,which is the number that transforms the number to integer)
             * For example if we have the number = 2.5 when it is multiplied by 2
             * now it's 5 and it's integer, now we have the numerator(the number (2.5)*i(2) = 5)
             * and the denominator i = 2
             */
            if(isInt(rest*(double)i)){
                numerator = (int)(rest*(double)i);
                denominator = i;
                break;
            }
            if(i>10000){
                //if i is greater than 10000 it's posible that the number is irrational
                //and it can't be represented as a fractional number
                return doub+"";
            }
        }
        //if we have the number 3.5 the whole part is 3 then we have the rest represented in fraction 0.5 = 1/2
        //so we have a mixed fraction 3+1/2 = 7/2
        numerator = (whole*denominator)+numerator;
    }else{
        //If not we'll try to transform the original number to an integer
        //with the same process
        for(int i = 2; ; i++){
            if(isInt(doub*(double)i)){
                numerator = (int)(doub*(double)i);
                denominator = i;
                break;
            }
            if(i>10000){
                return doub+"";
            }
        }
    }
    return numerator+"/"+denominator;
}


回答7:

My code looks like this.

public static int gcd(int a, int b)
    {
        if (b == 0)
            return a;
        else
            return gcd(b, a % b);
    }

public static String doubleToStringFraction(Double d)
    {
        StringBuffer result = new StringBuffer(" " + ((int) Math.floor(d)));
        int whole = (int) ((d - Math.floor(d)) * 10000);
        int gcd = gcd(whole, 10000);
        result.append(" " + (whole / gcd) + "/" + 10000 / gcd + " ");
        return result.toString();
    }


回答8:

As several others have poited out, fractions of 64 can be precicely represented by IEEE-floats. This means we can also convert to a fraction by moving and masking bits.

This is not the place to explain all details of floating point representations, please refer to wikipedia for details.

Briefly: a floating point number is stored as (sign)(exp)(frac) where sign is 1 bit, exp is 11 bits and frac is the fraction part (after 1.) and is 52 bits. This is enterpreted as the number:

(sign == 1 ? -1 : 1) * 1.(frac) * 2^(exp-1023)

Thus, we can get the 64th by moving the point accoring to the exponent and masking out the 6 bits after the point. In Java:

private static final long MANTISSA_FRAC_BITMAP = 0xfffffffffffffl;
private static final long MANTISSA_IMPLICIT_PREFIX = 0x10000000000000l;
private static final long DENOM_BITMAP = 0x3f; // 1/64
private static final long DENOM_LEN = 6;
private static final int FRAC_LEN = 52;

public String floatAsFrac64(double d) {
    long bitmap = Double.doubleToLongBits(d);
    long mantissa = bitmap & MANTISSA_FRAC_BITMAP | MANTISSA_IMPLICIT_PREFIX;
    long exponent = ((bitmap >> FRAC_LEN) & 0x7ff) - 1023;
    boolean negative = (bitmap & (1l << 63)) > 0;

    // algorithm:
    //   d is stored as SE(11)F(52), implicit "1." before F
    //   move point to the right <exponent> bits to the right:
    if(exponent > FRAC_LEN) System.out.println("warning: loosing precision, too high exponent");
    int pointPlace = FRAC_LEN-(int)exponent;
    //   get the whole part as the number left of the point: 
    long whole = mantissa >> pointPlace;
    //   get the frac part as the 6 first bits right of the point: 
    long frac = (mantissa >> (pointPlace-DENOM_LEN)) & DENOM_BITMAP;
    //   if the last operation shifted 1s out to the right, we lost precision, check with
    //   if any of these bits are set:
    if((mantissa & ((MANTISSA_FRAC_BITMAP | MANTISSA_IMPLICIT_PREFIX) >> (pointPlace - DENOM_LEN))) > 0) {
        System.out.println("warning: precision of input is smaller than 1/64");
    }
    if(frac == 0) return String.format("%d", whole);
    int denom = 64;
    // test last bit, divide nom and demon by 1 if not 1
    while((frac & 1) == 0) {
        frac = frac >> 1;
        denom = denom >> 1;
    }
    return String.format("%d %d/%d", whole, frac, denom);
}

(this code can probably be made shorter, but reading bit-flipping-code like this is hard enough as it is...)



回答9:

I create simply Fraction library.

The library is available here: https://github.com/adamjak/Fractions

Example:

String s = "1.125";
Fraction f1 = Fraction.tryParse(s);
f1.toString(); // return 9/8

Double d = 2.58;
Fraction f2 = Fraction.createFraction(d);
f2.divide(f1).toString() // return 172/75 (2.29)


回答10:

To solve this problem (in one of my projects), I took the following steps:

  • Built a dictionary of decimal/fraction strings.
  • Wrote a function to search the dictionary for the closest matching fraction depending on the "decimal" part of the number and the matching criteria.