Как конвертировать поплавки в читаемые человеком фракции?
Скажем, у нас 0,33, нам нужно вывести "1/3".
Если у нас есть "0,4", нам нужно вывести "2/5".
Идея состоит в том, чтобы сделать его понятным для человека, чтобы пользователь понял "х частей из у" как лучший способ понять данные.
Я знаю, что проценты - хорошая замена, но мне было интересно, есть ли простой способ сделать это?
Ответы
Ответ 1
Я нашел Дэвида Эппштейна найти рациональную аппроксимацию заданного действительного числа Код C должен быть именно тем, о чем вы просите. Он основан на теории непрерывных дробей и очень быстр и довольно компактен.
Я использовал версии этой настройки для конкретных значений числителя и знаменателя.
/*
** find rational approximation to given real number
** David Eppstein / UC Irvine / 8 Aug 1993
**
** With corrections from Arno Formella, May 2008
**
** usage: a.out r d
** r is real number to approx
** d is the maximum denominator allowed
**
** based on the theory of continued fractions
** if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...)))
** then best approximation is found by truncating this series
** (with some adjustments in the last term).
**
** Note the fraction can be recovered as the first column of the matrix
** ( a1 1 ) ( a2 1 ) ( a3 1 ) ...
** ( 1 0 ) ( 1 0 ) ( 1 0 )
** Instead of keeping the sequence of continued fraction terms,
** we just keep the last partial product of these matrices.
*/
#include <stdio.h>
main(ac, av)
int ac;
char ** av;
{
double atof();
int atoi();
void exit();
long m[2][2];
double x, startx;
long maxden;
long ai;
/* read command line arguments */
if (ac != 3) {
fprintf(stderr, "usage: %s r d\n",av[0]); // AF: argument missing
exit(1);
}
startx = x = atof(av[1]);
maxden = atoi(av[2]);
/* initialize matrix */
m[0][0] = m[1][1] = 1;
m[0][1] = m[1][0] = 0;
/* loop finding terms until denom gets too big */
while (m[1][0] * ( ai = (long)x ) + m[1][1] <= maxden) {
long t;
t = m[0][0] * ai + m[0][1];
m[0][1] = m[0][0];
m[0][0] = t;
t = m[1][0] * ai + m[1][1];
m[1][1] = m[1][0];
m[1][0] = t;
if(x==(double)ai) break; // AF: division by zero
x = 1/(x - (double) ai);
if(x>(double)0x7FFFFFFF) break; // AF: representation failure
}
/* now remaining x is between 0 and 1/ai */
/* approx as either 0 or 1/m where m is max that will fit in maxden */
/* first try zero */
printf("%ld/%ld, error = %e\n", m[0][0], m[1][0],
startx - ((double) m[0][0] / (double) m[1][0]));
/* now try other possibility */
ai = (maxden - m[1][1]) / m[1][0];
m[0][0] = m[0][0] * ai + m[0][1];
m[1][0] = m[1][0] * ai + m[1][1];
printf("%ld/%ld, error = %e\n", m[0][0], m[1][0],
startx - ((double) m[0][0] / (double) m[1][0]));
}
Ответ 2
Из Python 2.6 существует модуль fractions
.
(Цитата из документов.)
>>> from fractions import Fraction
>>> Fraction('3.1415926535897932').limit_denominator(1000)
Fraction(355, 113)
>>> from math import pi, cos
>>> Fraction.from_float(cos(pi/3))
Fraction(4503599627370497, 9007199254740992)
>>> Fraction.from_float(cos(pi/3)).limit_denominator()
Fraction(1, 2)
Ответ 3
Если выход должен дать читателю человека быстрое впечатление о порядке результата, нет смысла возвращать что-то вроде "113/211", поэтому выход должен ограничиваться использованием одноразрядных чисел (и возможно, 1/10 и 9/10). Если это так, вы можете заметить, что существует только 27 различных фракций.
Поскольку базовая математика для генерации вывода никогда не изменится, решение может состоять в том, чтобы просто жестко закодировать двоичное дерево поиска, чтобы функция выполняла не более логические (27) ~ = 4 3/4 сравнения. Вот проверенная версия C кода
char *userTextForDouble(double d, char *rval)
{
if (d == 0.0)
return "0";
// TODO: negative numbers:if (d < 0.0)...
if (d >= 1.0)
sprintf(rval, "%.0f ", floor(d));
d = d-floor(d); // now only the fractional part is left
if (d == 0.0)
return rval;
if( d < 0.47 )
{
if( d < 0.25 )
{
if( d < 0.16 )
{
if( d < 0.12 ) // Note: fixed from .13
{
if( d < 0.11 )
strcat(rval, "1/10"); // .1
else
strcat(rval, "1/9"); // .1111....
}
else // d >= .12
{
if( d < 0.14 )
strcat(rval, "1/8"); // .125
else
strcat(rval, "1/7"); // .1428...
}
}
else // d >= .16
{
if( d < 0.19 )
{
strcat(rval, "1/6"); // .1666...
}
else // d > .19
{
if( d < 0.22 )
strcat(rval, "1/5"); // .2
else
strcat(rval, "2/9"); // .2222...
}
}
}
else // d >= .25
{
if( d < 0.37 ) // Note: fixed from .38
{
if( d < 0.28 ) // Note: fixed from .29
{
strcat(rval, "1/4"); // .25
}
else // d >=.28
{
if( d < 0.31 )
strcat(rval, "2/7"); // .2857...
else
strcat(rval, "1/3"); // .3333...
}
}
else // d >= .37
{
if( d < 0.42 ) // Note: fixed from .43
{
if( d < 0.40 )
strcat(rval, "3/8"); // .375
else
strcat(rval, "2/5"); // .4
}
else // d >= .42
{
if( d < 0.44 )
strcat(rval, "3/7"); // .4285...
else
strcat(rval, "4/9"); // .4444...
}
}
}
}
else
{
if( d < 0.71 )
{
if( d < 0.60 )
{
if( d < 0.55 ) // Note: fixed from .56
{
strcat(rval, "1/2"); // .5
}
else // d >= .55
{
if( d < 0.57 )
strcat(rval, "5/9"); // .5555...
else
strcat(rval, "4/7"); // .5714
}
}
else // d >= .6
{
if( d < 0.62 ) // Note: Fixed from .63
{
strcat(rval, "3/5"); // .6
}
else // d >= .62
{
if( d < 0.66 )
strcat(rval, "5/8"); // .625
else
strcat(rval, "2/3"); // .6666...
}
}
}
else
{
if( d < 0.80 )
{
if( d < 0.74 )
{
strcat(rval, "5/7"); // .7142...
}
else // d >= .74
{
if(d < 0.77 ) // Note: fixed from .78
strcat(rval, "3/4"); // .75
else
strcat(rval, "7/9"); // .7777...
}
}
else // d >= .8
{
if( d < 0.85 ) // Note: fixed from .86
{
if( d < 0.83 )
strcat(rval, "4/5"); // .8
else
strcat(rval, "5/6"); // .8333...
}
else // d >= .85
{
if( d < 0.87 ) // Note: fixed from .88
{
strcat(rval, "6/7"); // .8571
}
else // d >= .87
{
if( d < 0.88 ) // Note: fixed from .89
{
strcat(rval, "7/8"); // .875
}
else // d >= .88
{
if( d < 0.90 )
strcat(rval, "8/9"); // .8888...
else
strcat(rval, "9/10"); // .9
}
}
}
}
}
}
return rval;
}
Ответ 4
Здесь ссылка, объясняющая математику за преобразование десятичной дроби в дроби:
http://www.webmath.com/dec2fract.html
И вот пример функции, как на самом деле сделать это с помощью VB (от www.freevbcode.com/ShowCode.asp?ID=582):
Public Function Dec2Frac(ByVal f As Double) As String
Dim df As Double
Dim lUpperPart As Long
Dim lLowerPart As Long
lUpperPart = 1
lLowerPart = 1
df = lUpperPart / lLowerPart
While (df <> f)
If (df < f) Then
lUpperPart = lUpperPart + 1
Else
lLowerPart = lLowerPart + 1
lUpperPart = f * lLowerPart
End If
df = lUpperPart / lLowerPart
Wend
Dec2Frac = CStr(lUpperPart) & "/" & CStr(lLowerPart)
End Function
(Из поиска в Google: преобразование десятичного числа в дробь, преобразование десятичного кода в дробный код)
Ответ 5
Возможно, вам захочется прочитать Что должен знать каждый компьютерный ученый о арифметике с плавающей точкой.
Вам нужно будет указать некоторую точность путем умножения на большое число:
3.141592 * 1000000 = 3141592
то вы можете сделать дробь:
3 + (141592 / 1000000)
и уменьшить через GCD...
3 + (17699 / 125000)
но нет способа получить выделенную фракцию. Возможно, вы захотите всегда использовать фракции во всем своем коде - просто помните, чтобы уменьшить фракции, когда вы можете избежать переполнения!
Ответ 6
Реализация С#
/// <summary>
/// Represents a rational number
/// </summary>
public struct Fraction
{
public int Numerator;
public int Denominator;
/// <summary>
/// Constructor
/// </summary>
public Fraction(int numerator, int denominator)
{
this.Numerator = numerator;
this.Denominator = denominator;
}
/// <summary>
/// Approximates a fraction from the provided double
/// </summary>
public static Fraction Parse(double d)
{
return ApproximateFraction(d);
}
/// <summary>
/// Returns this fraction expressed as a double, rounded to the specified number of decimal places.
/// Returns double.NaN if denominator is zero
/// </summary>
public double ToDouble(int decimalPlaces)
{
if (this.Denominator == 0)
return double.NaN;
return System.Math.Round(
Numerator / (double)Denominator,
decimalPlaces
);
}
/// <summary>
/// Approximates the provided value to a fraction.
/// http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions
/// </summary>
private static Fraction ApproximateFraction(double value)
{
const double EPSILON = .000001d;
int n = 1; // numerator
int d = 1; // denominator
double fraction = n / d;
while (System.Math.Abs(fraction - value) > EPSILON)
{
if (fraction < value)
{
n++;
}
else
{
d++;
n = (int)System.Math.Round(value * d);
}
fraction = n / (double)d;
}
return new Fraction(n, d);
}
}
Ответ 7
Вот версии Perl и Javascript кода VB, предложенные devinmoore:
Perl:
sub dec2frac {
my $d = shift;
my $df = 1;
my $top = 1;
my $bot = 1;
while ($df != $d) {
if ($df < $d) {
$top += 1;
}
else {
$bot += 1;
$top = int($d * $bot);
}
$df = $top / $bot;
}
return "$top/$bot";
}
И почти идентичный javascript:
function dec2frac(d) {
var df = 1;
var top = 1;
var bot = 1;
while (df != d) {
if (df < d) {
top += 1;
}
else {
bot += 1;
top = parseInt(d * bot);
}
df = top / bot;
}
return top + '/' + bot;
}
Ответ 8
Дерево Штерн-Броко вызывает довольно естественный способ аппроксимации действительных чисел фракциями с простыми знаменателями.
Ответ 9
Часть проблемы состоит в том, что так много фракций на самом деле не просто истолковываются как фракции. Например. 0,33 не 1/3, это 33/100. Но если вы помните об обучении в начальной школе, то есть процесс преобразования десятичных значений в дроби, но вряд ли он даст вам то, что вы хотите, поскольку большая часть времени десятичных чисел не хранится в 0.33, а 0.329999999999998 или некоторые такие.
Сделайте себе одолжение и не беспокойтесь об этом, но если вам нужно, вы можете сделать следующее:
Умножьте исходное значение на 10, пока не удалите дробную часть. Сохраните это число и используйте его как делитель. Затем выполните ряд упрощений, ища общие знаменатели.
Таким образом, 0,4 будет 4/10. Затем вы будете искать общие делители, начиная с низких значений, вероятно простых чисел. Начиная с 2, вы увидите, что 2 равномерно делит как числитель, так и знаменатель, проверяя, совпадает ли пол деления с самим делением.
floor(5/2) = 2
5/2 = 2.5
Таким образом, 5 не делит 2 равномерно. Итак, вы проверяете следующий номер, скажем 3. Вы делаете это, пока не нажмете квадратный корень меньшего числа или больше.
После этого вам нужно
Ответ 10
Это не "алгоритм", а просто решение Python:
http://docs.python.org/library/fractions.html
>>> from fractions import Fraction
>>> Fraction('3.1415926535897932').limit_denominator(1000)
Fraction(355, 113)
Ответ 11
"Скажем, что у нас 0,33, нам нужно вывести" 1/3 ".
Какую точность вы ожидаете от "решения"? 0,33 не равно 1/3. Как вы узнаете "хороший" (легко читаемый) ответ?
Независимо от того, возможный алгоритм может быть:
Если вы ожидаете найти ближайшую дробь в форме X/Y, где Y меньше 10, то вы можете зацикливать все 9 возможных Ys, для каждого Y вычислить X, а затем выбрать наиболее точный.
Ответ 12
Встроенное решение в R:
library(MASS)
fractions(0.666666666)
## [1] 2/3
Это использует метод с непрерывной дроби и имеет необязательные аргументы cycles
и max.denominator
для настройки точности.
Ответ 13
Вам нужно будет выяснить, какой уровень ошибки вы готовы принять. Не все десятичные дроби сводятся к простой доле. Вероятно, я бы выбрал легко делимое число, например 60, и выяснил, сколько 60-ых ближе всего к значению, а затем упростите фракцию.
Ответ 14
Вы можете сделать это на любом языке программирования, используя следующие шаги:
- Умножьте и разделите на 10 ^ x, где x - мощность 10, необходимая для того, чтобы убедиться, что число не имеет десятичных знаков.
Пример: Умножьте 0,33 на 10 ^ 2 = 100, чтобы сделать его 33 и разделите его на то же, чтобы получить 33/100
- Уменьшите числитель и знаменатель результирующей фракции с помощью факторизации, пока вы больше не сможете получать целые числа из результата.
- Результирующая уменьшенная доля должна быть вашим ответом.
Пример:
0.2
= 0,2 × 10 ^ 1/10 ^ 1
= 2/10
= 1/5
Итак, это можно прочитать как "1 часть из 5"
Ответ 15
Одним из решений является простое сохранение всех чисел в качестве рациональных чисел. Существуют библиотеки для арифметики рациональных чисел (например, GMP). Если вы используете язык OO, вы можете просто использовать библиотеку классов рационального номера, чтобы заменить свой класс номера.
Финансовые программы, в частности, будут использовать такое решение, чтобы иметь возможность делать точные вычисления и сохранять точность, которая может быть потеряна с помощью простого плавающего элемента.
Конечно, это будет намного медленнее, поэтому это может быть непрактично для вас. В зависимости от того, сколько расчетов вам нужно сделать, и насколько важна точность для вас.
a = rational(1);
b = rational(3);
c = a / b;
print (c.asFraction) ---> "1/3"
print (c.asFloat) ----> "0.333333"
Ответ 16
Я думаю, что лучший способ сделать это - сначала преобразовать ваше значение float в представление ascii. В С++ вы можете использовать ostringstream или на C, вы можете использовать sprintf. Вот как это выглядело бы в С++:
ostringstream oss;
float num;
cin >> num;
oss << num;
string numStr = oss.str();
int i = numStr.length(), pow_ten = 0;
while (i > 0) {
if (numStr[i] == '.')
break;
pow_ten++;
i--;
}
for (int j = 1; j < pow_ten; j++) {
num *= 10.0;
}
cout << static_cast<int>(num) << "/" << pow(10, pow_ten - 1) << endl;
Аналогичный подход можно было бы применить и в прямой C.
Затем вам нужно будет проверить, что фракция находится в самом низком выражении. Этот алгоритм даст точный ответ, т.е. 0,33 выдаст "33/100", а не "1/3". Однако 0,4 даст "4/10", который при сокращении до наименьших терминов будет "2/5". Это может быть не так эффективно, как решение EppStein, но я считаю, что это более прямолинейно.
Ответ 17
Ruby уже имеет встроенное решение:
0.33.rationalize.to_s # => "33/100"
0.4.rationalize.to_s # => "2/5"
В Rails можно также преобразовать числовые атрибуты ActiveRecord:
product.size = 0.33
product.size.to_r.to_s # => "33/100"
Ответ 18
Ответ на С++, предполагая, что у вас есть класс BigInt, который может хранить целые числа неограниченного размера.
Вместо этого вы можете использовать "unsigned long long", но он будет работать только для определенных значений.
void GetRational(double val)
{
if (val == val+1) // Inf
throw "Infinite Value";
if (val != val) // NaN
throw "Undefined Value";
bool sign = false;
BigInt enumerator = 0;
BigInt denominator = 1;
if (val < 0)
{
val = -val;
sign = true;
}
while (val > 0)
{
unsigned int intVal = (unsigned int)val;
val -= intVal;
enumerator += intVal;
val *= 2;
enumerator *= 2;
denominator *= 2;
}
BigInt gcd = GCD(enumerator,denominator);
enumerator /= gcd;
denominator /= gcd;
Print(sign? "-":"+");
Print(enumerator);
Print("/");
Print(denominator);
// Or simply return {sign,enumerator,denominator} as you wish
}
BTW, GetRational (0.0) вернет "+0/1", поэтому вы можете обрабатывать этот случай отдельно.
P.S.: Я использовал этот код в своем классе "RationalNum" уже несколько лет, и он был тщательно протестирован.
Ответ 19
Этот алгоритм Ян Ричардс/Джон Кеннеди не только возвращает приятные фракции, но и отлично работает с точки зрения скорости. Это код С#, взятый мной из этого.
Он может обрабатывать все значения double
, за исключением специальных значений, таких как NaN и +/- бесконечность, которые вам нужно будет добавить при необходимости.
Возвращает a new Fraction(numerator, denominator)
. Замените ваш собственный тип.
Дополнительные примеры и сравнение с другими алгоритмами перейдите сюда
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);
}
Примеры значений, возвращаемых этим алгоритмом:
Accuracy: 1.0E-3 | Richards
Input | Result Error
======================| =============================
3 | 3/1 0
0.999999 | 1/1 1.0E-6
1.000001 | 1/1 -1.0E-6
0.50 (1/2) | 1/2 0
0.33... (1/3) | 1/3 0
0.67... (2/3) | 2/3 0
0.25 (1/4) | 1/4 0
0.11... (1/9) | 1/9 0
0.09... (1/11) | 1/11 0
0.62... (307/499) | 8/13 2.5E-4
0.14... (33/229) | 16/111 2.7E-4
0.05... (33/683) | 10/207 -1.5E-4
0.18... (100/541) | 17/92 -3.3E-4
0.06... (33/541) | 5/82 -3.7E-4
0.1 | 1/10 0
0.2 | 1/5 0
0.3 | 3/10 0
0.4 | 2/5 0
0.5 | 1/2 0
0.6 | 3/5 0
0.7 | 7/10 0
0.8 | 4/5 0
0.9 | 9/10 0
0.01 | 1/100 0
0.001 | 1/1000 0
0.0001 | 1/10000 0
0.33333333333 | 1/3 1.0E-11
0.333 | 333/1000 0
0.7777 | 7/9 1.0E-4
0.11 | 10/91 -1.0E-3
0.1111 | 1/9 1.0E-4
3.14 | 22/7 9.1E-4
3.14... (pi) | 22/7 4.0E-4
2.72... (e) | 87/32 1.7E-4
0.7454545454545 | 38/51 -4.8E-4
0.01024801004 | 2/195 8.2E-4
0.99011 | 100/101 -1.1E-5
0.26... (5/19) | 5/19 0
0.61... (37/61) | 17/28 9.7E-4
|
Accuracy: 1.0E-4 | Richards
Input | Result Error
======================| =============================
0.62... (307/499) | 299/486 -6.7E-6
0.05... (33/683) | 23/476 6.4E-5
0.06... (33/541) | 33/541 0
1E-05 | 1/99999 1.0E-5
0.7777 | 1109/1426 -1.8E-7
3.14... (pi) | 333/106 -2.6E-5
2.72... (e) | 193/71 1.0E-5
0.61... (37/61) | 37/61 0
Ответ 20
У вас будут две основные проблемы, которые сделают это трудным:
1) Плавающая точка не является точным представлением, что означает, что если у вас есть доля "x/y", которая приводит к значению "z", ваш алгоритм дроби может возвращать результат, отличный от "x/y".
2) Существует бесконечность многих иррациональных чисел, чем рациональных. Рациональное число - это число, которое может быть представлено как дробь. Иррациональные существа, которые не могут.
Однако, дешевым способом, поскольку с плавающей точкой имеет предельную точность, вы всегда можете представлять ее как некоторую форму фракции. (Я думаю...)
Ответ 21
Завершил вышеуказанный код и преобразовал его в as3
public static function toFrac(f:Number) : String
{
if (f>1)
{
var parte1:int;
var parte2:Number;
var resultado:String;
var loc:int = String(f).indexOf(".");
parte2 = Number(String(f).slice(loc, String(f).length));
parte1 = int(String(f).slice(0,loc));
resultado = toFrac(parte2);
parte1 *= int(resultado.slice(resultado.indexOf("/") + 1, resultado.length)) + int(resultado.slice(0, resultado.indexOf("/")));
resultado = String(parte1) + resultado.slice(resultado.indexOf("/"), resultado.length)
return resultado;
}
if( f < 0.47 )
if( f < 0.25 )
if( f < 0.16 )
if( f < 0.13 )
if( f < 0.11 )
return "1/10";
else
return "1/9";
else
if( f < 0.14 )
return "1/8";
else
return "1/7";
else
if( f < 0.19 )
return "1/6";
else
if( f < 0.22 )
return "1/5";
else
return "2/9";
else
if( f < 0.38 )
if( f < 0.29 )
return "1/4";
else
if( f < 0.31 )
return "2/7";
else
return "1/3";
else
if( f < 0.43 )
if( f < 0.40 )
return "3/8";
else
return "2/5";
else
if( f < 0.44 )
return "3/7";
else
return "4/9";
else
if( f < 0.71 )
if( f < 0.60 )
if( f < 0.56 )
return "1/2";
else
if( f < 0.57 )
return "5/9";
else
return "4/7";
else
if( f < 0.63 )
return "3/5";
else
if( f < 0.66 )
return "5/8";
else
return "2/3";
else
if( f < 0.80 )
if( f < 0.74 )
return "5/7";
else
if(f < 0.78 )
return "3/4";
else
return "7/9";
else
if( f < 0.86 )
if( f < 0.83 )
return "4/5";
else
return "5/6";
else
if( f < 0.88 )
return "6/7";
else
if( f < 0.89 )
return "7/8";
else
if( f < 0.90 )
return "8/9";
else
return "9/10";
}
Ответ 22
Скажем, у нас 0,33, нам нужно вывести "1/3". Если у нас "0,4", мы необходимо вывести "2/5".
Это неправильно в общем случае из-за 1/3 = 0,33333333 = 0. (3)
Более того, из предложенных выше решений невозможно определить десятичную величину, которая может быть преобразована в фракцию с определенной точностью, потому что выход всегда является фракцией.
НО, я предлагаю свою всеобъемлющую функцию со многими опциями, основанными на идее Бесконечные геометрические серии, в частности по формуле:
![enter image description here]()
Сначала эта функция пытается найти период фракции в строчном представлении. После этого описанная выше формула применяется.
Код рациональных чисел заимствован из Stephen M. McKamey реализация рациональных чисел на С#. Я надеюсь, что не очень сложно переносить мой код на другие языки.
/// <summary>
/// Convert decimal to fraction
/// </summary>
/// <param name="value">decimal value to convert</param>
/// <param name="result">result fraction if conversation is succsess</param>
/// <param name="decimalPlaces">precision of considereation frac part of value</param>
/// <param name="trimZeroes">trim zeroes on the right part of the value or not</param>
/// <param name="minPeriodRepeat">minimum period repeating</param>
/// <param name="digitsForReal">precision for determination value to real if period has not been founded</param>
/// <returns></returns>
public static bool FromDecimal(decimal value, out Rational<T> result,
int decimalPlaces = 28, bool trimZeroes = false, decimal minPeriodRepeat = 2, int digitsForReal = 9)
{
var valueStr = value.ToString("0.0000000000000000000000000000", CultureInfo.InvariantCulture);
var strs = valueStr.Split('.');
long intPart = long.Parse(strs[0]);
string fracPartTrimEnd = strs[1].TrimEnd(new char[] { '0' });
string fracPart;
if (trimZeroes)
{
fracPart = fracPartTrimEnd;
decimalPlaces = Math.Min(decimalPlaces, fracPart.Length);
}
else
fracPart = strs[1];
result = new Rational<T>();
try
{
string periodPart;
bool periodFound = false;
int i;
for (i = 0; i < fracPart.Length; i++)
{
if (fracPart[i] == '0' && i != 0)
continue;
for (int j = i + 1; j < fracPart.Length; j++)
{
periodPart = fracPart.Substring(i, j - i);
periodFound = true;
decimal periodRepeat = 1;
decimal periodStep = 1.0m / periodPart.Length;
var upperBound = Math.Min(fracPart.Length, decimalPlaces);
int k;
for (k = i + periodPart.Length; k < upperBound; k += 1)
{
if (periodPart[(k - i) % periodPart.Length] != fracPart[k])
{
periodFound = false;
break;
}
periodRepeat += periodStep;
}
if (!periodFound && upperBound - k <= periodPart.Length && periodPart[(upperBound - i) % periodPart.Length] > '5')
{
var ind = (k - i) % periodPart.Length;
var regroupedPeriod = (periodPart.Substring(ind) + periodPart.Remove(ind)).Substring(0, upperBound - k);
ulong periodTailPlusOne = ulong.Parse(regroupedPeriod) + 1;
ulong fracTail = ulong.Parse(fracPart.Substring(k, regroupedPeriod.Length));
if (periodTailPlusOne == fracTail)
periodFound = true;
}
if (periodFound && periodRepeat >= minPeriodRepeat)
{
result = FromDecimal(strs[0], fracPart.Substring(0, i), periodPart);
break;
}
else
periodFound = false;
}
if (periodFound)
break;
}
if (!periodFound)
{
if (fracPartTrimEnd.Length >= digitsForReal)
return false;
else
{
result = new Rational<T>(long.Parse(strs[0]), 1, false);
if (fracPartTrimEnd.Length != 0)
result = new Rational<T>(ulong.Parse(fracPartTrimEnd), TenInPower(fracPartTrimEnd.Length));
return true;
}
}
return true;
}
catch
{
return false;
}
}
public static Rational<T> FromDecimal(string intPart, string fracPart, string periodPart)
{
Rational<T> firstFracPart;
if (fracPart != null && fracPart.Length != 0)
{
ulong denominator = TenInPower(fracPart.Length);
firstFracPart = new Rational<T>(ulong.Parse(fracPart), denominator);
}
else
firstFracPart = new Rational<T>(0, 1, false);
Rational<T> secondFracPart;
if (periodPart != null && periodPart.Length != 0)
secondFracPart =
new Rational<T>(ulong.Parse(periodPart), TenInPower(fracPart.Length)) *
new Rational<T>(1, Nines((ulong)periodPart.Length), false);
else
secondFracPart = new Rational<T>(0, 1, false);
var result = firstFracPart + secondFracPart;
if (intPart != null && intPart.Length != 0)
{
long intPartLong = long.Parse(intPart);
result = new Rational<T>(intPartLong, 1, false) + (intPartLong == 0 ? 1 : Math.Sign(intPartLong)) * result;
}
return result;
}
private static ulong TenInPower(int power)
{
ulong result = 1;
for (int l = 0; l < power; l++)
result *= 10;
return result;
}
private static decimal TenInNegPower(int power)
{
decimal result = 1;
for (int l = 0; l > power; l--)
result /= 10.0m;
return result;
}
private static ulong Nines(ulong power)
{
ulong result = 9;
if (power >= 0)
for (ulong l = 0; l < power - 1; l++)
result = result * 10 + 9;
return result;
}
Есть несколько примеров использования:
Rational<long>.FromDecimal(0.33333333m, out r, 8, false);
// then r == 1 / 3;
Rational<long>.FromDecimal(0.33333333m, out r, 9, false);
// then r == 33333333 / 100000000;
Ваш случай с нулевой частью нулевой части:
Rational<long>.FromDecimal(0.33m, out r, 28, true);
// then r == 1 / 3;
Rational<long>.FromDecimal(0.33m, out r, 28, true);
// then r == 33 / 100;
Минимальный период демострации:
Rational<long>.FromDecimal(0.123412m, out r, 28, true, 1.5m));
// then r == 1234 / 9999;
Rational<long>.FromDecimal(0.123412m, out r, 28, true, 1.6m));
// then r == 123412 / 1000000; because of minimu repeating of period is 0.1234123 in this case.
Округление в конце:
Rational<long>.FromDecimal(0.8888888888888888888888888889m, out r));
// then r == 8 == 9;
Самый интересный случай:
Rational<long>.FromDecimal(0.12345678m, out r, 28, true, 2, 9);
// then r == 12345678 / 100000000;
Rational<long>.FromDecimal(0.12345678m, out r, 28, true, 2, 8);
// Conversation failed, because of period has not been founded and there are too many digits in fraction part of input value.
Rational<long>.FromDecimal(0.12121212121212121m, out r, 28, true, 2, 9));
// then r == 4 / 33; Despite of too many digits in input value, period has been founded. Thus it possible to convert value to fraction.
Другие тесты и коды, которые можно найти в моей библиотеке MathFunctions на github.
Ответ 23
Вот быстрая и грязная реализация в javascript, которая использует подход грубой силы.
Он не оптимизирован, он работает в пределах предопределенного диапазона фракций: http://jsfiddle.net/PdL23/1/
/* This should convert any decimals to a simplified fraction within the range specified by the two for loops. Haven't done any thorough testing, but it seems to work fine.
I have set the bounds for numerator and denominator to 20, 20... but you can increase this if you want in the two for loops.
Disclaimer: Its not at all optimized. (Feel free to create an improved version.)
*/
decimalToSimplifiedFraction = function(n) {
for(num = 1; num < 20; num++) { // "num" is the potential numerator
for(den = 1; den < 20; den++) { // "den" is the potential denominator
var multiplyByInverse = (n * den ) / num;
var roundingError = Math.round(multiplyByInverse) - multiplyByInverse;
// Checking if we have found the inverse of the number,
if((Math.round(multiplyByInverse) == 1) && (Math.abs(roundingError) < 0.01)) {
return num + "/" + den;
}
}
}
};
//Put in your test number here.
var floatNumber = 2.56;
alert(floatNumber + " = " + decimalToSimplifiedFraction(floatNumber));
Это вдохновляет подход, используемый JPS.
Ответ 24
Как многие люди заявили, что вы действительно не можете преобразовать плавающую точку обратно в дробь (если только это не очень точно, как .25). Конечно, вы могли бы создать некоторый тип поиска для большого массива фракций и использовать какую-то нечеткую логику для получения результата, который вы ищете. Опять же, это не было бы точным, хотя и вам нужно было бы определить нижние границы того, насколько велика ваша потребность в знаменателе.
.32 < x <.34 = 1/3 или что-то в этом роде.
Ответ 25
Вот реализация для ruby http://github.com/valodzka/frac
Math.frac(0.2, 100) # => (1/5)
Math.frac(0.33, 10) # => (1/3)
Math.frac(0.33, 100) # => (33/100)
Ответ 26
Я столкнулся с особенно элегантным решением Haskell, использующим анаморфизм. Это зависит от пакета recursion-schemes.
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Applicative (liftA2)
import Control.Monad (ap)
import Data.Functor.Foldable
import Data.Ratio (Ratio, (%))
isInteger :: (RealFrac a) => a -> Bool
isInteger = ((==) <*>) (realToFrac . floor)
continuedFraction :: (RealFrac a) => a -> [Int]
continuedFraction = liftA2 (:) floor (ana coalgebra)
where coalgebra x
| isInteger x = Nil
| otherwise = Cons (floor alpha) alpha
where alpha = 1 / (x - realToFrac (floor x))
collapseFraction :: (Integral a) => [Int] -> Ratio a
collapseFraction [x] = fromIntegral x % 1
collapseFraction (x:xs) = (fromIntegral x % 1) + 1 / collapseFraction xs
-- | Use the nth convergent to approximate x
approximate :: (RealFrac a, Integral b) => a -> Int -> Ratio b
approximate x n = collapseFraction $ take n (continuedFraction x)
Если вы попробуете это в ghci, это действительно сработает!
λ:> approximate pi 2
22 % 7