Как преобразовать поплавки в читаемые человеком фракции? - PullRequest
99 голосов
/ 18 сентября 2008

Допустим, у нас есть 0.33, нам нужно вывести «1/3».
Если у нас есть «0,4», нам нужно вывести «2/5».

Идея состоит в том, чтобы сделать его читаемым человеком, чтобы пользователь понимал "x частей из y" как лучший способ понимания данных.

Я знаю, что проценты - хорошая замена, но мне было интересно, есть ли простой способ сделать это?

Ответы [ 26 ]

4 голосов
/ 18 сентября 2008

"Допустим, у нас есть 0.33, нам нужно вывести" 1/3 "."

Какую точность вы ожидаете от "решения"? 0,33 не равно 1/3. Как распознать «хороший» (легко читаемый) ответ?

Не смотря ни на что, возможный алгоритм может быть:

Если вы ожидаете найти ближайшую дробь в форме X / Y, где Y меньше 10, то вы можете зациклить все 9 возможных Y для каждого Y, вычислить X, а затем выбрать наиболее точную. *

3 голосов
/ 30 декабря 2012

Встроенное решение в R:

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

Используется метод непрерывной дроби и необязательные аргументы cycles и max.denominator для настройки точности.

2 голосов
/ 13 февраля 2017

Этот алгоритм Иана Ричардса / Джона Кеннеди не только возвращает хорошие дроби, но и очень хорошо работает в плане скорости. Это код C #, взятый из этого ответа мной.

Может обрабатывать все double значения, кроме специальных значений, таких как NaN и +/- бесконечность, которые вы должны будете добавить при необходимости.

Возвращает 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         
2 голосов
/ 30 декабря 2013

Ответ на C ++, при условии, что у вас есть класс 'BigInt', который может хранить целые числа неограниченного размера.

Вместо этого вы можете использовать «unsigned long long», но он будет работать только для определенных значений.

void GetRational(double val)
{
    if (val == val+1) // Inf
        throw "Infinite Value";
    if (val != val) // NaN
        throw "Undefined Value";

    bool sign = false;
    BigInt enumerator = 0;
    BigInt denominator = 1;

    if (val < 0)
    {
        val = -val;
        sign = true;
    }

    while (val > 0)
    {
        unsigned int intVal = (unsigned int)val;
        val -= intVal;
        enumerator += intVal;
        val *= 2;
        enumerator *= 2;
        denominator *= 2;
    }

    BigInt gcd = GCD(enumerator,denominator);
    enumerator /= gcd;
    denominator /= gcd;

    Print(sign? "-":"+");
    Print(enumerator);
    Print("/");
    Print(denominator);

    // Or simply return {sign,enumerator,denominator} as you wish
}

Кстати, GetRational (0.0) вернет "+0/1", так что вы можете заняться этим делом отдельно.

P.S .: Я использовал этот код в своем собственном классе 'RationalNum' в течение нескольких лет, и он был тщательно протестирован.

2 голосов
/ 08 декабря 2012

В 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"
2 голосов
/ 17 сентября 2011

Я думаю, что лучший способ сделать это - сначала преобразовать значение с плавающей запятой в представление ascii. В C ++ вы можете использовать ostringstream или в C, вы можете использовать sprintf. Вот как это будет выглядеть в C ++:

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

Аналогичный подход может быть использован в прямой С.

После этого вам нужно будет проверить, что фракция находится в самых низких условиях. Этот алгоритм даст точный ответ, то есть 0,33 будет выводить «33/100», а не «1/3». Тем не менее, 0,4 даст «4/10», что при уменьшении до минимальных условий будет «2/5». Это может быть не так эффективно, как решение EppStein, но я считаю, что это более просто.

2 голосов
/ 18 сентября 2008

Вы должны выяснить, какой уровень ошибки вы готовы принять. Не все десятичные дроби приведут к простой дроби. Я бы, вероятно, выбрал бы легко делимое число, например, 60, и выяснил, сколько 60-х ближе всего к значению, а затем упростил бы дробь.

2 голосов
/ 18 сентября 2008

Вы можете сделать это на любом языке программирования, выполнив следующие действия:

  1. Умножьте и разделите на 10 ^ x, где x - это степень 10, необходимая для того, чтобы в числе не осталось десятичных знаков. Пример: умножьте 0,33 на 10 ^ 2 = 100, чтобы получить 33, и разделите на то же самое, чтобы получить 33/100
  2. Сократите числитель и знаменатель полученной дроби с помощью факторизации, пока вы не сможете больше получить целые числа из результата.
  3. Полученная уменьшенная дробь должна быть вашим ответом.

Пример: 0.2 = 0,2 х 10 ^ 1/10 ^ 1 = 2/10 = 1/5

Итак, это можно прочитать как '1 часть из 5'

2 голосов
/ 19 сентября 2008

Одним из решений является сохранение всех чисел как рациональных чисел. Существуют библиотеки для арифметики рациональных чисел (например, GMP ). Если вы используете язык ОО, вы можете просто использовать рациональную библиотеку классов чисел для замены своего класса чисел.

Финансовые программы, среди прочего, будут использовать такое решение, чтобы иметь возможность делать точные вычисления и сохранять точность, которая может быть потеряна при использовании простого плавающего числа.

Конечно, это будет намного медленнее, поэтому это может быть непрактично для вас. Зависит от того, сколько вычислений вам нужно сделать, и насколько важна для вас точность.

a = rational(1);
b = rational(3);
c = a / b;

print (c.asFraction)  --->  "1/3"
print (c.asFloat) ----> "0.333333"
1 голос
/ 09 декабря 2013

Вот быстрая и грязная реализация в 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...