Format Double as Fraction [closed]

2020-06-17 05:27发布

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.

10条回答
Deceive 欺骗
2楼-- · 2020-06-17 05:46

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

查看更多
地球回转人心会变
3楼-- · 2020-06-17 05:46

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)
查看更多
放荡不羁爱自由
4楼-- · 2020-06-17 05:49

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.
查看更多
Fickle 薄情
5楼-- · 2020-06-17 05:51

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.

查看更多
一纸荒年 Trace。
6楼-- · 2020-06-17 05:52

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);
}
查看更多
Deceive 欺骗
7楼-- · 2020-06-17 05:55

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.

查看更多
登录 后发表回答