私は小数を分数に単純化するアルゴリズムを書いてみましたが、それはあまりにも単純ではないことに気付きました。驚いたことに、私はオンラインで見つけたすべてのコードを探しましたが、長すぎたり、場合によっては機能しませんでした。さらに厄介なのは、繰り返しの小数に対して機能しなかったことです。しかし、小数を小数に単純化する際に関係するすべてのプロセスを理解する数学者/プログラマーがここにいるかどうか疑問に思っていました。誰でも?
他の人があなたに与えたアルゴリズムは、数の Continued Fraction を計算することで答えを得ます。これにより、非常に高速で収束することが保証されている分数列が得られます。しかしそれは じゃない 実数の距離イプシロン内にある最小の端数を与えることが保証されています。 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)
(コードは2017年2月に改善-「最適化」までスクロールダウン...)
(この回答の最後のアルゴリズム比較表)
btilly's answer をC#で実装し...
accuracy
パラメーターを提供します。最大ではなく相対誤差。絶対誤差; 0.01
は、値の1%以内の小数部を見つけます。Double.NaN
およびDouble.Infinity
はサポートされていません。それらを処理したい場合があります( ここの例 )。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
型は単純な構造体です。もちろん、好みのタイプを使用してください...(私は this one Rick Davinが好きです。)
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
- statementの内部では、次を置き換えます。
// 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は比較しました:
最低分母の分数を必要としない場合は、Richardsが適しています。
あなたがオンラインで検索したと言っていることは知っていますが、次の論文を見逃してしまった場合は、助けになるかもしれません。 Pascalのコード例が含まれています。
あるいは、その標準ライブラリの一部として、Rubyには有理数を扱うコードがあります。フロートから有理数へ、またはその逆に変換できます。コードも参照できると思います。ドキュメントが見つかりました here 。あなたはRubyを使用していないことを知っていますが、それはアルゴリズムを見るのに役立つかもしれません。
さらに、 IronRuby を使用する場合、C#からRubyコードを呼び出す(またはC#コードファイル内にRubyコードを書き込む)こともできます) 、これは.netフレームワークの上で実行されます。
*元のURLが壊れているように見えるため、新しいリンクに更新されました( http://homepage.smc.edu/kennedy_john/DEC2FRAC.pdf )
Mattが参照したのと同じ論文を見つけて、2つ目を取り上げてPythonで実装しました。コードで同じアイデアを見ると、より明確になるでしょう。確かに、あなたはC#で回答をリクエストし、Pythonで回答していますが、それはかなり簡単なプログラムであり、翻訳は簡単だと確信しています。パラメーターは、num
(有理数に変換する10進数)およびepsilon
(num
と計算された有理数との最大許容差)です。いくつかの簡単なテスト実行では、epsilon
が約1e-4のときに収束するのに通常2〜3回の反復しか必要ないことがわかります。
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、それぞれ)。
別の編集(これはそんなに面白いとは思わなかった!):このアルゴリズムの背後にある理論についてもっと知りたい場合、 ウィキペディアにはユークリッドアルゴリズムに関する優れたページがあります
Will BrownのC#バージョンpythonの例。個別の整数を処理するように変更しました(たとえば、 "17/8"ではなく "2 1/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()
);
}
.netで繰り返しの小数を表すことはできないため、質問のその部分は無視します。
表示できる桁数は、有限で比較的少数です。
非常に単純なアルゴリズムがあります:
x
n
と呼びます(10^n * x) / 10^n
0.44の場合、小数点以下2桁をカウントします-n = 2
(0.44 * 10^2) / 10^2
44 / 100
11 / 25
かなり高速で実行され、期待どおりの結果が得られるクイッククラスを作成しました。精度も選択できます。私が見たどのコードよりもずっと簡単で、同様に高速に実行されます。
//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;
}
}
}
これは 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);
}
UC IrvineのDavid Eppsteinによるこのアルゴリズムは、連続分数の理論に基づいており、もともとはCでしたが、私がC#に翻訳しました。生成される分数はエラーマージンを満たしますが、ほとんどの場合、他の回答のソリューションほど良く見えません。例えば。 0.5
は999/1999
になりますが、1/2
はユーザーに表示されるときに優先されます(必要な場合は、my otheranswers を参照してください) 。
エラーマージンをdouble(絶対エラーではなく、値に相対)として指定するオーバーロードがあります。 Fraction
タイプについては、他の回答を参照してください。
ちなみに、分数が大きくなる可能性がある場合は、関連するint
sを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);
}
私は非常に遅い答えを思いつきます。コードは 1981年に公開されたRichardsの記事 から取得され、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 |
ご覧のとおり、2つの方法はほぼ同じ結果をもたらしますが、リチャードの方法はより効率的で実装が簡単です。
コードをコンパイルするには、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); }
}
この答え は同じアルゴリズムを指していると思います。私は前にそれを見なかった...
まあ、私は最終的に自分でやらなければならなかったようです。自分で解決する自然な方法をシミュレートするプログラムを作成する必要がありました。ここでコード全体を書き出すのは適切ではないので、コードをcodeprojectに送信しました。ここからプロジェクトをダウンロードできます Fraction_Conversion 、または こちらのcodeprojectページ をご覧ください。
仕組みは次のとおりです。
コードプレビュー:
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);
}
繰り返し小数を解決する方法のアイデアを与えてくれてありがとう@ダリウス:)
私の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
繰り返し小数は、2つの有限小数で表すことができます。繰り返しの前の左向き部分と繰り返し部分です。例えば。 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
で始まり、別の数で終わることになります。
(これは、Kirk Broadhurstの答えを詳しく述べていますが、これは正しい範囲ですが、小数の繰り返しについてはカバーしていません。一般的なアプローチが機能すると確信していますが、上記の間違いを犯さないとは約束しません。)
この問題の最も一般的な解決策は、 Richardsのアルゴリズム および Stern-Brocotアルゴリズム であり、btillyと speed Optimizationization によってbtillyとJay Zedによって実装されています。リチャーズのアルゴリズムは最速ですが、最高の分数を返すことを保証するものではありません。
私はこの問題に対する解決策を持っていますが、これは常に最良の割合を与え、上記のすべてのアルゴリズムよりも高速です。 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 のような他のソリューション、または小数の繰り返しに基づくソリューションは、より遅く、および/または次善の結果をもたらすことが証明されました。
継続分数
継続分数に基づくソリューションは、主に2つのアルゴリズムに基づいており、どちらも1981年に公開されたIan Richardsの記事に記載されています here 。高速連分数アルゴリズム」。前者はStern-Brocotアルゴリズム、後者はRichardsアルゴリズムとして知られています。
私のアルゴリズム(簡単な説明)
私のアルゴリズムを完全に理解するには、Ian Richardsの記事を読むか、少なくともFareyペアとは何かを理解する必要があります。さらに、この記事の最後にコメント付きのアルゴリズムを読んでください。
アルゴリズムは、左端と右端を含むFareyペアを使用しています。中央値を繰り返し取得することにより、目標値に近づいています。これは遅いアルゴリズムと同じですが、2つの大きな違いがあります。
代わりに、ターゲット値の右側と左側がチェックされます。アルゴリズムが目標値により近い結果を生成できない場合、プロセスは終了します。結果の中央値が最適なソリューションです。
次のアルゴリズムを使用して、ラップトップで速度テストを行いました。
最悪の場合のパフォーマンスが悪いため、元の低速アルゴリズムを btilly で省略しました。
テストセット
一連の目標値(非常に任意)を選択し、5つの異なる精度で100,000回の端数を計算しました。可能性のある(将来の)アルゴリズムでは不適切な分数を処理できないため、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%未満です。改善された低速アルゴリズムは、最速のアルゴリズムよりも30%〜35%低速です。
一方、最も遅いアルゴリズムでも、平均で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);
}
}
最近、SQL Serverデータベースに格納されている10進データ型を操作するというまさにこのタスクを実行する必要がありました。プレゼンテーション層で、この値はTextBoxの小数値として編集されました。ここでの複雑さは、intまたはlongと比較してかなり大きな値を保持するDecimal Data Typeで機能していました。そのため、データオーバーランの機会を減らすために、変換中は10進データ型を使用しました。
始める前に、カークの以前の答えについてコメントしたいと思います。仮定がなされない限り、彼は絶対に正しい。ただし、開発者が10進データ型の範囲内で繰り返しパターンのみを探す場合、.3333333 ...は1/3として表すことができます。アルゴリズムの例は basic-mathematics.com にあります。繰り返しますが、これは、利用可能な情報に基づいて仮定を行う必要があることを意味し、この方法を使用すると、繰り返し小数の非常に小さなサブセットのみがキャプチャされます。ただし、小さい数字でも大丈夫です。
今後、私のソリューションのスナップショットをお見せします。追加のコードを含む完全な例を読みたい場合は、より詳細な ブログ投稿 を作成しました。
10進データ型を文字列分数に変換
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(decimal value)が、分数を構成するすべてのコンポーネントへのアクセスを提供する最初のメソッドの単純化されたエントリポイントにすぎない場合、これは非常に単純です。 10進数の.325がある場合は、10の小数点以下の桁数で割ります。最後に端数を減らします。そして、この例では.325 = 325/10 ^ 3 = 325/1000 = 13/40です。
次に、別の方向に進みます。
文字列の分数を10進データ型に変換する
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;
}
10進数に戻すことも非常に簡単です。ここで、小数コンポーネントを解析し、操作可能なもの(ここでは10進数値)に格納し、除算を実行します。
この問題に対する一般的な回答の2つの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))
}
}
}
これは、さほど昔ではないプロジェクトのために書いたアルゴリズムです。それは別のアプローチを取ります。それはあなたが手でやるようなものです。私はその効率を保証することはできませんが、仕事を終わらせます。
public static string toFraction(string exp) {
double x = Convert.ToDouble(exp);
int sign = (Math.Abs(x) == x) ? 1 : -1;
x = Math.Abs(x);
int n = (int)x; // integer part
x -= n; // fractional part
int mult, nm, dm;
int decCount = 0;
Match m = Regex.Match(Convert.ToString(x), @"([0-9]+?)\1+.?$");
// repeating fraction
if (m.Success) {
m = Regex.Match(m.Value, @"([0-9]+?)(?=\1)");
mult = (int)Math.Pow(10, m.Length);
// We have our basic fraction
nm = (int)Math.Round(((x * mult) - x));
dm = mult - 1;
}
// get the number of decimal places
else {
double t = x;
while (t != 0) {
decCount++;
t *= 10;
t -= (int)t;
}
mult = (int)Math.Pow(10, decCount);
// We have our basic fraction
nm = (int)((x * mult));
dm = mult;
}
// can't be simplified
if (nm < 0 || dm < 0) return exp;
//Simplify
Stack factors = new Stack();
for (int i = 2; i < nm + 1; i++) {
if (nm % i == 0) factors.Push(i); // i is a factor of the numerator
}
// check against the denominator, stopping at the highest match
while(factors.Count != 0) {
// we have a common factor
if (dm % (int)factors.Peek() == 0) {
int f = (int)factors.Pop();
nm /= f;
dm /= f;
break;
}
else factors.Pop();
}
nm += (n * dm);
nm *= sign;
if (dm == 1) return Convert.ToString(nm);
else return Convert.ToString(nm) + "/" + Convert.ToString(dm);
}
もし私があなたなら、「。NETで小数が繰り返されない」問題を、何らかの形でマークされた繰り返しで文字列を変換することで処理します。
例えば。 1/3は「0.R3」を表すことができます1/60は「0.01R6」を表すことができます
そのような値は近い端数にしか変換できないため、doubleまたはdecimalからの明示的なキャストが必要になります。 intからの暗黙的なキャストは問題ありません。
構造体を使用して、f = p/q、q!= 0、gcd(p、q)== 1のように、2つのlong pとqに小数部(f)を格納できます。
以下に、VB= 浮動小数点10進数から整数分数)に変換するアルゴリズム で実装したアルゴリズムを示します。
基本的には、分子= 0および分母= 1で始まり、商が小数入力よりも小さい場合は分子に1を加算し、商が小数入力よりも大きい場合は分母に1を加算します。目的の精度内に収まるまで繰り返します。
単純な解決策/繰り返し小数の内訳
私は、1〜9を9で割った数字が繰り返されているという論理を取りました。別名7/9 = .77777
私の解決策は、整数に9を掛け、繰り返し数を加算し、再び9で除算することです。
Ex: 28.66666
28*9=252
252+6=258
258/9=28.66666
この方法もプログラミングが簡単です。 10進数を切り捨て、9で乗算し、最初の10進数を加算してから、9で除算します。
唯一欠けているのは、左の数値が3で割り切れる場合、分数を単純化する必要がある場合があることです。
ここでは、DecimalをFractionsに変換するメソッドを使用できます。
/// <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();
}
}