我尝试编写一种算法来将小数简化为一小部分,并意识到它并不太简单。令人惊讶的是,我在网上看到了我发现的所有代码,这些代码要么太长,要么在某些情况下不起作用。更令人讨厌的是,它们不适用于重复小数。我想知道是否会有一位数学家/程序员在这里理解所有涉及的过程,将小数简化为一小部分。任何人吗?
答案 0 :(得分:41)
其他人给你的算法通过计算数字的Continued Fraction得到答案。这给出了一个分数序列,保证非常快速地收敛。然而,不保证为您提供实数的距离epsilon内的最小分数。要找到你必须走Stern-Brocot tree。
要做到这一点,你减去地板以获得[0,1]范围内的数字,然后你的估计值为0,你的估计值为1.现在进行二元搜索直到你足够接近。在每次迭代时,如果你的下限是a / b而你的上限是c / d,你的中间是(a + c)/(b + d)。测试你的中间部分对着x,然后让中间成为上部,下部,或者返回你的最终答案。
这是一些非惯用的(因此,即使你不懂语言,也可能是可读的)实现这种算法的Python。
def float_to_fraction (x, error=0.000001):
n = int(math.floor(x))
x -= n
if x < error:
return (n, 1)
elif 1 - error < x:
return (n+1, 1)
# The lower fraction is 0/1
lower_n = 0
lower_d = 1
# The upper fraction is 1/1
upper_n = 1
upper_d = 1
while True:
# The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
middle_n = lower_n + upper_n
middle_d = lower_d + upper_d
# If x + error < middle
if middle_d * (x + error) < middle_n:
# middle is our new upper
upper_n = middle_n
upper_d = middle_d
# Else If middle < x - error
elif middle_n < (x - error) * middle_d:
# middle is our new lower
lower_n = middle_n
lower_d = middle_d
# Else middle is our best fraction
else:
return (n * middle_d + middle_n, middle_d)
答案 1 :(得分:26)
(2017年2月改进的代码 - 向下滚动到'优化'...)
(此答案末尾的算法比较表)
我在C#中实现了btilly's answer并且......
accuracy
参数以指定最大值。相对误差,而不是最大值。绝对错误; 0.01
会在值的1%范围内找到一小部分。Double.NaN
和Double.Infinity
不受支持;你可能想要处理那些(example here)。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);
}
// The lower fraction is 0/1
int lower_n = 0;
int lower_d = 1;
// The upper fraction is 1/1
int upper_n = 1;
int upper_d = 1;
while (true)
{
// The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
int middle_n = lower_n + upper_n;
int middle_d = lower_d + upper_d;
if (middle_d * (value + maxError) < middle_n)
{
// real + error < middle : middle is our new upper
upper_n = middle_n;
upper_d = middle_d;
}
else if (middle_n < (value - maxError) * middle_d)
{
// middle < real - error : middle is our new lower
lower_n = middle_n;
lower_d = middle_d;
}
else
{
// Middle is our best fraction
return new Fraction((n * middle_d + middle_n) * sign, middle_d);
}
}
}
Fraction
类型只是一个简单的结构。当然,请使用您自己喜欢的类型......(我喜欢Rick Davin的this one。)
public struct Fraction
{
public Fraction(int n, int d)
{
N = n;
D = d;
}
public int N { get; private set; }
public int D { get; private set; }
}
2017年2月优化
对于某些值,例如0.01
,0.001
等,算法会经历数百或数千次线性迭代。为了解决这个问题,我实现了一种查找最终值的二进制方法 - 感谢btilly这个想法。在if
- 语句中,替换以下内容:
// real + error < middle : middle is our new upper
Seek(ref upper_n, ref upper_d, lower_n, lower_d, (un, ud) => (lower_d + ud) * (value + maxError) < (lower_n + un));
和
// middle < real - error : middle is our new lower
Seek(ref lower_n, ref lower_d, upper_n, upper_d, (ln, ld) => (ln + upper_n) < (value - maxError) * (ld + upper_d));
以下是Seek
方法实现:
/// <summary>
/// Binary seek for the value where f() becomes false.
/// </summary>
void Seek(ref int a, ref int b, int ainc, int binc, Func<int, int, bool> f)
{
a += ainc;
b += binc;
if (f(a, b))
{
int weight = 1;
do
{
weight *= 2;
a += ainc * weight;
b += binc * weight;
}
while (f(a, b));
do
{
weight /= 2;
int adec = ainc * weight;
int bdec = binc * weight;
if (!f(a - adec, b - bdec))
{
a -= adec;
b -= bdec;
}
}
while (weight > 1);
}
}
算法比较表
您可能希望将表格复制到文本编辑器以进行全屏查看。
Accuracy: 1.0E-3 | Stern-Brocot OPTIMIZED | Eppstein | Richards
Input | Result Error Iterations Iterations | Result Error Iterations | Result Error Iterations
======================| =====================================================| =========================================| =========================================
0 | 0/1 (zero) 0 0 0 | 0/1 (zero) 0 0 | 0/1 (zero) 0 0
1 | 1/1 0 0 0 | 1001/1000 1.0E-3 1 | 1/1 0 0
3 | 3/1 0 0 0 | 1003/334 1.0E-3 1 | 3/1 0 0
-1 | -1/1 0 0 0 | -1001/1000 1.0E-3 1 | -1/1 0 0
-3 | -3/1 0 0 0 | -1003/334 1.0E-3 1 | -3/1 0 0
0.999999 | 1/1 1.0E-6 0 0 | 1000/1001 -1.0E-3 2 | 1/1 1.0E-6 0
-0.999999 | -1/1 1.0E-6 0 0 | -1000/1001 -1.0E-3 2 | -1/1 1.0E-6 0
1.000001 | 1/1 -1.0E-6 0 0 | 1001/1000 1.0E-3 1 | 1/1 -1.0E-6 0
-1.000001 | -1/1 -1.0E-6 0 0 | -1001/1000 1.0E-3 1 | -1/1 -1.0E-6 0
0.50 (1/2) | 1/2 0 1 1 | 999/1999 -5.0E-4 2 | 1/2 0 1
0.33... (1/3) | 1/3 0 2 2 | 999/2998 -3.3E-4 2 | 1/3 0 1
0.67... (2/3) | 2/3 0 2 2 | 999/1498 3.3E-4 3 | 2/3 0 2
0.25 (1/4) | 1/4 0 3 3 | 999/3997 -2.5E-4 2 | 1/4 0 1
0.11... (1/9) | 1/9 0 8 4 | 999/8992 -1.1E-4 2 | 1/9 0 1
0.09... (1/11) | 1/11 0 10 5 | 999/10990 -9.1E-5 2 | 1/11 0 1
0.62... (307/499) | 8/13 2.5E-4 5 5 | 913/1484 -2.2E-6 8 | 8/13 2.5E-4 5
0.14... (33/229) | 15/104 8.7E-4 20 9 | 974/6759 -4.5E-6 6 | 16/111 2.7E-4 3
0.05... (33/683) | 7/145 -8.4E-4 24 10 | 980/20283 1.5E-6 7 | 10/207 -1.5E-4 4
0.18... (100/541) | 17/92 -3.3E-4 11 10 | 939/5080 -2.0E-6 8 | 17/92 -3.3E-4 4
0.06... (33/541) | 5/82 -3.7E-4 19 8 | 995/16312 -1.9E-6 6 | 5/82 -3.7E-4 4
0.1 | 1/10 0 9 5 | 999/9991 -1.0E-4 2 | 1/10 0 1
0.2 | 1/5 0 4 3 | 999/4996 -2.0E-4 2 | 1/5 0 1
0.3 | 3/10 0 5 5 | 998/3327 -1.0E-4 4 | 3/10 0 3
0.4 | 2/5 0 3 3 | 999/2497 2.0E-4 3 | 2/5 0 2
0.5 | 1/2 0 1 1 | 999/1999 -5.0E-4 2 | 1/2 0 1
0.6 | 3/5 0 3 3 | 1000/1667 -2.0E-4 4 | 3/5 0 3
0.7 | 7/10 0 5 5 | 996/1423 -1.0E-4 4 | 7/10 0 3
0.8 | 4/5 0 4 3 | 997/1246 2.0E-4 3 | 4/5 0 2
0.9 | 9/10 0 9 5 | 998/1109 -1.0E-4 4 | 9/10 0 3
0.01 | 1/100 0 99 8 | 999/99901 -1.0E-5 2 | 1/100 0 1
0.001 | 1/1000 0 999 11 | 999/999001 -1.0E-6 2 | 1/1000 0 1
0.0001 | 1/9991 9.0E-4 9990 15 | 999/9990001 -1.0E-7 2 | 1/10000 0 1
1E-05 | 1/99901 9.9E-4 99900 18 | 1000/99999999 1.0E-8 3 | 1/99999 1.0E-5 1
0.33333333333 | 1/3 1.0E-11 2 2 | 1000/3001 -3.3E-4 2 | 1/3 1.0E-11 1
0.3 | 3/10 0 5 5 | 998/3327 -1.0E-4 4 | 3/10 0 3
0.33 | 30/91 -1.0E-3 32 8 | 991/3003 1.0E-5 3 | 33/100 0 2
0.333 | 167/502 -9.9E-4 169 11 | 1000/3003 1.0E-6 3 | 333/1000 0 2
0.7777 | 7/9 1.0E-4 5 4 | 997/1282 -1.1E-5 4 | 7/9 1.0E-4 3
0.101 | 10/99 1.0E-4 18 10 | 919/9099 1.1E-6 5 | 10/99 1.0E-4 3
0.10001 | 1/10 -1.0E-4 9 5 | 1/10 -1.0E-4 4 | 1/10 -1.0E-4 2
0.100000001 | 1/10 -1.0E-8 9 5 | 1000/9999 1.0E-4 3 | 1/10 -1.0E-8 2
0.001001 | 1/999 1.0E-6 998 11 | 1/999 1.0E-6 3 | 1/999 1.0E-6 1
0.0010000001 | 1/1000 -1.0E-7 999 11 | 1000/999999 9.0E-7 3 | 1/1000 -1.0E-7 2
0.11 | 10/91 -1.0E-3 18 9 | 1000/9091 -1.0E-5 4 | 10/91 -1.0E-3 2
0.1111 | 1/9 1.0E-4 8 4 | 1000/9001 -1.1E-5 2 | 1/9 1.0E-4 1
0.111111111111 | 1/9 1.0E-12 8 4 | 1000/9001 -1.1E-4 2 | 1/9 1.0E-12 1
1 | 1/1 0 0 0 | 1001/1000 1.0E-3 1 | 1/1 0 0
-1 | -1/1 0 0 0 | -1001/1000 1.0E-3 1 | -1/1 0 0
-0.5 | -1/2 0 1 1 | -999/1999 -5.0E-4 2 | -1/2 0 1
3.14 | 22/7 9.1E-4 6 4 | 964/307 2.1E-5 3 | 22/7 9.1E-4 1
3.1416 | 22/7 4.0E-4 6 4 | 732/233 9.8E-6 3 | 22/7 4.0E-4 1
3.14... (pi) | 22/7 4.0E-4 6 4 | 688/219 -1.3E-5 4 | 22/7 4.0E-4 1
0.14 | 7/50 0 13 7 | 995/7107 2.0E-5 3 | 7/50 0 2
0.1416 | 15/106 -6.4E-4 21 8 | 869/6137 9.2E-7 5 | 16/113 -5.0E-5 2
2.72... (e) | 68/25 6.3E-4 7 7 | 878/323 -5.7E-6 8 | 87/32 1.7E-4 5
0.141592653589793 | 15/106 -5.9E-4 21 8 | 991/6999 -7.0E-6 4 | 15/106 -5.9E-4 2
-1.33333333333333 | -4/3 2.5E-15 2 2 | -1001/751 -3.3E-4 2 | -4/3 2.5E-15 1
-1.3 | -13/10 0 5 5 | -992/763 1.0E-4 3 | -13/10 0 2
-1.33 | -97/73 -9.3E-4 26 8 | -935/703 1.1E-5 3 | -133/100 0 2
-1.333 | -4/3 2.5E-4 2 2 | -1001/751 -8.3E-5 2 | -4/3 2.5E-4 1
-1.33333337 | -4/3 -2.7E-8 2 2 | -999/749 3.3E-4 3 | -4/3 -2.7E-8 2
-1.7 | -17/10 0 5 5 | -991/583 -1.0E-4 4 | -17/10 0 3
-1.37 | -37/27 2.7E-4 7 7 | -996/727 1.0E-5 7 | -37/27 2.7E-4 5
-1.33337 | -4/3 -2.7E-5 2 2 | -999/749 3.1E-4 3 | -4/3 -2.7E-5 2
0.047619 | 1/21 1.0E-6 20 6 | 1000/21001 -4.7E-5 2 | 1/21 1.0E-6 1
12.125 | 97/8 0 7 4 | 982/81 -1.3E-4 2 | 97/8 0 1
5.5 | 11/2 0 1 1 | 995/181 -5.0E-4 2 | 11/2 0 1
0.1233333333333 | 9/73 -3.7E-4 16 8 | 971/7873 -3.4E-6 4 | 9/73 -3.7E-4 2
0.7454545454545 | 38/51 -4.8E-4 15 8 | 981/1316 -1.9E-5 6 | 38/51 -4.8E-4 4
0.01024801004 | 2/195 8.2E-4 98 9 | 488/47619 2.0E-8 13 | 2/195 8.2E-4 3
0.99011 | 91/92 -9.9E-4 91 8 | 801/809 1.3E-6 5 | 100/101 -1.1E-5 2
0.9901134545 | 91/92 -9.9E-4 91 8 | 601/607 1.9E-6 5 | 100/101 -1.5E-5 2
0.19999999 | 1/5 5.0E-8 4 3 | 1000/5001 -2.0E-4 2 | 1/5 5.0E-8 1
0.20000001 | 1/5 -5.0E-8 4 3 | 1000/4999 2.0E-4 3 | 1/5 -5.0E-8 2
5.0183168565E-05 | 1/19908 9.5E-4 19907 16 | 1000/19927001 -5.0E-8 2 | 1/19927 5.2E-12 1
3.909E-07 | 1/2555644 1.0E-3 2555643 23 | 1/1 2.6E6 (!) 1 | 1/2558199 1.1E-8 1
88900003.001 |88900003/1 -1.1E-11 0 0 |88900004/1 1.1E-8 1 |88900003/1 -1.1E-11 0
0.26... (5/19) | 5/19 0 7 6 | 996/3785 -5.3E-5 4 | 5/19 0 3
0.61... (37/61) | 17/28 9.7E-4 8 7 | 982/1619 -1.7E-5 8 | 17/28 9.7E-4 5
| | |
Accuracy: 1.0E-4 | Stern-Brocot OPTIMIZED | Eppstein | Richards
Input | Result Error Iterations Iterations | Result Error Iterations | Result Error Iterations
======================| =====================================================| =========================================| =========================================
0.62... (307/499) | 227/369 -8.8E-5 33 11 | 9816/15955 -2.0E-7 8 | 299/486 -6.7E-6 6
0.05... (33/683) | 23/476 6.4E-5 27 12 | 9989/206742 1.5E-7 7 | 23/476 6.4E-5 5
0.06... (33/541) | 28/459 6.6E-5 24 12 | 9971/163464 -1.9E-7 6 | 33/541 0 5
1E-05 | 1/99991 9.0E-5 99990 18 | 10000/999999999 1.0E-9 3 | 1/99999 1.0E-5 1
0.333 | 303/910 -9.9E-5 305 12 | 9991/30003 1.0E-7 3 | 333/1000 0 2
0.7777 | 556/715 -1.0E-4 84 12 | 7777/10000 0 8 | 1109/1426 -1.8E-7 4
3.14... (pi) | 289/92 -9.2E-5 19 8 | 9918/3157 -8.1E-7 4 | 333/106 -2.6E-5 2
2.72... (e) | 193/71 1.0E-5 10 9 | 9620/3539 6.3E-8 11 | 193/71 1.0E-5 7
0.7454545454545 | 41/55 6.1E-14 16 8 | 9960/13361 -1.8E-6 6 | 41/55 6.1E-14 5
0.01024801004 | 7/683 8.7E-5 101 12 | 9253/902907 -1.3E-10 16 | 7/683 8.7E-5 5
0.99011 | 100/101 -1.1E-5 100 8 | 901/910 -1.1E-7 6 | 100/101 -1.1E-5 2
0.9901134545 | 100/101 -1.5E-5 100 8 | 8813/8901 1.6E-8 7 | 100/101 -1.5E-5 2
0.26... (5/19) | 5/19 0 7 6 | 9996/37985 -5.3E-6 4 | 5/19 0 3
0.61... (37/61) | 37/61 0 10 8 | 9973/16442 -1.6E-6 8 | 37/61 0 7
效果比较
我进行了详细的速度测试并绘制了结果。不看质量而只考虑速度:
Stern-Brocot和Richards比较:
如果你不需要最低分母分数,理查兹是个不错的选择。
答案 2 :(得分:14)
我知道你说你在网上搜索过,但如果你错过了下面的文章,那可能会有所帮助。它包含Pascal中的代码示例。
Algorithm To Convert A Decimal To A Fraction *
或者,作为其标准库的一部分,Ruby具有处理有理数的代码。它可以从浮点数转换为有理数,反之亦然。我相信你也可以查看代码。找到文档here。我知道你没有使用Ruby,但它可能有助于查看算法。
此外,如果使用运行在.net框架之上的IronRuby,则可以从C#调用Ruby代码(甚至在C#代码文件中编写Ruby代码)。
* 更新为新链接,因为原始网址已损坏(http://homepage.smc.edu/kennedy_john/DEC2FRAC.pdf)
答案 3 :(得分:9)
我找到了Matt引用的相同文章,我花了一秒钟用Python实现了它。也许在代码中看到相同的想法会使它更清晰。当然,你在C#中请求答案,我用Python给你,但这是一个相当简单的程序,我相信它很容易翻译。参数为num
(您要转换为理性的十进制数)和epsilon
(num
与计算的理性之间允许的最大差值)。一些快速测试运行发现,当epsilon
大约为1e-4时,通常只需要两到三次迭代就可以收敛。
def dec2frac(num, epsilon, max_iter=20):
d = [0, 1] + ([0] * max_iter)
z = num
n = 1
t = 1
while num and t < max_iter and abs(n/d[t] - num) > epsilon:
t += 1
z = 1/(z - int(z))
d[t] = d[t-1] * int(z) + d[t-2]
# int(x + 0.5) is equivalent to rounding x.
n = int(num * d[t] + 0.5)
return n, d[t]
编辑:我刚刚注意到您希望他们使用重复小数的说明。我不知道任何语法有支持重复小数的语法,所以我不确定如何处理它们,但通过这种方法运行0.6666666和0.166666返回正确的结果(2/3和1/6,分别地)。
另一个编辑(我不认为这会如此有趣!):如果你想进一步了解这个算法背后的理论,Wikipedia has an excellent page on the Euclidian algorithm
答案 4 :(得分:5)
你不能代表.net中的重复小数,所以我会忽略你问题的那一部分。
您只能表示有限且数量相对较少的数字。
有一个非常简单的算法:
x
n
(10^n * x) / 10^n
所以,如果你有0.44,你会计算2位是小数点 - n = 2,然后写
(0.44 * 10^2) / 10^2
44 / 100
11 / 25
答案 5 :(得分:5)
这是Will Brown的python示例的C#版本。我也改变它来处理单独的整数(例如“2 1/8”而不是“17/8”)。
public static string DoubleToFraction(double num, double epsilon = 0.0001, int maxIterations = 20)
{
double[] d = new double[maxIterations + 2];
d[1] = 1;
double z = num;
double n = 1;
int t = 1;
int wholeNumberPart = (int)num;
double decimalNumberPart = num - Convert.ToDouble(wholeNumberPart);
while (t < maxIterations && Math.Abs(n / d[t] - num) > epsilon)
{
t++;
z = 1 / (z - (int)z);
d[t] = d[t - 1] * (int)z + d[t - 2];
n = (int)(decimalNumberPart * d[t] + 0.5);
}
return string.Format((wholeNumberPart > 0 ? wholeNumberPart.ToString() + " " : "") + "{0}/{1}",
n.ToString(),
d[t].ToString()
);
}
答案 6 :(得分:4)
我写了一个快速的课程,运行得相当快,并给出了我期望的结果。您也可以选择您的精度。从我看到的任何代码都可以更加简单,并且运行得很快。
//Written By Brian Dobony
public static class Fraction
{
public static string ConvertDecimal(Double NumberToConvert, int DenominatorPercision = 32)
{
int WholeNumber = (int)NumberToConvert;
double DecimalValue = NumberToConvert - WholeNumber;
double difference = 1;
int numerator = 1;
int denominator = 1;
// find closest value that matches percision
// Automatically finds Fraction in simplified form
for (int y = 2; y < DenominatorPercision + 1; y++)
{
for (int x = 1; x < y; x++)
{
double tempdif = Math.Abs(DecimalValue - (double)x / (double)y);
if (tempdif < difference)
{
numerator = x;
denominator = y;
difference = tempdif;
// if exact match is found return it
if (difference == 0)
{
return FractionBuilder(WholeNumber, numerator, denominator);
}
}
}
}
return FractionBuilder(WholeNumber, numerator, denominator);
}
private static string FractionBuilder(int WholeNumber, int Numerator, int Denominator)
{
if (WholeNumber == 0)
{
return Numerator + @"/" + Denominator;
}
else
{
return WholeNumber + " " + Numerator + @"/" + Denominator;
}
}
}
答案 7 :(得分:3)
这是Ian Richards / John Kennedy编写的算法的C#版本。其他答案在这里使用相同的算法:
它不处理无穷大和NaN。
此算法 fast 。
例如值和与其他算法的比较,请参阅my other answer
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);
}
答案 8 :(得分:2)
我想出了一个很晚的答案。代码取自an article from
Richards published in 1981并写在c
。
inline unsigned int richards_solution(double const& x0, unsigned long long& num, unsigned long long& den, double& sign, double const& err = 1e-10){
sign = my::sign(x0);
double g(std::abs(x0));
unsigned long long a(0);
unsigned long long b(1);
unsigned long long c(1);
unsigned long long d(0);
unsigned long long s;
unsigned int iter(0);
do {
s = std::floor(g);
num = a + s*c;
den = b + s*d;
a = c;
b = d;
c = num;
d = den;
g = 1.0/(g-s);
if(err>std::abs(sign*num/den-x0)){ return iter; }
} while(iter++<1e6);
std::cerr<<__PRETTY_FUNCTION__<<" : failed to find a fraction for "<<x0<<std::endl;
return 0;
}
我在这里重写了btilly_solution的实现:
inline unsigned int btilly_solution(double x, unsigned long long& num, unsigned long long& den, double& sign, double const& err = 1e-10){
sign = my::sign(x);
num = std::floor(std::abs(x));
x = std::abs(x)-num;
unsigned long long lower_n(0);
unsigned long long lower_d(1);
unsigned long long upper_n(1);
unsigned long long upper_d(1);
unsigned long long middle_n;
unsigned long long middle_d;
unsigned int iter(0);
do {
middle_n = lower_n + upper_n;
middle_d = lower_d + upper_d;
if(middle_d*(x+err)<middle_n){
upper_n = middle_n;
upper_d = middle_d;
} else if(middle_d*(x-err)>middle_n) {
lower_n = middle_n;
lower_d = middle_d;
} else {
num = num*middle_d+middle_n;
den = middle_d;
return iter;
}
} while(iter++<1e6);
den = 1;
std::cerr<<__PRETTY_FUNCTION__<<" : failed to find a fraction for "<<x+num<<std::endl;
return 0;
}
在这里,我提出了一些错误为1e-10
:
------------------------------------------------------ |
btilly 0.166667 0.166667=1/6 in 5 iterations | 1/6
richard 0.166667 0.166667=1/6 in 1 iterations |
------------------------------------------------------ |
btilly 0.333333 0.333333=1/3 in 2 iterations | 1/3
richard 0.333333 0.333333=1/3 in 1 iterations |
------------------------------------------------------ |
btilly 0.142857 0.142857=1/7 in 6 iterations | 1/7
richard 0.142857 0.142857=1/7 in 1 iterations |
------------------------------------------------------ |
btilly 0.714286 0.714286=5/7 in 4 iterations | 5/7
richard 0.714286 0.714286=5/7 in 4 iterations |
------------------------------------------------------ |
btilly 1e-07 1.001e-07=1/9990010 in 9990009 iteration | 0.0000001
richard 1e-07 1e-07=1/10000000 in 1 iterations |
------------------------------------------------------ |
btilly 3.66667 3.66667=11/3 in 2 iterations | 11/3
richard 3.66667 3.66667=11/3 in 3 iterations |
------------------------------------------------------ |
btilly 1.41421 1.41421=114243/80782 in 25 iterations | sqrt(2)
richard 1.41421 1.41421=114243/80782 in 13 iterations |
------------------------------------------------------ |
btilly 3.14159 3.14159=312689/99532 in 317 iterations | pi
richard 3.14159 3.14159=312689/99532 in 7 iterations |
------------------------------------------------------ |
btilly 2.71828 2.71828=419314/154257 in 36 iterations | e
richard 2.71828 2.71828=517656/190435 in 14 iterations |
------------------------------------------------------ |
btilly 0.390885 0.390885=38236/97819 in 60 iterations | random
richard 0.390885 0.390885=38236/97819 in 13 iterations |
正如你所看到的,这两种方法给出的结果大致相同,但是 理查兹的一种方式更有效,更容易实施。
要编译我的代码,您需要my::sign
的定义,这只是一个
返回变量符号的函数。这是我的实现
namespace my{
template<typename Type> inline constexpr
int sign_unsigned(Type x){ return Type(0)<x; }
template<typename Type> inline constexpr
int sign_signed(Type x){ return (Type(0)<x)-(x<Type(0)); }
template<typename Type> inline constexpr
int sign(Type x) { return std::is_signed<Type>()?sign_signed(x):sign_unsigned(x); }
}
我猜this answer指的是同一算法。我之前没有看到......
答案 9 :(得分:2)
David Eppstein,UC Irvine的这个算法,基于连续分数的理论,最初是在C中,由我翻译成C#。它生成的分数满足误差范围,但大多数看起来不如我的其他答案中的解决方案。例如。 0.5
变为999/1999
,而1/2
在向用户展示时会首选(如果您需要,请参阅我的other answers)。
有一个重载将错误边距指定为double(相对于值,而不是绝对错误)。对于Fraction
类型,请参阅我的其他答案。
顺便说一句,如果您的分数变大,请将相关的int
更改为long
。与其他算法相比,这个算法很容易溢出。
例如值和与其他算法的比较,请参阅my other answer
public Fraction RealToFraction(double value, int maxDenominator)
{
// http://www.ics.uci.edu/~eppstein/numth/frap.c
// Find rational approximation to given real number
// David Eppstein / UC Irvine / 8 Aug 1993
// With corrections from Arno Formella, May 2008
if (value == 0.0)
{
return new Fraction(0, 1);
}
int sign = Math.Sign(value);
if (sign == -1)
{
value = Math.Abs(value);
}
int[,] m = { { 1, 0 }, { 0, 1 } };
int ai = (int) value;
// Find terms until denominator gets too big
while (m[1, 0] * ai + m[1, 1] <= maxDenominator)
{
int 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;
value = 1.0 / (value - ai);
// 0x7FFFFFFF = Assumes 32 bit floating point just like in the C implementation.
// This check includes Double.IsInfinity(). Even though C# double is 64 bits,
// the algorithm sometimes fails when trying to increase this value too much. So
// I kept it. Anyway, it works.
if (value > 0x7FFFFFFF)
{
break;
}
ai = (int) value;
}
// Two approximations are calculated: one on each side of the input
// The result of the first one is the current value. Below the other one
// is calculated and it is returned.
ai = (maxDenominator - 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];
return new Fraction(sign * m[0, 0], m[1, 0]);
}
public Fraction RealToFraction(double value, double accuracy)
{
if (accuracy <= 0.0 || accuracy >= 1.0)
{
throw new ArgumentOutOfRangeException("accuracy", "Must be > 0 and < 1.");
}
int maxDenominator = (int) Math.Ceiling(Math.Abs(1.0 / (value * accuracy)));
if (maxDenominator < 1)
{
maxDenominator = 1;
}
return RealToFraction(value, maxDenominator);
}
答案 10 :(得分:1)
我最近不得不执行使用存储在SQL Server数据库中的十进制数据类型的任务。在表示层,此值在TextBox中被编辑为小数值。这里的复杂性是使用十进制数据类型,与int或long相比,它包含一些相当大的值。因此,为了减少数据溢出的机会,我在整个转换过程中坚持使用十进制数据类型。
在开始之前,我想评论一下Kirk之前的回答。只要没有做出任何假设,他就绝对正确。但是,如果开发人员只在十进制数据类型的范围内查找重复模式.3333333 ...可以表示为1/3。可以在basic-mathematics.com找到该算法的示例。同样,这意味着您必须根据可用信息进行假设,并且使用此方法仅捕获重复小数的一小部分。但是对于小数字应该没问题。
继续前进,让我简要介绍一下我的解决方案。如果您想阅读包含其他代码的完整示例,我创建了一个更详细的blog post。
将十进制数据类型转换为字符串分数
public static void DecimalToFraction(decimal value, ref decimal sign, ref decimal numerator, ref decimal denominator)
{
const decimal maxValue = decimal.MaxValue / 10.0M;
// e.g. .25/1 = (.25 * 100)/(1 * 100) = 25/100 = 1/4
var tmpSign = value < decimal.Zero ? -1 : 1;
var tmpNumerator = Math.Abs(value);
var tmpDenominator = decimal.One;
// While numerator has a decimal value
while ((tmpNumerator - Math.Truncate(tmpNumerator)) > 0 &&
tmpNumerator < maxValue && tmpDenominator < maxValue)
{
tmpNumerator = tmpNumerator * 10;
tmpDenominator = tmpDenominator * 10;
}
tmpNumerator = Math.Truncate(tmpNumerator); // Just in case maxValue boundary was reached.
ReduceFraction(ref tmpNumerator, ref tmpDenominator);
sign = tmpSign;
numerator = tmpNumerator;
denominator = tmpDenominator;
}
public static string DecimalToFraction(decimal value)
{
var sign = decimal.One;
var numerator = decimal.One;
var denominator = decimal.One;
DecimalToFraction(value, ref sign, ref numerator, ref denominator);
return string.Format("{0}/{1}", (sign * numerator).ToString().TruncateDecimal(),
denominator.ToString().TruncateDecimal());
}
这很简单,其中DecimalToFraction(十进制值)只不过是第一个方法的简化入口点,它提供了对组成分数的所有组件的访问。如果您的小数点为.325,则将其除以10除以小数位数的幂。最后减少分数。并且,在此示例中.325 = 325/10 ^ 3 = 325/1000 = 13/40。
接下来,走向另一个方向。
将字符串分数转换为十进制数据类型
static readonly Regex FractionalExpression = new Regex(@"^(?<sign>[-])?(?<numerator>\d+)(/(?<denominator>\d+))?$");
public static decimal? FractionToDecimal(string fraction)
{
var match = FractionalExpression.Match(fraction);
if (match.Success)
{
// var sign = Int32.Parse(match.Groups["sign"].Value + "1");
var numerator = Int32.Parse(match.Groups["sign"].Value + match.Groups["numerator"].Value);
int denominator;
if (Int32.TryParse(match.Groups["denominator"].Value, out denominator))
return denominator == 0 ? (decimal?)null : (decimal)numerator / denominator;
if (numerator == 0 || numerator == 1)
return numerator;
}
return null;
}
转换回小数也很简单。在这里,我们解析小数分量,将它们存储在我们可以使用的东西中(这里是十进制值)并执行我们的分割。
答案 11 :(得分:1)
此问题最常见的解决方案是Richards’ algorithm和the Stern-Brocot algorithm,由btilly和Jay Zed与speed optimalization实施。 Richards的算法是最快的,但不能保证返回最佳分数。
我有一个解决这个问题的方法,它总能提供最好的分数,并且比上面的所有算法都快。这是C#中的算法(下面的解释和速度测试)。
这是一个没有注释的简短算法。最后在源代码中提供了完整版本。
public static Fraction DoubleToFractionSjaak(double value, double accuracy)
{
int sign = value < 0 ? -1 : 1;
value = value < 0 ? -value : value;
int integerpart = (int)value;
value -= integerpart;
double minimalvalue = value - accuracy;
if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
double maximumvalue = value + accuracy;
if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
int a = 0;
int b = 1;
int c = 1;
int d = (int)(1 / maximumvalue);
while (true)
{
int n = (int)((b * minimalvalue - a) / (c - d * minimalvalue));
if (n == 0) break;
a += n * c;
b += n * d;
n = (int)((c - d * maximumvalue) / (b * maximumvalue - a));
if (n == 0) break;
c += n * a;
d += n * b;
}
int denominator = b + d;
return new Fraction(sign * (integerpart * denominator + (a + c)), denominator);
}
其中Fraction是一个存储分数的简单类,如下所示:
public class Fraction
{
public int Numerator { get; private set; }
public int Denominator { get; private set; }
public Fraction(int numerator, int denominator)
{
Numerator = numerator;
Denominator = denominator;
}
}
与上述其他解决方案一样,我的解决方案基于持续分数。其他解决方案,例如来自Eppstein的解决方案或基于重复小数的解决方案被证明更慢和/或给出次优结果。
续分数
基于连续分数的解决方案主要基于两种算法,两者都在Ian Richards在1981年发表的here文章中描述。他称之为“慢连续分数算法”和“快速连续分数算法”。第一种被称为Stern-Brocot算法,而后者被称为Richards算法。
我的算法(简短说明)
要完全理解我的算法,你需要阅读Ian Richards的文章,或者至少了解Farey对是什么。此外,请阅读本文末尾带有注释的算法。
该算法使用Farey对,包含左和右分数。通过反复取得媒体,它正在接近目标值。这就像慢速算法一样,但有两个主要区别:
或者,检查目标值的右侧和左侧。如果算法不能产生更接近目标值的结果,则该过程结束。由此产生的媒体是最佳解决方案。
我使用以下算法在笔记本电脑上进行了一些速度测试:
我省略了btilly的原始慢速算法,因为它的最坏情况表现不佳。
测试集
我选择一组目标值(非常随意)并用5种不同的精度计算分数100000次。因为某些(未来)算法可能无法处理不正确的分数,所以只测试了从0.0到1.0的目标值。精度取自2至6位小数(0.005至0.0000005)的范围。使用了以下集合:
0.999999, 0.000001, 0.25
0.33, 0.333, 0.3333, 0.33333, 0.333333, 0.333333333333,
0.666666666666, 0.777777777777, 0.090909090909, 0.263157894737,
0.606557377049, 0.745454545454, 0.000050183168565,
pi - 3, e - 2.0, sqrt(2) - 1
结果
我做了13次测试。结果是整个数据集所需的毫秒数。
Run 1 Run 2 Run 3 Run 4 Run 5 Run 6 Run 7 Run 8 Run 9 Run 10 Run 11 Run 12 Run 13
1. 9091 9222 9070 9111 9091 9108 9293 9118 9115 9113 9102 9143 9121
2. 7071 7125 7077 6987 7126 6985 7037 6964 7023 6980 7053 7050 6999
3. 6903 7059 7062 6891 6942 6880 6882 6918 6853 6918 6893 6993 6966
4. 7546 7554 7564 7504 7483 7529 7510 7512 7517 7719 7513 7520 7514
5. 6839 6951 6882 6836 6854 6880 6846 7017 6874 6867 6828 6848 6864
结论(跳过分析)
即使没有统计分析,也很容易看出我的算法比其他测试算法更快。然而,与“快速算法”的最快变体的差异小于1%。改进的慢速算法比最快的算法“。
另一方面,即使是最慢的算法也会在不到一微秒的时间内平均进行计算。所以在正常情况下速度并不是真正的问题。在我看来,最好的算法主要是品味问题,因此请根据其他标准选择任何经过测试的算法。
下面的源代码包含所有使用的算法。它包括:
public class DoubleToFraction
{
// ===================================================
// Sjaak algorithm - original version
//
public static Fraction SjaakOriginal(double value, double accuracy)
{
// Split value in a sign, an integer part, a fractional part
int sign = value < 0 ? -1 : 1;
value = value < 0 ? -value : value;
int integerpart = (int)value;
value -= integerpart;
// check if the fractional part is near 0
double minimalvalue = value - accuracy;
if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
// check if the fractional part is near 1
double maximumvalue = value + accuracy;
if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
// The left fraction (a/b) is initially (0/1), the right fraction (c/d) is initially (1/1)
// Together they form a Farey pair.
// We will keep the left fraction below the minimumvalue and the right fraction above the maximumvalue
int a = 0;
int b = 1;
int c = 1;
int d = (int)(1 / maximumvalue);
// The first interation is performed above. Calculate maximum n where (n*a+c)/(n*b+d) >= maximumvalue
// This is the same as n <= 1/maximumvalue - 1, d will become n+1 = floor(1/maximumvalue)
// repeat forever (at least until we cannot close in anymore)
while (true)
{
// Close in from the left n times.
// Calculate maximum n where (a+n*c)/(b+n*d) <= minimalvalue
// This is the same as n <= (b * minimalvalue - a) / (c-d*minimalvalue)
int n = (int)((b * minimalvalue - a) / (c - d * minimalvalue));
// If we cannot close in from the left (and also not from the right anymore) the loop ends
if (n == 0) break;
// Update left fraction
a += n * c;
b += n * d;
// Close in from the right n times.
// Calculate maximum n where (n*a+c)/(n*b+d) >= maximumvalue
// This is the same as n <= (c - d * maximumvalue) / (b * maximumvalue - a)
n = (int)((c - d * maximumvalue) / (b * maximumvalue - a));
// If we cannot close in from the right (and also not from the left anymore) the loop ends
if (n == 0) break;
// Update right fraction
c += n * a;
d += n * b;
}
// We cannot close in anymore
// The best fraction will be the mediant of the left and right fraction = (a+c)/(b+d)
int denominator = b + d;
return new Fraction(sign * (integerpart * denominator + (a + c)), denominator);
}
// ===================================================
// Sjaak algorithm - faster version
//
public static Fraction SjaakFaster(double value, double accuracy)
{
int sign = value < 0 ? -1 : 1;
value = value < 0 ? -value : value;
int integerpart = (int)value;
value -= integerpart;
double minimalvalue = value - accuracy;
if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
double maximumvalue = value + accuracy;
if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
//int a = 0;
int b = 1;
//int c = 1;
int d = (int)(1 / maximumvalue);
double left_n = minimalvalue; // b * minimalvalue - a
double left_d = 1.0 - d * minimalvalue; // c - d * minimalvalue
double right_n = 1.0 - d * maximumvalue; // c - d * maximumvalue
double right_d = maximumvalue; // b * maximumvalue - a
while (true)
{
if (left_n < left_d) break;
int n = (int)(left_n / left_d);
//a += n * c;
b += n * d;
left_n -= n * left_d;
right_d -= n * right_n;
if (right_n < right_d) break;
n = (int)(right_n / right_d);
//c += n * a;
d += n * b;
left_d -= n * left_n;
right_n -= n * right_d;
}
int denominator = b + d;
int numerator = (int)(value * denominator + 0.5);
return new Fraction(sign * (integerpart * denominator + numerator), denominator);
}
// ===================================================
// Original Farley - Implemented by btilly
//
public static Fraction OriginalFarley(double value, double accuracy)
{
// Split value in a sign, an integer part, a fractional part
int sign = value < 0 ? -1 : 1;
value = value < 0 ? -value : value;
int integerpart = (int)value;
value -= integerpart;
// check if the fractional part is near 0
double minimalvalue = value - accuracy;
if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
// check if the fractional part is near 1
double maximumvalue = value + accuracy;
if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
// The lower fraction is 0/1
int lower_numerator = 0;
int lower_denominator = 1;
// The upper fraction is 1/1
int upper_numerator = 1;
int upper_denominator = 1;
while (true)
{
// The middle fraction is (lower_numerator + upper_numerator) / (lower_denominator + upper_denominator)
int middle_numerator = lower_numerator + upper_numerator;
int middle_denominator = lower_denominator + upper_denominator;
if (middle_denominator * maximumvalue < middle_numerator)
{
// real + error < middle : middle is our new upper
upper_numerator = middle_numerator;
upper_denominator = middle_denominator;
}
else if (middle_numerator < minimalvalue * middle_denominator)
{
// middle < real - error : middle is our new lower
lower_numerator = middle_numerator;
lower_denominator = middle_denominator;
}
else
{
return new Fraction(sign * (integerpart * middle_denominator + middle_numerator), middle_denominator);
}
}
}
// ===================================================
// Modified Farley - Implemented by btilly, Kay Zed
//
public static Fraction ModifiedFarley(double value, double accuracy)
{
// Split value in a sign, an integer part, a fractional part
int sign = value < 0 ? -1 : 1;
value = value < 0 ? -value : value;
int integerpart = (int)value;
value -= integerpart;
// check if the fractional part is near 0
double minimalvalue = value - accuracy;
if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
// check if the fractional part is near 1
double maximumvalue = value + accuracy;
if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
// The lower fraction is 0/1
int lower_numerator = 0;
int lower_denominator = 1;
// The upper fraction is 1/1
int upper_numerator = 1;
int upper_denominator = 1;
while (true)
{
// The middle fraction is (lower_numerator + upper_numerator) / (lower_denominator + upper_denominator)
int middle_numerator = lower_numerator + upper_numerator;
int middle_denominator = lower_denominator + upper_denominator;
if (middle_denominator * maximumvalue < middle_numerator)
{
// real + error < middle : middle is our new upper
ModifiedFarleySeek(ref upper_numerator, ref upper_denominator, lower_numerator, lower_denominator, (un, ud) => (lower_denominator + ud) * maximumvalue < (lower_numerator + un));
}
else if (middle_numerator < minimalvalue * middle_denominator)
{
// middle < real - error : middle is our new lower
ModifiedFarleySeek(ref lower_numerator, ref lower_denominator, upper_numerator, upper_denominator, (ln, ld) => (ln + upper_numerator) < minimalvalue * (ld + upper_denominator));
}
else
{
return new Fraction(sign * (integerpart * middle_denominator + middle_numerator), middle_denominator);
}
}
}
private static void ModifiedFarleySeek(ref int a, ref int b, int ainc, int binc, Func<int, int, bool> f)
{
// Binary seek for the value where f() becomes false
a += ainc;
b += binc;
if (f(a, b))
{
int weight = 1;
do
{
weight *= 2;
a += ainc * weight;
b += binc * weight;
}
while (f(a, b));
do
{
weight /= 2;
int adec = ainc * weight;
int bdec = binc * weight;
if (!f(a - adec, b - bdec))
{
a -= adec;
b -= bdec;
}
}
while (weight > 1);
}
}
// ===================================================
// Richards implementation by Jemery Hermann
//
public static Fraction RichardsJemeryHermann(double value, double accuracy, int maxIterations = 20)
{
// Split value in a sign, an integer part, a fractional part
int sign = value < 0 ? -1 : 1;
value = value < 0 ? -value : value;
int integerpart = (int)value;
value -= integerpart;
// check if the fractional part is near 0
double minimalvalue = value - accuracy;
if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
// check if the fractional part is near 1
double maximumvalue = value + accuracy;
if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
// Richards - Implemented by Jemery Hermann
double[] d = new double[maxIterations + 2];
d[1] = 1;
double z = value;
double n = 1;
int t = 1;
while (t < maxIterations && Math.Abs(n / d[t] - value) > accuracy)
{
t++;
z = 1 / (z - (int)z);
d[t] = d[t - 1] * (int)z + d[t - 2];
n = (int)(value * d[t] + 0.5);
}
return new Fraction(sign * (integerpart * (int)d[t] + (int)n), (int)d[t]);
}
// ===================================================
// Richards implementation by Kennedy
//
public static Fraction RichardsKennedy(double value, double accuracy)
{
// Split value in a sign, an integer part, a fractional part
int sign = value < 0 ? -1 : 1;
value = value < 0 ? -value : value;
int integerpart = (int)value;
value -= integerpart;
// check if the fractional part is near 0
double minimalvalue = value - accuracy;
if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
// check if the fractional part is near 1
double maximumvalue = value + accuracy;
if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
// Richards
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 = (int)(value * denominator + 0.5);
}
while (Math.Abs(value - (double)numerator / denominator) > accuracy && z != (int)z);
return new Fraction(sign * (integerpart * denominator + numerator), denominator);
}
// ===================================================
// Richards implementation by Sjaak
//
public static Fraction RichardsOriginal(double value, double accuracy)
{
// Split value in a sign, an integer part, a fractional part
int sign = value < 0 ? -1 : 1;
value = value < 0 ? -value : value;
int integerpart = (int)value;
value -= integerpart;
// check if the fractional part is near 0
double minimalvalue = value - accuracy;
if (minimalvalue < 0.0) return new Fraction(sign * integerpart, 1);
// check if the fractional part is near 1
double maximumvalue = value + accuracy;
if (maximumvalue > 1.0) return new Fraction(sign * (integerpart + 1), 1);
// Richards
double z = value;
int denominator0 = 0;
int denominator1 = 1;
int numerator0 = 1;
int numerator1 = 0;
int n = (int)z;
while (true)
{
z = 1.0 / (z - n);
n = (int)z;
int temp = denominator1;
denominator1 = denominator1 * n + denominator0;
denominator0 = temp;
temp = numerator1;
numerator1 = numerator1 * n + numerator0;
numerator0 = temp;
double d = (double)numerator1 / denominator1;
if (d > minimalvalue && d < maximumvalue) break;
}
return new Fraction(sign * (integerpart * denominator1 + numerator1), denominator1);
}
}
答案 12 :(得分:1)
重复的小数可以用两个有限的小数表示:重复前的左侧部分和重复的部分。例如。 1.6181818... = 1.6 + 0.1*(0.18...)
。可以将其视为a + b * sum(c * 10**-(d*k) for k in range(1, infinity))
(在此处使用Python表示法)。在我的示例中,a=1.6
,b=0.1
,c=18
,d=2
(c
中的位数)。无限和可以简化(sum(r**k for r in range(1, infinity)) == r / (1 - r)
,如果我正确地回忆),产生a + b * (c * 10**-d) / (1 - c * 10**-d))
,有限比率。也就是说,从a
,b
,c
和d
开始作为有理数,最后你会得到另一个。
(这详细说明了柯克布罗德赫斯特的回答,这是正确的,但不包括重复的小数。我不保证我没有犯过任何错误,但我相信一般方法有效。)< / p>
答案 13 :(得分:1)
我的2美分。这是btilly优秀算法的VB.NET版本:
Public Shared Sub float_to_fraction(x As Decimal, ByRef Numerator As Long, ByRef Denom As Long, Optional ErrMargin As Decimal = 0.001)
Dim n As Long = Int(Math.Floor(x))
x -= n
If x < ErrMargin Then
Numerator = n
Denom = 1
Return
ElseIf x >= 1 - ErrMargin Then
Numerator = n + 1
Denom = 1
Return
End If
' The lower fraction is 0/1
Dim lower_n As Integer = 0
Dim lower_d As Integer = 1
' The upper fraction is 1/1
Dim upper_n As Integer = 1
Dim upper_d As Integer = 1
Dim middle_n, middle_d As Decimal
While True
' The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
middle_n = lower_n + upper_n
middle_d = lower_d + upper_d
' If x + error < middle
If middle_d * (x + ErrMargin) < middle_n Then
' middle is our new upper
upper_n = middle_n
upper_d = middle_d
' Else If middle < x - error
ElseIf middle_n < (x - ErrMargin) * middle_d Then
' middle is our new lower
lower_n = middle_n
lower_d = middle_d
' Else middle is our best fraction
Else
Numerator = n * middle_d + middle_n
Denom = middle_d
Return
End If
End While
End Sub
答案 14 :(得分:1)
以下是它的工作原理:
代码预览:
private static string dec2frac(double dbl)
{
char neg = ' ';
double dblDecimal = dbl;
if (dblDecimal == (int) dblDecimal) return dblDecimal.ToString(); //return no if it's not a decimal
if (dblDecimal < 0)
{
dblDecimal = Math.Abs(dblDecimal);
neg = '-';
}
var whole = (int) Math.Truncate(dblDecimal);
string decpart = dblDecimal.ToString().Replace(Math.Truncate(dblDecimal) + ".", "");
double rN = Convert.ToDouble(decpart);
double rD = Math.Pow(10, decpart.Length);
string rd = recur(decpart);
int rel = Convert.ToInt32(rd);
if (rel != 0)
{
rN = rel;
rD = (int) Math.Pow(10, rd.Length) - 1;
}
//just a few prime factors for testing purposes
var primes = new[] {41, 43, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2};
foreach (int i in primes) reduceNo(i, ref rD, ref rN);
rN = rN + (whole*rD);
return string.Format("{0}{1}/{2}", neg, rN, rD);
}
感谢@Darius给我一个如何解决重复小数的想法:)
答案 15 :(得分:0)
这是一个用VB实现的算法,用于转换我多年前写的Floating Point Decimal to Integer Fraction。
基本上你以分子= 0和分母= 1开头,如果商小于小数输入,则在分子上加1,如果商大于小数输入,则在分母上加1。重复,直到达到所需的精度。
答案 16 :(得分:0)
如果我是你,我会通过以某种方式标记的重复转换字符串来处理“在.NET中没有重复小数”的问题。
E.g。 1/3可以表示为“0.R3” 1/60可以表示为“0.01R6”
我需要从double或decimal显式转换,因为这些值只能转换为接近的分数。来自int的隐式转换是可以的。
您可以使用结构并将分数(f)存储在两个长度p和q中,使得f = p / q,q!= 0和gcd(p,q)== 1.
答案 17 :(得分:0)
在这里,您可以使用将Decimal转换为分数的方法:
/// <summary>
/// Converts Decimals into Fractions.
/// </summary>
/// <param name="value">Decimal value</param>
/// <returns>Fraction in string type</returns>
public string DecimalToFraction(double value)
{
string result;
double numerator, realValue = value;
int num, den, decimals, length;
num = (int)value;
value = value - num;
value = Math.Round(value, 5);
length = value.ToString().Length;
decimals = length - 2;
numerator = value;
for (int i = 0; i < decimals; i++)
{
if (realValue < 1)
{
numerator = numerator * 10;
}
else
{
realValue = realValue * 10;
numerator = realValue;
}
}
den = length - 2;
string ten = "1";
for (int i = 0; i < den; i++)
{
ten = ten + "0";
}
den = int.Parse(ten);
num = (int)numerator;
result = SimplifiedFractions(num, den);
return result;
}
/// <summary>
/// Converts Fractions into Simplest form.
/// </summary>
/// <param name="num">Numerator</param>
/// <param name="den">Denominator</param>
/// <returns>Simplest Fractions in string type</returns>
string SimplifiedFractions(int num, int den)
{
int remNum, remDen, counter;
if (num > den)
{
counter = den;
}
else
{
counter = num;
}
for (int i = 2; i <= counter; i++)
{
remNum = num % i;
if (remNum == 0)
{
remDen = den % i;
if (remDen == 0)
{
num = num / i;
den = den / i;
i--;
}
}
}
return num.ToString() + "/" + den.ToString();
}
}
答案 18 :(得分:0)
这是我不久前为一个项目编写的算法。它需要一种不同的方法,这更类似于你手工做的事情。我不能保证它的效率,但它完成了工作。
def nth_prime(n)
prime_num = 0
i = 2
while true
if is_prime?(i)
prime_num += 1
if prime_num == n
return i
end
end
i += 1
end
end
答案 19 :(得分:0)
重复小数的简单解决方案/细分。
我认为1-9除以9的数字是重复的。 AKA 7/9 = .77777
我的解决方法是将整数乘以9,加上重复数,再再除以9。
Ex: 28.66666
28*9=252
252+6=258
258/9=28.66666
这种方法也很容易编程。截断十进制数字,乘以9,加上第一个十进制数,然后除以9。
唯一缺少的是,如果左边的数字可以被分割为3,则可能需要简化分数。
答案 20 :(得分:0)
以下是两个关于此问题的流行答案的Swift 4转换:
public func decimalToFraction(_ d: Double) -> (Int, Int) {
var df: Double = 1
var top: Int = 1
var bot: Int = 1
while df != d {
if df < d {
top += 1
} else {
bot += 1
top = Int(d * bot)
}
df = top / bot
}
return (top, bot)
}
public func realToFraction(_ value: Double, accuracy: Double = 0.00005) -> (Int, Int)? {
var value = value
guard accuracy >= 0 && accuracy <= 1 else {
Swift.print(accuracy, "Must be > 0 and < 1.")
return nil
}
let theSign = sign(value)
if theSign == -1 {
value = abs(value)
}
// Accuracy is the maximum relative error; convert to absolute maxError
let maxError = theSign == 0 ? accuracy : value * accuracy
let n = floor(value)
value -= n
if value < maxError {
return (Int(theSign * n), 1)
}
if 1 - maxError < value {
return (Int(theSign * (n + 1)), 1)
}
// The lower fraction is 0/1
var lowerN: Double = 0
var lowerD: Double = 1
// The upper fraction is 1/1
var upperN: Double = 1
var upperD: Double = 1
while true {
// The middle fraction is (lowerN + upperN) / (lowerD + upperD)
let middleN = lowerN + upperN
let middleD = lowerD + upperD
if middleD * (value + maxError) < middleN {
// real + error < middle : middle is our new upper
upperN = middleN
upperD = middleD
} else if middleN < (value - maxError) * middleD {
// middle < real - error : middle is our new lower
lowerN = middleN
lowerD = middleD
} else {
// Middle is our best fraction
return (Int(n * middleD + middleN * theSign), Int(middleD))
}
}
}
答案 21 :(得分:0)
我尝试在btilly's answer上进行扩展
更改为:
如果要以小数形式显示它,请更改btilly's answer的最后else部分。因此修改后的代码变为:
def float_to_fraction (x, error=0.000001):
n = int(math.floor(x))
x -= n
if x < error:
return (n, 1)
elif 1 - error < x:
return (n+1, 1)
# The lower fraction is 0/1
lower_n = 0
lower_d = 1
# The upper fraction is 1/1
upper_n = 1
upper_d = 1
while True:
# The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
middle_n = lower_n + upper_n
middle_d = lower_d + upper_d
# If x + error < middle
if middle_d * (x + error) < middle_n:
# middle is our new upper
upper_n = middle_n
upper_d = middle_d
# Else If middle < x - error
elif middle_n < (x - error) * middle_d:
# middle is our new lower
lower_n = middle_n
lower_d = middle_d
# Else middle is our best fraction
else:
#return (n * middle_d + middle_n, middle_d)
frac = Fraction(n * middle_d + middle_n, middle_d)
if (frac.numerator // frac.denominator) == 0:
return(f"{frac.numerator % frac.denominator}/{frac.denominator}")
elif ((frac.numerator % frac.denominator)/frac.denominator) == 0/1:
return(f"{frac.numerator // frac.denominator}")
else:
return(f"{frac.numerator // frac.denominator} "f"{frac.numerator % frac.denominator}/{frac.denominator}")```
答案 22 :(得分:0)
这是btilly答案的JavaScript版本。我只是想将浮点数显示为分数,所以我返回一个字符串;
function float_to_fraction(x, error = 0.00001) {
const n = Math.floor(x);
x -= n;
if (x < error) {
return `${n}`;
} else if (1 - error < x) {
return `${n + 1}`;
}
// The lower fraction is 0/1
let lower_n = 0;
let lower_d = 1;
// The upper fraction is 1/1
let upper_n = 1;
let upper_d = 1;
while (true) {
// The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
let middle_n = lower_n + upper_n;
let middle_d = lower_d + upper_d;
// If x + error < middle
if (middle_d * (x + error) < middle_n) {
// middle is our new upper
upper_n = middle_n;
upper_d = middle_d;
// Else If middle < x - error
} else if (middle_n < (x - error) * middle_d) {
// middle is our new lower
lower_n = middle_n;
lower_d = middle_d;
//Else middle is our best fraction
} else {
return `${n * middle_d + middle_n}/${middle_d}`;
}
}
}
答案 23 :(得分:-1)
第一个函数获取摩擦字符串格式“ 1/2” ,第二个函数查找上下部分的 gcd (最大公约数)。
public static string DoubleToFraction(double num)
{
if (Math.Round(num, 6) == Math.Round(num, 0))
return Math.Round(num, 0).ToString();
bool minus = (num < 0) ? true : false;
int up;
if (minus)
up = (int)((Math.Round(num, 6) - 0.000001) * 362880);
else
up = (int)((Math.Round(num, 6) + 0.000001) * 362880);
int down = 362880;
int div = gcd(up, down);
up /= div;
down /= div;
return up + "/" + down;
}
public static int gcd(int a, int b)
{
if (b == 0)
return Math.Abs(a);
return gcd(b, a % b);
}