如何将浮动转换为可读的分数?

假设我们有0.33,我们需要输出“1/3”。
如果我们有“0.4”,我们需要输出“2/5”。

这个想法是让用户理解“x部分y”是理解数据的一种更好的方法。

我知道百分比是一个很好的替代品,但我想知道是否有一个简单的方法来做到这一点?

我发现大卫·埃普斯坦(David Eppstein) 发现了给定实数 C代码的合理逼近,正是你所要求的。 它基于连续分数的理论,非常快速和相当紧凑。

我已经使用了针对特定分子和分母限制定制的版本。

/* ** find rational approximation to given real number ** David Eppstein / UC Irvine / 8 Aug 1993 ** ** With corrections from Arno Formella, May 2008 ** ** usage: a.out rd ** r is real number to approx ** d is the maximum denominator allowed ** ** based on the theory of continued fractions ** if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...))) ** then best approximation is found by truncating this series ** (with some adjustments in the last term). ** ** Note the fraction can be recovered as the first column of the matrix ** ( a1 1 ) ( a2 1 ) ( a3 1 ) ... ** ( 1 0 ) ( 1 0 ) ( 1 0 ) ** Instead of keeping the sequence of continued fraction terms, ** we just keep the last partial product of these matrices. */ #include <stdio.h> main(ac, av) int ac; char ** av; { double atof(); int atoi(); void exit(); long m[2][2]; double x, startx; long maxden; long ai; /* read command line arguments */ if (ac != 3) { fprintf(stderr, "usage: %srd\n",av[0]); // AF: argument missing exit(1); } startx = x = atof(av[1]); maxden = atoi(av[2]); /* initialize matrix */ m[0][0] = m[1][1] = 1; m[0][1] = m[1][0] = 0; /* loop finding terms until denom gets too big */ while (m[1][0] * ( ai = (long)x ) + m[1][1] <= maxden) { long t; t = m[0][0] * ai + m[0][1]; m[0][1] = m[0][0]; m[0][0] = t; t = m[1][0] * ai + m[1][1]; m[1][1] = m[1][0]; m[1][0] = t; if(x==(double)ai) break; // AF: division by zero x = 1/(x - (double) ai); if(x>(double)0x7FFFFFFF) break; // AF: representation failure } /* now remaining x is between 0 and 1/ai */ /* approx as either 0 or 1/m where m is max that will fit in maxden */ /* first try zero */ printf("%ld/%ld, error = %e\n", m[0][0], m[1][0], startx - ((double) m[0][0] / (double) m[1][0])); /* now try other possibility */ ai = (maxden - m[1][1]) / m[1][0]; m[0][0] = m[0][0] * ai + m[0][1]; m[1][0] = m[1][0] * ai + m[1][1]; printf("%ld/%ld, error = %e\n", m[0][0], m[1][0], startx - ((double) m[0][0] / (double) m[1][0])); } 

从Python 2.6上就有fractions模块。

(从文档引用)

 >>> from fractions import Fraction >>> Fraction('3.1415926535897932').limit_denominator(1000) Fraction(355, 113) >>> from math import pi, cos >>> Fraction.from_float(cos(pi/3)) Fraction(4503599627370497, 9007199254740992) >>> Fraction.from_float(cos(pi/3)).limit_denominator() Fraction(1, 2) 

如果输出的结果是给读者一个结果顺序的快速印象,那么返回类似“113/211”的东西是没有意义的,所以输出应该限制为使用一位数字(也许是1 / 10和9/10)。 如果是这样,你可以观察到只有27个不同的分数。

由于用于生成输出的底层math不会改变,所以解决scheme可以简单地对二叉search树进行硬编码,以使该函数能够执行最多log(27)= 4/4比较。 这是一个经过testing的C代码版本

 char *userTextForDouble(double d, char *rval) { if (d == 0.0) return "0"; // TODO: negative numbers:if (d < 0.0)... if (d >= 1.0) sprintf(rval, "%.0f ", floor(d)); d = d-floor(d); // now only the fractional part is left if (d == 0.0) return rval; if( d < 0.47 ) { if( d < 0.25 ) { if( d < 0.16 ) { if( d < 0.12 ) // Note: fixed from .13 { if( d < 0.11 ) strcat(rval, "1/10"); // .1 else strcat(rval, "1/9"); // .1111.... } else // d >= .12 { if( d < 0.14 ) strcat(rval, "1/8"); // .125 else strcat(rval, "1/7"); // .1428... } } else // d >= .16 { if( d < 0.19 ) { strcat(rval, "1/6"); // .1666... } else // d > .19 { if( d < 0.22 ) strcat(rval, "1/5"); // .2 else strcat(rval, "2/9"); // .2222... } } } else // d >= .25 { if( d < 0.37 ) // Note: fixed from .38 { if( d < 0.28 ) // Note: fixed from .29 { strcat(rval, "1/4"); // .25 } else // d >=.28 { if( d < 0.31 ) strcat(rval, "2/7"); // .2857... else strcat(rval, "1/3"); // .3333... } } else // d >= .37 { if( d < 0.42 ) // Note: fixed from .43 { if( d < 0.40 ) strcat(rval, "3/8"); // .375 else strcat(rval, "2/5"); // .4 } else // d >= .42 { if( d < 0.44 ) strcat(rval, "3/7"); // .4285... else strcat(rval, "4/9"); // .4444... } } } } else { if( d < 0.71 ) { if( d < 0.60 ) { if( d < 0.55 ) // Note: fixed from .56 { strcat(rval, "1/2"); // .5 } else // d >= .55 { if( d < 0.57 ) strcat(rval, "5/9"); // .5555... else strcat(rval, "4/7"); // .5714 } } else // d >= .6 { if( d < 0.62 ) // Note: Fixed from .63 { strcat(rval, "3/5"); // .6 } else // d >= .62 { if( d < 0.66 ) strcat(rval, "5/8"); // .625 else strcat(rval, "2/3"); // .6666... } } } else { if( d < 0.80 ) { if( d < 0.74 ) { strcat(rval, "5/7"); // .7142... } else // d >= .74 { if(d < 0.77 ) // Note: fixed from .78 strcat(rval, "3/4"); // .75 else strcat(rval, "7/9"); // .7777... } } else // d >= .8 { if( d < 0.85 ) // Note: fixed from .86 { if( d < 0.83 ) strcat(rval, "4/5"); // .8 else strcat(rval, "5/6"); // .8333... } else // d >= .85 { if( d < 0.87 ) // Note: fixed from .88 { strcat(rval, "6/7"); // .8571 } else // d >= .87 { if( d < 0.88 ) // Note: fixed from .89 { strcat(rval, "7/8"); // .875 } else // d >= .88 { if( d < 0.90 ) strcat(rval, "8/9"); // .8888... else strcat(rval, "9/10"); // .9 } } } } } } return rval; } 

这是一个链接,解释将小数转换为分数后的math运算:

http://www.webmath.com/dec2fract.html

这里是一个示例函数,如何使用VB实际做到这一点(从www.freevbcode.com/ShowCode.asp?ID=582):

 Public Function Dec2Frac(ByVal f As Double) As String Dim df As Double Dim lUpperPart As Long Dim lLowerPart As Long lUpperPart = 1 lLowerPart = 1 df = lUpperPart / lLowerPart While (df <> f) If (df < f) Then lUpperPart = lUpperPart + 1 Else lLowerPart = lLowerPart + 1 lUpperPart = f * lLowerPart End If df = lUpperPart / lLowerPart Wend Dec2Frac = CStr(lUpperPart) & "/" & CStr(lLowerPart) End Function 

(谷歌search:将小数转换为分数,将小数转换为分数代码)

你可能想读一读计算机科学家应该知道的关于浮点运算的知识 。

你必须通过乘以大数来指定一些精度:

 3.141592 * 1000000 = 3141592 

那么你可以做一个分数:

 3 + (141592 / 1000000) 

并通过GCD减less…

 3 + (17699 / 125000) 

但是没有办法得到预期的部分。 你可能想要在你的代码中总是使用分数,只要记得减less分数就可以避免溢出!

AC#实现

 /// <summary> /// Represents a rational number /// </summary> public struct Fraction { public int Numerator; public int Denominator; /// <summary> /// Constructor /// </summary> public Fraction(int numerator, int denominator) { this.Numerator = numerator; this.Denominator = denominator; } /// <summary> /// Approximates a fraction from the provided double /// </summary> public static Fraction Parse(double d) { return ApproximateFraction(d); } /// <summary> /// Returns this fraction expressed as a double, rounded to the specified number of decimal places. /// Returns double.NaN if denominator is zero /// </summary> public double ToDouble(int decimalPlaces) { if (this.Denominator == 0) return double.NaN; return System.Math.Round( Numerator / (double)Denominator, decimalPlaces ); } /// <summary> /// Approximates the provided value to a fraction. /// http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions /// </summary> private static Fraction ApproximateFraction(double value) { const double EPSILON = .000001d; int n = 1; // numerator int d = 1; // denominator double fraction = n / d; while (System.Math.Abs(fraction - value) > EPSILON) { if (fraction < value) { n++; } else { d++; n = (int)System.Math.Round(value * d); } fraction = n / (double)d; } return new Fraction(n, d); } } 

斯特恩 – 布罗科特 ( Stern-Brocot)树引入了一种相当自然的方法,用简单的分母来逼近实数。

这里是由devinmoorebuild议的VB代码的Perl和Javascript版本:

Perl的:

 sub dec2frac { my $d = shift; my $df = 1; my $top = 1; my $bot = 1; while ($df != $d) { if ($df < $d) { $top += 1; } else { $bot += 1; $top = int($d * $bot); } $df = $top / $bot; } return "$top/$bot"; } 

和几乎相同的javascript:

 function dec2frac(d) { var df = 1; var top = 1; var bot = 1; while (df != d) { if (df < d) { top += 1; } else { bot += 1; top = parseInt(d * bot); } df = top / bot; } return top + '/' + bot; } 

部分问题在于,如此多的分数实际上不能轻易地被解释为分数。 例如0.33不是1/3,是33/100。 但是如果你还记得你的小学训练,那么就有一个将小数值转换成小数的过程,但是由于大部分时间十进制数不是0.33,而是0.329999999999998等等,所以不可能给你想要的东西。

自己帮忙,不要打扰,但如果你需要,那么你可以做到以下几点:

将原始值乘以10,直到删除小数部分。 保留这个数字,并用它作为除数。 然后通过寻找共同点来做一系列的简化。

所以0.4会是4/10。 然后你会寻找从低值,可能是素数开始的公约数。 从2开始,通过检查除法的分层是否与分区本身相同,你会看到2是否均分分子和分母。

 floor(5/2) = 2 5/2 = 2.5 

所以5不均匀分2。 那么你检查下一个数字,说3,你这样做,直到你击中或更小的数字的平方根。

当你这样做后,你需要

“假设我们有0.33,我们需要输出”1/3“。”

你期望“解决scheme”有什么精度? 0.33不等于1/3。 你如何认识到一个“好”(容易阅读)的答案?

无论如何,一个可能的algorithm可能是:

如果你希望find一个X / Yforms的最接近的分数,其中Y小于10,那么你可以遍历所有9个可能的Y,对于每个Y计算X,然后select最准确的一个。

这不是一个“algorithm”,只是一个Python解决scheme: http : //docs.python.org/library/fractions.html

 >>> from fractions import Fraction >>> Fraction('3.1415926535897932').limit_denominator(1000) Fraction(355, 113) 

R中的内置解决scheme:

 library(MASS) fractions(0.666666666) ## [1] 2/3 

这使用连续分数方法,并具有可选的cyclesmax.denominator参数来调整精度。

你必须弄清楚你愿意接受的错误级别。 并非所有的小数部分都会减less到一个简单的分数。 我可能会select一个容易分解的数字,比如60,并且找出多less第60个数字最接近该值,然后简化分数。

您可以使用以下步骤在任何编程语言中执行此操作:

  1. 乘以除以10 ^ x,其中x是确保该数字没有剩余小数位数所需的10次幂。 例如:乘以0.33乘以10 ^ 2 = 100得到33,然后除以33/100
  2. 通过因式分解来减less所得分数的分子和分母,直到不能再从结果中获得整数。
  3. 由此产生的缩减分数应该是你的答案。

例如:0.2 = 0.2×10 ^ 1/10 ^ 1 = 2/10 = 1/5

所以,这可以理解为“5分之1”

一个解决scheme就是将所有数字都作为有理数存储起来。 有理性数字算术库(如GMP )。 如果使用面向对象的语言,你可能只需要使用一个理性的数字类库来replace你的数字类。

除此之外,财务计划将使用这样的解决scheme,以便能够进行精确的计算,并保持使用普通浮标可能会丢失的精度。

当然这会慢很多,所以对你来说可能不太实际。 取决于你需要做多less计算,以及精确度对你有多重要。

 a = rational(1); b = rational(3); c = a / b; print (c.asFraction) ---> "1/3" print (c.asFloat) ----> "0.333333" 

我认为这样做的最好方法是首先将你的浮点值转换为ascii表示。 在C ++中,你可以使用ostringstream或C语言,你可以使用sprintf。 以下是C ++中的外观:

 ostringstream oss; float num; cin >> num; oss << num; string numStr = oss.str(); int i = numStr.length(), pow_ten = 0; while (i > 0) { if (numStr[i] == '.') break; pow_ten++; i--; } for (int j = 1; j < pow_ten; j++) { num *= 10.0; } cout << static_cast<int>(num) << "/" << pow(10, pow_ten - 1) << endl; 

可以采取类似的方法直C

之后,您需要检查分数是最低的。 这个algorithm会给出一个精确的答案,即0.33会输出“33/100”,而不是“1/3”。 然而,0.4会给“4/10”,如果降到最低的话就是“2/5”。 这可能不如EppStein的解决scheme那样强大,但我相信这更直接。

Ruby已经有了一个内置的解决scheme:

 0.33.rationalize.to_s # => "33/100" 0.4.rationalize.to_s # => "2/5" 

在Rails中,ActiveRecord的数字属性也可以被转换:

 product.size = 0.33 product.size.to_r.to_s # => "33/100" 

在C ++中回答,假设您有一个“BigInt”类,它可以存储无限大小的整数。

你可以使用'unsigned long long'来代替,但是它只能用于某些值。

 void GetRational(double val) { if (val == val+1) // Inf throw "Infinite Value"; if (val != val) // NaN throw "Undefined Value"; bool sign = false; BigInt enumerator = 0; BigInt denominator = 1; if (val < 0) { val = -val; sign = true; } while (val > 0) { unsigned int intVal = (unsigned int)val; val -= intVal; enumerator += intVal; val *= 2; enumerator *= 2; denominator *= 2; } BigInt gcd = GCD(enumerator,denominator); enumerator /= gcd; denominator /= gcd; Print(sign? "-":"+"); Print(enumerator); Print("/"); Print(denominator); // Or simply return {sign,enumerator,denominator} as you wish } 

顺便说一句,GetRational(0.0)将返回“+0 / 1”,所以你可能想分开处理这个情况。

PS:我已经在自己的'RationalNum'类中使用了这个代码好几年了,并且已经完全testing过了。

你将会遇到两个基本的问题,这将使得这个问题变得很困难:

1)浮点不是一个精确的表示,这意味着如果你有一个“x / y”分数导致一个值“z”,你的分数algorithm可能会返回除“x / y”以外的结果。

2)无理数比无理数要多得多。 有理数是可以表示为一个分数的数。 不合理的是不能的。

但是,以一种便宜的方式,由于浮点的精度有限,所以你可以把它表示为某种forms的派别。 (我认为…)

完成上面的代码并将其转换为as3

 public static function toFrac(f:Number) : String { if (f>1) { var parte1:int; var parte2:Number; var resultado:String; var loc:int = String(f).indexOf("."); parte2 = Number(String(f).slice(loc, String(f).length)); parte1 = int(String(f).slice(0,loc)); resultado = toFrac(parte2); parte1 *= int(resultado.slice(resultado.indexOf("/") + 1, resultado.length)) + int(resultado.slice(0, resultado.indexOf("/"))); resultado = String(parte1) + resultado.slice(resultado.indexOf("/"), resultado.length) return resultado; } if( f < 0.47 ) if( f < 0.25 ) if( f < 0.16 ) if( f < 0.13 ) if( f < 0.11 ) return "1/10"; else return "1/9"; else if( f < 0.14 ) return "1/8"; else return "1/7"; else if( f < 0.19 ) return "1/6"; else if( f < 0.22 ) return "1/5"; else return "2/9"; else if( f < 0.38 ) if( f < 0.29 ) return "1/4"; else if( f < 0.31 ) return "2/7"; else return "1/3"; else if( f < 0.43 ) if( f < 0.40 ) return "3/8"; else return "2/5"; else if( f < 0.44 ) return "3/7"; else return "4/9"; else if( f < 0.71 ) if( f < 0.60 ) if( f < 0.56 ) return "1/2"; else if( f < 0.57 ) return "5/9"; else return "4/7"; else if( f < 0.63 ) return "3/5"; else if( f < 0.66 ) return "5/8"; else return "2/3"; else if( f < 0.80 ) if( f < 0.74 ) return "5/7"; else if(f < 0.78 ) return "3/4"; else return "7/9"; else if( f < 0.86 ) if( f < 0.83 ) return "4/5"; else return "5/6"; else if( f < 0.88 ) return "6/7"; else if( f < 0.89 ) return "7/8"; else if( f < 0.90 ) return "8/9"; else return "9/10"; } 

假设我们有0.33,我们需要输出“1/3”。 如果我们有“0.4”,我们需要输出“2/5”。

(3)此外,从上面的解决scheme中不可能发现,由于输出总是小数,所以小数可以转换为具有确定精度的分数。

但是,我build议我基于无限几何系列的思想,有很多选项的综合function,特别是在公式:

在这里输入图像描述

起初这个函数试图找出string表示中的小数部分。 之后,上述公式被应用。

合理的数字代码是从Stephen M. McKamey在C#中有理数的实现中借用的。 我希望在其他语言上移植我的代码并不困难。

 /// <summary> /// Convert decimal to fraction /// </summary> /// <param name="value">decimal value to convert</param> /// <param name="result">result fraction if conversation is succsess</param> /// <param name="decimalPlaces">precision of considereation frac part of value</param> /// <param name="trimZeroes">trim zeroes on the right part of the value or not</param> /// <param name="minPeriodRepeat">minimum period repeating</param> /// <param name="digitsForReal">precision for determination value to real if period has not been founded</param> /// <returns></returns> public static bool FromDecimal(decimal value, out Rational<T> result, int decimalPlaces = 28, bool trimZeroes = false, decimal minPeriodRepeat = 2, int digitsForReal = 9) { var valueStr = value.ToString("0.0000000000000000000000000000", CultureInfo.InvariantCulture); var strs = valueStr.Split('.'); long intPart = long.Parse(strs[0]); string fracPartTrimEnd = strs[1].TrimEnd(new char[] { '0' }); string fracPart; if (trimZeroes) { fracPart = fracPartTrimEnd; decimalPlaces = Math.Min(decimalPlaces, fracPart.Length); } else fracPart = strs[1]; result = new Rational<T>(); try { string periodPart; bool periodFound = false; int i; for (i = 0; i < fracPart.Length; i++) { if (fracPart[i] == '0' && i != 0) continue; for (int j = i + 1; j < fracPart.Length; j++) { periodPart = fracPart.Substring(i, j - i); periodFound = true; decimal periodRepeat = 1; decimal periodStep = 1.0m / periodPart.Length; var upperBound = Math.Min(fracPart.Length, decimalPlaces); int k; for (k = i + periodPart.Length; k < upperBound; k += 1) { if (periodPart[(k - i) % periodPart.Length] != fracPart[k]) { periodFound = false; break; } periodRepeat += periodStep; } if (!periodFound && upperBound - k <= periodPart.Length && periodPart[(upperBound - i) % periodPart.Length] > '5') { var ind = (k - i) % periodPart.Length; var regroupedPeriod = (periodPart.Substring(ind) + periodPart.Remove(ind)).Substring(0, upperBound - k); ulong periodTailPlusOne = ulong.Parse(regroupedPeriod) + 1; ulong fracTail = ulong.Parse(fracPart.Substring(k, regroupedPeriod.Length)); if (periodTailPlusOne == fracTail) periodFound = true; } if (periodFound && periodRepeat >= minPeriodRepeat) { result = FromDecimal(strs[0], fracPart.Substring(0, i), periodPart); break; } else periodFound = false; } if (periodFound) break; } if (!periodFound) { if (fracPartTrimEnd.Length >= digitsForReal) return false; else { result = new Rational<T>(long.Parse(strs[0]), 1, false); if (fracPartTrimEnd.Length != 0) result = new Rational<T>(ulong.Parse(fracPartTrimEnd), TenInPower(fracPartTrimEnd.Length)); return true; } } return true; } catch { return false; } } public static Rational<T> FromDecimal(string intPart, string fracPart, string periodPart) { Rational<T> firstFracPart; if (fracPart != null && fracPart.Length != 0) { ulong denominator = TenInPower(fracPart.Length); firstFracPart = new Rational<T>(ulong.Parse(fracPart), denominator); } else firstFracPart = new Rational<T>(0, 1, false); Rational<T> secondFracPart; if (periodPart != null && periodPart.Length != 0) secondFracPart = new Rational<T>(ulong.Parse(periodPart), TenInPower(fracPart.Length)) * new Rational<T>(1, Nines((ulong)periodPart.Length), false); else secondFracPart = new Rational<T>(0, 1, false); var result = firstFracPart + secondFracPart; if (intPart != null && intPart.Length != 0) { long intPartLong = long.Parse(intPart); result = new Rational<T>(intPartLong, 1, false) + (intPartLong == 0 ? 1 : Math.Sign(intPartLong)) * result; } return result; } private static ulong TenInPower(int power) { ulong result = 1; for (int l = 0; l < power; l++) result *= 10; return result; } private static decimal TenInNegPower(int power) { decimal result = 1; for (int l = 0; l > power; l--) result /= 10.0m; return result; } private static ulong Nines(ulong power) { ulong result = 9; if (power >= 0) for (ulong l = 0; l < power - 1; l++) result = result * 10 + 9; return result; } 

有一些使用的例子:

 Rational<long>.FromDecimal(0.33333333m, out r, 8, false); // then r == 1 / 3; Rational<long>.FromDecimal(0.33333333m, out r, 9, false); // then r == 33333333 / 100000000; 

您的情况与右零件零部件修剪:

 Rational<long>.FromDecimal(0.33m, out r, 28, true); // then r == 1 / 3; Rational<long>.FromDecimal(0.33m, out r, 28, true); // then r == 33 / 100; 

最短时间演示:

 Rational<long>.FromDecimal(0.123412m, out r, 28, true, 1.5m)); // then r == 1234 / 9999; Rational<long>.FromDecimal(0.123412m, out r, 28, true, 1.6m)); // then r == 123412 / 1000000; because of minimu repeating of period is 0.1234123 in this case. 

最后舍入:

 Rational<long>.FromDecimal(0.8888888888888888888888888889m, out r)); // then r == 8 == 9; 

最有趣的情况是:

 Rational<long>.FromDecimal(0.12345678m, out r, 28, true, 2, 9); // then r == 12345678 / 100000000; Rational<long>.FromDecimal(0.12345678m, out r, 28, true, 2, 8); // Conversation failed, because of period has not been founded and there are too many digits in fraction part of input value. Rational<long>.FromDecimal(0.12121212121212121m, out r, 28, true, 2, 9)); // then r == 4 / 33; Despite of too many digits in input value, period has been founded. Thus it's possible to convert value to fraction. 

其他testing和代码,大家可以在我的MathFunctions库中findgithub 。

这是一个在JavaScript中使用暴力方法的快速和肮脏的实现。 一点也不优化,它在预定的分数范围内工作: http : //jsfiddle.net/PdL23/1/

 /* This should convert any decimals to a simplified fraction within the range specified by the two for loops. Haven't done any thorough testing, but it seems to work fine. I have set the bounds for numerator and denominator to 20, 20... but you can increase this if you want in the two for loops. Disclaimer: Its not at all optimized. (Feel free to create an improved version.) */ decimalToSimplifiedFraction = function(n) { for(num = 1; num < 20; num++) { // "num" is the potential numerator for(den = 1; den < 20; den++) { // "den" is the potential denominator var multiplyByInverse = (n * den ) / num; var roundingError = Math.round(multiplyByInverse) - multiplyByInverse; // Checking if we have found the inverse of the number, if((Math.round(multiplyByInverse) == 1) && (Math.abs(roundingError) < 0.01)) { return num + "/" + den; } } } }; //Put in your test number here. var floatNumber = 2.56; alert(floatNumber + " = " + decimalToSimplifiedFraction(floatNumber)); 

这受到JPS使用的方法的启发。

正如很多人所说的,你实际上不能将浮点数转换成分数(除非它非常精确,如.25)。 当然,你可以创build一些types的查找大量的分数,并使用某种模糊逻辑来产生你正在寻找的结果。 再一次,这不是确切的,你需要定义一个你想要分母有多大的下界。

.32 <x <.34 = 1/3或类似的东西。

这里是Ruby的实现http://github.com/valodzka/frac

 Math.frac(0.2, 100) # => (1/5) Math.frac(0.33, 10) # => (1/3) Math.frac(0.33, 100) # => (33/100) 

Ian Richards / John Kennedy的这个algorithm不仅返回了很好的分数,而且在速度方面也performance的非常好。 这是我从这个答案采取的C#代码。

它可以处理除了NaN和+/-无穷大等特殊值之外的所有double值,必要时可以添加它们。

它返回一个new Fraction(numerator, denominator) 。 用你自己的typesreplace。

有关更多示例值和与其他algorithm的比较, 请转到此处

 public Fraction RealToFraction(double value, double accuracy) { if (accuracy <= 0.0 || accuracy >= 1.0) { throw new ArgumentOutOfRangeException("accuracy", "Must be > 0 and < 1."); } int sign = Math.Sign(value); if (sign == -1) { value = Math.Abs(value); } // Accuracy is the maximum relative error; convert to absolute maxError double maxError = sign == 0 ? accuracy : value * accuracy; int n = (int) Math.Floor(value); value -= n; if (value < maxError) { return new Fraction(sign * n, 1); } if (1 - maxError < value) { return new Fraction(sign * (n + 1), 1); } double z = value; int previousDenominator = 0; int denominator = 1; int numerator; do { z = 1.0 / (z - (int) z); int temp = denominator; denominator = denominator * (int) z + previousDenominator; previousDenominator = temp; numerator = Convert.ToInt32(value * denominator); } while (Math.Abs(value - (double) numerator / denominator) > maxError && z != (int) z); return new Fraction((n * denominator + numerator) * sign, denominator); } 

此algorithm返回的示例值:

 Accuracy: 1.0E-3 | Richards Input | Result Error ======================| ============================= 3 | 3/1 0 0.999999 | 1/1 1.0E-6 1.000001 | 1/1 -1.0E-6 0.50 (1/2) | 1/2 0 0.33... (1/3) | 1/3 0 0.67... (2/3) | 2/3 0 0.25 (1/4) | 1/4 0 0.11... (1/9) | 1/9 0 0.09... (1/11) | 1/11 0 0.62... (307/499) | 8/13 2.5E-4 0.14... (33/229) | 16/111 2.7E-4 0.05... (33/683) | 10/207 -1.5E-4 0.18... (100/541) | 17/92 -3.3E-4 0.06... (33/541) | 5/82 -3.7E-4 0.1 | 1/10 0 0.2 | 1/5 0 0.3 | 3/10 0 0.4 | 2/5 0 0.5 | 1/2 0 0.6 | 3/5 0 0.7 | 7/10 0 0.8 | 4/5 0 0.9 | 9/10 0 0.01 | 1/100 0 0.001 | 1/1000 0 0.0001 | 1/10000 0 0.33333333333 | 1/3 1.0E-11 0.333 | 333/1000 0 0.7777 | 7/9 1.0E-4 0.11 | 10/91 -1.0E-3 0.1111 | 1/9 1.0E-4 3.14 | 22/7 9.1E-4 3.14... (pi) | 22/7 4.0E-4 2.72... (e) | 87/32 1.7E-4 0.7454545454545 | 38/51 -4.8E-4 0.01024801004 | 2/195 8.2E-4 0.99011 | 100/101 -1.1E-5 0.26... (5/19) | 5/19 0 0.61... (37/61) | 17/28 9.7E-4 | Accuracy: 1.0E-4 | Richards Input | Result Error ======================| ============================= 0.62... (307/499) | 299/486 -6.7E-6 0.05... (33/683) | 23/476 6.4E-5 0.06... (33/541) | 33/541 0 1E-05 | 1/99999 1.0E-5 0.7777 | 1109/1426 -1.8E-7 3.14... (pi) | 333/106 -2.6E-5 2.72... (e) | 193/71 1.0E-5 0.61... (37/61) | 37/61 0 

我遇到了一个特别优雅的Haskell解决scheme,利用了一个变形。 这取决于recursionscheme包。

 {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE FlexibleContexts #-} import Control.Applicative (liftA2) import Control.Monad (ap) import Data.Functor.Foldable import Data.Ratio (Ratio, (%)) isInteger :: (RealFrac a) => a -> Bool isInteger = ((==) <*>) (realToFrac . floor) continuedFraction :: (RealFrac a) => a -> [Int] continuedFraction = liftA2 (:) floor (ana coalgebra) where coalgebra x | isInteger x = Nil | otherwise = Cons (floor alpha) alpha where alpha = 1 / (x - realToFrac (floor x)) collapseFraction :: (Integral a) => [Int] -> Ratio a collapseFraction [x] = fromIntegral x % 1 collapseFraction (x:xs) = (fromIntegral x % 1) + 1 / collapseFraction xs -- | Use the nth convergent to approximate x approximate :: (RealFrac a, Integral b) => a -> Int -> Ratio b approximate xn = collapseFraction $ take n (continuedFraction x) 

如果你用ghci来试试这个,那真的能起作用!

 λ:> approximate pi 2 22 % 7