将双精度浮点数格式化为分数

8
有没有一个库可以将Double转换为整数加分数的字符串?
例如:
1.125 = 1 1/8

我只需要64分之一英寸的小数。
10个回答

10

你的问题很简单,因为你确保分母总是可以被64整除。在C#中(欢迎有兴趣的人翻译Java版本):

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);
}

额外奖励:代码高尔夫

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);
}

请注意,检查分母是否为偶数是不必要的。请参考我的Java版本。 - erickson
现在看看你让我做了什么...一旦你开始精简代码,就停不下来了。 - Jimmy

5
你可能会遇到的一个问题是,并非所有分数值都可以用双精度浮点数表示,即使有些看起来很简单,比如0.1。现在让我们继续伪代码算法。你最好确定64分之一英寸的数量,但要将小数部分除以0.015625。之后,你可以将分数化简为最简形式。然而,由于你提到了英寸,你可能不想使用最小公分母,而是只使用通常表示英寸的值,即2、4、8、16、32、64。
然而需要指出的一件事是,由于你正在使用英寸,如果这些值都是英寸的适当分数,分母为2、4、8、16、32、64,则该值不应包含浮点误差,因为分母始终是2的幂。但是,如果你的数据集中有0.1英寸的值,那么你就会遇到问题。

输入的数据很好(始终是64分之一英寸)。 - Milhous

4

3

根据事实,米尔豪斯想要覆盖1/64英寸的精度,我不一定同意。假设程序始终需要1/64英寸的精度,那么应该使用6位浮点数来表示。在一个浮点数中,有24-6=18位可用,这意味着(如果我的计算正确)他的范围应该是+/- 262144 + 63/64英寸。

在浮点数中这可能已经有足够的精度以避免损失转换成分数。

由于大多数处理英寸的人使用2的幂作为分母,所以应该没有问题。

但说回原问题,我不知道是否有任何库可以实现这个功能。


2
在名为LPC的C变体中,此功能如下。一些注释:
  1. 在开始时添加输入值是为了尝试处理精度问题,否则很容易告诉您5是4 999999/1000000。
  2. to_int()函数截断为整数。
  3. 语言具有to_string(),可以将某些浮点数转换为指数表示法。

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;
}

1

我为我的项目编写了这个,希望它能有用:

//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;
}

0

我创建了一个简单的分数库。

该库可以在这里找到:https://github.com/adamjak/Fractions

示例:

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)

0

我的代码看起来是这样的。

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();
    }

0

正如其他人所指出的那样,64的分数可以通过IEEE-floats精确表示。这意味着我们也可以通过移动和掩码位来转换为分数。

这不是解释浮点表示所有细节的地方,请参考wikipedia了解详情。

简而言之:浮点数存储为(符号)(exp)(frac),其中符号为1位,exp为11位,frac为小数部分(在1.后面),为52位。这被解释为数字:

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

因此,我们可以通过根据指数移动点并屏蔽点后的6位来获取第64个。在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);
}

(这段代码可能可以更短,但是读取像这样的位翻转代码已经很困难了...)


-1
为了解决这个问题(在我的一个项目中),我采取了以下步骤:
  • 建立了一个十进制/分数字符串的字典。
  • 编写了一个函数,根据数字的“小数”部分和匹配标准,在字典中搜索最接近的匹配分数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接