Я попытался написать алгоритм, чтобы упростить десятичную дробь и понял, что это не слишком просто. Удивительно, но я посмотрел онлайн и все коды, которые я нашел там, где они слишком долго, или не будут работать в некоторых случаях. Что еще более раздражало, так это то, что они не работали на повторяющиеся десятины. Мне было интересно, есть ли здесь математик/программист, который понимает все вовлеченные процессы в упрощение десятичной дроби до фракции. Кто-нибудь?
Алгоритм упрощения десятичных дробей
Ответ 1
Ну, кажется, мне, наконец, пришлось сделать это сам. Мне просто нужно было создать программу, имитирующую естественный способ, которым я сам ее решал. Я просто отправил код в кодпроект, так как выписать весь код здесь не подходит. Вы можете скачать проект здесь Fraction_Conversion или посмотреть кодпроект здесь.
Вот как это работает:
- Узнайте, является ли данный десятичный знак отрицательным.
- Преобразование десятичного в абсолютное значение
- Получить целую часть заданного десятичного числа
- Получить десятичную часть
- Проверьте, повторяется ли десятичная дробь. Если десятичное число повторяется, мы возвращаем точное повторяющееся десятичное число
- Если десятичное значение не повторяется, начните уменьшение, изменив числитель на 10 ^ нет. десятичного числа, иначе мы вычитаем 1 из числителя
- Затем уменьшите долю
Предварительный просмотр кода:
private static string dec2frac(double dbl)
{
char neg = ' ';
double dblDecimal = dbl;
if (dblDecimal == (int) dblDecimal) return dblDecimal.ToString(); //return no if it 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 за то, что дал мне представление о том, как решить повторяющиеся десятичные знаки:)
Ответ 2
Алгоритм, который другие люди дали вам, получает ответ, вычисляя Продолжение фракции. Это дает дробную последовательность, которая гарантированно сходится очень, очень быстро. Однако гарантированно not дает вам наименьшую долю, находящуюся в пределах расстояния от реального числа. Чтобы найти, что вам нужно пройти дерево Stern-Brocot.
Чтобы сделать это, вы вычитаете пол, чтобы получить номер в диапазоне [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)
Ответ 3
(код улучшен в феврале 2017 года - прокрутите вниз до "оптимизации"...)
(таблица сравнения алгоритмов в конце этого ответа)
Я реализовал btilly answer в С# и...
- добавлена поддержка отрицательных чисел
- укажите параметр
accuracy
, чтобы указать max. относительная ошибка, а не макс. абсолютная ошибка;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
- просто простая структура. Конечно, используйте свой собственный предпочтительный тип... (мне нравится этот Риком Дэвином.)
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
Для определенных значений, таких как 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 замедляет ее не более чем на 2, но исходный Stern-Brocot может быть в сотни или тысячи раз медленнее, когда он попадает в несчастливые значения, упомянутые выше. Это все еще только пара микросекунд, хотя за звонок.
- Richards постоянно быстро.
- Eppstein примерно в 3 раза медленнее остальных.
Стерн-Брокот и Ричардс сравнивали:
- Оба возвращают хорошие фракции.
- Ричардс часто приводит к меньшей ошибке. Это также немного быстрее.
- Стерн-Брокот спускается по дереву S-B. Он находит долю самого низкого знаменателя, которая соответствует требуемой точности, а затем останавливается.
Если вам не нужна фракция с наименьшим знаменателем, Ричардс - хороший выбор.
Ответ 4
Я знаю, что вы сказали, что искали в Интернете, но если вы пропустили следующую статью, это может помочь. Он включает пример кода в Паскале.
Алгоритм преобразования десятичной доли в дроби *
В качестве альтернативы, как часть стандартной библиотеки, Ruby имеет код, который имеет дело с рациональными числами. Он может конвертировать из поплавков в рациональные и наоборот. Я считаю, что вы также можете просмотреть код. Документация найдена здесь. Я знаю, что вы не используете Ruby, но это может помочь взглянуть на алгоритмы.
Кроме того, вы можете вызывать Ruby-код из С# (или даже писать код Ruby внутри файла кода С#), если вы используете IronRuby, который выполняется поверх структуры .net.
* Обновлено по новой ссылке, поскольку, как кажется, исходный URL-адрес поврежден (http://homepage.smc.edu/kennedy_john/DEC2FRAC.pdf)
Ответ 5
Я нашел тот же документ, на который ссылался Мэтт, и я взял второй и реализовал его на Python. Возможно, увидеть ту же идею в коде сделает ее более ясной. Конечно, вы запросили ответ на С#, и я даю его вам на 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, соответственно).
Другое редактирование (я не думал, что это будет так интересно!): Если вы хотите узнать больше об теории этого алгоритма, Википедия имеет отличная страница по алгоритму Евклида
Ответ 6
Здесь приведен пример С# на примере python Will Brown. Я также изменил его для обработки отдельных целых чисел (например, "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()
);
}
Ответ 7
Я написал быстрый класс, который работает довольно быстро и дает результаты, которые я ожидал бы. Вы также можете выбрать свою точность. Это намного проще от любого кода, который я видел, и работает быстро, а также.
//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;
}
}
}
Ответ 8
Вы не можете представить повторяющееся десятичное число в .net, поэтому я проигнорирую эту часть вашего вопроса.
Вы можете представлять только конечное и относительно небольшое количество цифр.
Там очень простой алгоритм:
- принять десятичный
x
- подсчитать количество цифр после десятичной точки; назовите это
n
- создать фрагмент
(10^n * x) / 10^n
- удалите общие факторы из числителя и знаменателя.
так что если у вас 0,44, вы бы посчитали 2 места - это десятичная точка - n = 2, а затем напишите
-
(0.44 * 10^2) / 10^2
- =
44 / 100
- факторизация (удаление общего коэффициента 4) дает
11 / 25
Ответ 9
Этот алгоритм Дэвида Эппштейна, UC Irvine, основанный на теории непрерывных дробей и первоначально на C, был переведен на С# мной. Фракции, которые он генерирует, удовлетворяют пределу погрешности, но в основном не выглядят так же хорошо, как решения в моих других ответах. Например. 0.5
становится 999/1999
, в то время как 1/2
будет предпочтительнее при отображении пользователю (если вам это нужно, см. мой другой ответы).
Существует перегрузка для указания поля ошибки как двойного (относительно значения, а не абсолютной ошибки). Для типа Fraction
см. Мой другой ответ.
Кстати, если ваши фракции могут стать большими, измените соответствующий int
на long
. По сравнению с другими алгоритмами этот метод подвержен переполнению.
Например, значения и сравнение с другими алгоритмами см. мой другой ответ
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
Я придумал очень поздний ответ. Код взят из статьи из
Ричардс опубликован в 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 |
Как вы можете видеть, эти два метода дают более или менее одинаковые результаты, но richards 'является более эффективным и более простым в реализации.
Изменить
Чтобы скомпилировать мой код, вам нужно определить для 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); }
}
К сожалению
Я думаю, этот ответ относится к одному и тому же алгоритму. Я этого раньше не видел...
Ответ 11
Это версия алгоритма С# с помощью Ян Ричардс/Джон Кеннеди. Другие ответы здесь, используя этот же алгоритм:
- Matt (ссылки только на бумагу Кеннеди)
- Haldean Brown (Python)
- Джереми Херрман (С#)
- PinkFloyd (C)
Он не обрабатывает бесконечности и NaN.
Этот алгоритм быстрый.
Например, значения и сравнение с другими алгоритмами см. мой другой ответ
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);
}
Ответ 12
Повторяющееся десятичное число может быть представлено двумя конечными десятичными знаками: левая часть перед повторением и повторяющаяся часть. Например. 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
в качестве рациональных чисел, и вы закончите с другим.
(Это уточняет ответ Кирка Бродхерста, который прав, насколько это возможно, но не охватывает повторяющиеся десятичные знаки. Я не обещаю, что не допустил ошибок выше, хотя я уверен, что общий подход работает.)
Ответ 13
Недавно мне пришлось выполнить эту самую задачу работы с типом десятичных данных, который хранится в нашей базе данных SQL Server. На уровне презентации это значение редактировалось как дробное значение в TextBox. Сложность здесь заключалась в работе с типом десятичных данных, который содержит довольно большие значения по сравнению с int или long. Поэтому, чтобы уменьшить вероятность переполнения данных, я придерживался десятичного типа данных во время преобразования.
Прежде чем начать, я хочу прокомментировать предыдущий ответ Кирка. Он абсолютно прав, если нет никаких предположений. Однако, если разработчик ищет повторяющиеся шаблоны в пределах десятичного типа данных .3333333... может быть представлен как 1/3. Пример алгоритма можно найти на basic-mathematics.com. Опять же, это означает, что вы должны делать предположения на основе имеющейся информации, и использование этого метода позволяет только фиксировать очень небольшое подмножество повторяющихся десятичных знаков. Однако для небольших чисел должно быть хорошо.
Двигаясь вперед, позвольте мне дать вам снимок моего решения. Если вы хотите прочитать полный пример с дополнительным кодом, я создал гораздо более подробно сообщение в блоге.
Преобразование десятичного типа данных в число строк
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;
}
Преобразование обратно в десятичную строку также довольно простое. Здесь мы анализируем дробные компоненты, храним их в чем-то, с чем мы можем работать (здесь десятичные значения) и выполняем наше деление.
Ответ 14
Мои 2 цента. Здесь версия VB.NET отличного алгоритма btilly:
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
Ответ 15
Наиболее популярными решениями этой проблемы являются алгоритм Ричардса и алгоритм Stern-Brocot, реализованный btilly с оптимизацией скорости от btilly и Jay Zed. Алгоритм Ричардса является самым быстрым, но не гарантирует возврата лучшей фракции.
У меня есть решение этой проблемы, которая всегда дает лучшую фракцию и также быстрее, чем все вышеприведенные алгоритмы. Вот алгоритм в С# (объяснение и проверка скорости ниже).
Это короткий алгоритм без комментариев. Полная версия предоставляется в исходном коде в конце.
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);
}
Где Фракция - это простой класс для хранения дробной части, например:
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 или решения, основанные на повторении десятичных знаков, оказались медленнее и/или дают субоптимальные результаты.
Продолжение фракции
Решения, основанные на продолжающейся дробности, в основном основаны на двух алгоритмах, описанных в статье Яна Ричардса, опубликованной в 1981 году . Он назвал их "алгоритм медленной непрерывной фракции" и "алгоритм быстрой непрерывной фракции". Первый известен как алгоритм Штерна-Брокота, а последний известен как алгоритм Ричардса.
Мой алгоритм (краткое объяснение)
Чтобы полностью понять мой алгоритм, вам нужно прочитать статью Яна Ричардса или хотя бы понять, что такое пара Farey. Кроме того, прочитайте алгоритм с комментариями в конце этой статьи.
Алгоритм использует пару Farey, содержащую левую и правую доли. Повторяя медиатор, он приближается к целевому значению. Это похоже на медленный алгоритм, но есть два основных отличия:
- Несколько итераций выполняются сразу, пока медиатор остается на одной стороне целевого значения.
- Левая и правая фракции не могут приблизиться к целевому значению, чем заданная точность.
В качестве альтернативы проверяются правая и левая стороны целевого значения. Если алгоритм не может привести результат ближе к целевому значению, процесс завершается. Результирующим медиатором является оптимальное решение.
Тест скорости
Я сделал несколько тестов скорости на своем ноутбуке со следующими алгоритмами:
- Улучшен медленный алгоритм Kay Zed и btilly
- Джон Кеннеди реализует алгоритм Fast, преобразованный в С# Kay Zed
- Моя реализация алгоритма Fast (рядом с оригиналом Яна Ричардса)
- Джереми Херрманс реализация алгоритма Fast
- Мой алгоритм выше
Я опустил оригинальный медленный алгоритм btilly из-за его плохих худших результатов.
Набор тестов
Я выбираю набор целевых значений (очень произвольный) и вычисляет фракцию 100000 раз с 5 различными точками. Поскольку возможные (будущие) алгоритмы не могли обрабатывать неправильные дроби, проверялись только целевые значения от 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% медленнее, чем самый быстрый алгоритм ".
С другой стороны, даже самый медленный алгоритм выполняет вычисление в среднем менее чем за микросекунду. Поэтому при нормальных обстоятельствах скорость не является проблемой. На мой взгляд, лучший алгоритм в основном зависит от вкуса, поэтому выберите любой из проверенных алгоритмов по другим критериям.
- Дает ли алгоритм лучший результат?
- Доступен ли алгоритм на моем любимом языке?
- Каков размер кода алгоритма?
- Является ли читаемый алгоритм понятным?
Исходный код
В приведенном ниже исходном коде содержатся все используемые алгоритмы. Он включает в себя:
- Мой оригинальный алгоритм (с комментариями)
- Еще более быстрый вариант моего алгоритма (но менее читаемый)
- Оригинальный медленный алгоритм
- Все проверенные алгоритмы
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);
}
}
Ответ 16
Здесь реализован алгоритм, реализованный в VB, который преобразует "Десятичное число с плавающей запятой в целую партию" , которое я написал много лет назад.
В основном вы начинаете с числителя = 0 и знаменателя = 1, тогда, если коэффициент меньше десятичного ввода, добавьте 1 в числитель и если коэффициент больше десятичного ввода, добавьте 1 к знаменателю. Повторяйте, пока не получите нужную точность.
Ответ 17
Если бы я был вами, я бы справился с проблемой "no repeating demimals in.NET", попросив его преобразовать строки с отмеченной как-то повторяемостью.
например. 1/3 может быть представлено "0.R3" 1/60 может быть представлено "0.01R6"
Мне понадобится явный листинг из double или decimal, потому что такие значения могут быть преобразованы только в дроби, которая была близка. Неявный приведение из int в порядке.
Вы можете использовать структуру и сохранить свою долю (f) в двух длинах p и q таких, что f = p/q, q!= 0 и gcd (p, q) == 1.
Ответ 18
Здесь вы можете использовать метод преобразования десятичных чисел в дроби:
/// <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();
}
}
Ответ 19
Вот алгоритм, который я написал для проекта не так давно. Это требует другого подхода, который более сродни тому, что вы делаете вручную. Я не могу гарантировать его эффективность, но он выполняет свою работу.
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);
}
Ответ 20
Простое решение/разбиение повторяющегося десятичного числа.
Я взял логику, что числа 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.