Как преобразовать числа с плавающей запятой в удобочитаемые дроби?

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

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

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


person Swaroop C H    schedule 18.09.2008    source источник
comment
Меня интересует пример .33 = ›"1/3"; Я ожидал .33 = ›"33/100". Я предполагаю, что вы, конечно, имели в виду .33..., но это выявляет проблему с вопросом - прежде чем мы сможем выбрать алгоритм, нам нужно определить ожидаемое поведение. В ответе на Python @ Debilski используется .limit_denominator(), который по умолчанию максимальный знаменатель 10 ^ 7; вероятно, это хороший вариант по умолчанию на практике, но это все равно может привести к ошибкам, если вы не будете осторожны, и действительно возвращает "33/100" в случае .33.   -  person dimo414    schedule 22.04.2015
comment
Для любого языка - специфика доступны функции. Непонятно, о чем вы спрашиваете, если это действительно противоречие.   -  person user207421    schedule 13.02.2017


Ответы (26)


Я нашел рационального приближения к заданному действительному числу 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]));
}
person Epsilon    schedule 18.09.2008
comment
Тем из вас, кто ищет решение на Ruby, нам повезло! Кристофер Лорд реализовал описанный выше алгоритм в геме Ruby. См. christopher.lord.ac/fractions-in-ruby и rubygems.org/gems/fraction - person shedd; 26.01.2011
comment
Имейте в виду, что есть некоторые крайние случаи, которые этот код не обрабатывает очень хорошо: когда задано -1,3333333 с максимальным знаменателем 4, он возвращает 4 / -3 с ошибкой 3,333333e-08 и -5/4 с ошибкой = -8.333330e-02, что правильно. Но когда дано -1,33333337 с тем же максимальным знаменателем, получается 12121211 / -9090908 с ошибкой error = 4.218847e-15 и -4/3 с ошибкой -3.666667e-08, что неверно. Это проблема, в частности, при представлении алгоритма с вычисленными числами с плавающей запятой, такими как -4/3, что дает такие неверные результаты. - person edsko; 01.08.2011

Начиная с 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)
person Debilski    schedule 02.01.2010
comment
Примечания по реализации и алгоритму на hg.python.org/cpython/file /822c7c0d27d1/Lib/fractions.py#l211 - person piro; 28.03.2011
comment
@Debilski, какой из тегов OP language agnostic и algorithm удовлетворяет ваш ответ? - person vladr; 06.11.2015
comment
@vladr Что ж, учитывая, что я написал этот ответ почти 6 лет назад (и более чем через год после того, как вопрос был задан), думаю, я больше не знаю, каковы были мои рассуждения тогда. Скорее всего, я имел в виду этот комментарий: stackoverflow.com/questions/95727/ OTOH Также может быть, что этот ответ был объединен с другим вопросом. Кто может сказать после стольких лет ... - person Debilski; 08.11.2015
comment
Вы можете добавить несколько предложений об алгоритме, используемом модулем дробей (и, возможно, обновить свой ответ для Python3). - person einpoklum; 27.03.2016

Если вывод должен дать читателю быстрое представление о порядке результата, нет смысла возвращать что-то вроде «113/211», поэтому вывод должен ограничиваться использованием однозначных чисел (и, возможно, 1 / 10 и 9/10). Если это так, вы можете заметить, что существует всего 27 различных дробей.

Поскольку лежащая в основе математика для генерации вывода никогда не изменится, решением может быть просто жестко закодировать двоичное дерево поиска, чтобы функция выполняла не более log (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;
}
person J P    schedule 18.09.2008
comment
Это тот вид нестандартного мышления, которого нам нужно больше! Отличное предложение. - person edsko; 01.08.2011
comment
Немного уродливый, но очень быстрый и практичный способ - person Bosak; 11.11.2012
comment
Это интересный и удивительно простой подход. Чтобы сэкономить место, вы можете вместо этого выполнить двоичный поиск в массиве или создать двоичное дерево, но ваш подход, вероятно, немного быстрее (вы можете сэкономить место, используя один вызов strcat перед возвратом и назначением переменной, где она сейчас вызывается). Также я бы включил 3/10 и 7/10, но, может быть, это только я. - person jimhark; 20.01.2013
comment
Вдохновленный этим решением, я создал короткий (но совершенно неоптимизированный) код. Его можно легко расширить, чтобы охватить более широкий диапазон фракций. jsfiddle.net/PdL23/1 - person Deepak Joy Cheenath; 09.12.2013
comment
Обратите внимание, что 1/1000 также очень хорошо читается человеком, но приведенный выше алгоритм дает только очень грубое 1/10 приближение; Я считаю, что могут быть сделаны улучшения с точки зрения того, какие знаменатели, понятные человеку, можно выбирать, и / или добавление префиксов <, >, <<, >>, чтобы дать представление о грубости приближения. - person vladr; 06.11.2015

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

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: преобразовать десятичное число в дробное, преобразовать десятичное в дробный код)

person devinmoore    schedule 18.09.2008
comment
Обратите внимание, что этот алгоритм занимает время Ω (m), когда f = n / m. И это может быть много, даже если вы этого не планировали (рассмотрим 0,66666666667). - person einpoklum; 27.03.2016

Возможно, вы захотите прочитать Что должен знать каждый компьютерный ученый об арифметике с плавающей запятой .

Вам нужно будет указать некоторую точность, умножив на большое число:

3.141592 * 1000000 = 3141592

тогда вы можете сделать дробь:

3 + (141592 / 1000000)

и уменьшить через GCD ...

3 + (17699 / 125000)

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

person nlucaroni    schedule 18.09.2008

Вот версии 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;
}
person mivk    schedule 25.03.2009

Реализация C #

/// <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);
    }
}
person Tom    schedule 21.12.2011

Дерево Стерна-Броко предлагает довольно естественный способ аппроксимировать действительные числа дробями с простыми знаменателями. .

person Doug McClean    schedule 19.09.2008

Отчасти проблема в том, что такое количество дробей на самом деле нелегко интерпретировать как дроби. Например. 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. Вы делаете это до тех пор, пока не достигнете квадратного корня меньшего числа или больше.

После того, как вы это сделаете, вам понадобится

person Orion Adrian    schedule 18.09.2008
comment
Я бы предложил использовать евклидов алгоритм для этого последнего шага - person Graphics Noob; 26.08.2009

Это не «алгоритм», а просто решение Python: http://docs.python.org/library/fractions.html

>>> from fractions import Fraction
>>> Fraction('3.1415926535897932').limit_denominator(1000)
Fraction(355, 113)
person eldad    schedule 25.08.2009

«Допустим, у нас есть 0,33, нам нужно вывести« 1/3 ».»

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

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

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

person Suma    schedule 18.09.2008

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

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

Пример: 0,2 = 0,2 x 10 ^ 1/10 ^ 1 = 2/10 = 1/5.

Таким образом, это можно прочитать как «1 часть из 5».

person Pascal    schedule 18.09.2008

Я думаю, что лучший способ сделать это - сначала преобразовать значение с плавающей запятой в представление 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;

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

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

person bpm    schedule 17.09.2011
comment
8 лет спустя я познакомился с вашим решением, я протестировал, и до сих пор оно работает отлично, но вы сказали, что оно не так мощно, как решение EppStein, и мне интересно, почему. Поскольку ваше решение намного проще, разве это не должно быть предпочтительным решением, разве мы не должны делать максимально простой код, если он работает и безопасен? - person HBatalha; 10.12.2019

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

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

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

person Ben Bolker    schedule 29.12.2012
comment
Также library(numbers) и contFrac(0.6666); чтобы получить желаемый строковый вывод: paste(contFrac(0.666, tol=1e-03)$rat, collapse="/") - person rbatt; 30.06.2015

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

person Mark Bessey    schedule 18.09.2008

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

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

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

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

print (c.asFraction)  --->  "1/3"
print (c.asFloat) ----> "0.333333"
person robottobor    schedule 18.09.2008

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

В общем случае это неправильно, потому что 1/3 = 0,3333333 = 0. (3) Более того, из предложенных выше решений невозможно выяснить, можно ли десятичное число преобразовать в дробь с определенной точностью, потому что вывод всегда дробный.

НО, я предлагаю свою комплексную функцию с множеством вариантов, основанных на идее Бесконечных геометрических рядов, конкретно по формуле:

введите описание изображения здесь

Сначала эта функция пытается найти период дроби в строковом представлении. После этого применяется описанная выше формула.

Код рациональных чисел заимствован у Стивена М. МакКейми Реализация рациональных чисел на C #. Надеюсь, что перенести мой код на другие языки не так уж и сложно.

/// <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's possible to convert value to fraction.

Другие тесты и код можно найти в моей библиотеке MathFunctions на github < / а>.

person Ivan Kochurkin    schedule 24.09.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"
person Josh W Lewis    schedule 08.12.2012

Ответьте на 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 несколько лет, и он был тщательно протестирован.

person barak manos    schedule 30.12.2013
comment
Ваш пример, похоже, разбивается на такие значения, как 1,333333 ... он входит в очень длинный цикл, пытаясь найти значение, и, похоже, не работает ... отлично работает с другими простыми значениями, такими как 1,25 - person Adamski; 01.06.2014
comment
@ Адамски: Спасибо. Период сходимости цикла while ограничен размером double, который обычно составляет 64 бита. Таким образом, это не зависит от начального значения ввода (val). Однако функция GCD действительно зависит от этого значения, хотя обычно она довольно быстро сводится к решению. Возможно, вы неправильно реализовали эту функцию? - person barak manos; 01.06.2014
comment
@ Адамски: Вдобавок, как я уже упоминал в начале ответа, если вы используете unsigned long long вместо BigInt, тогда это не обязательно даст правильный результат для каждого входного значения ... Но даже в этом сценарии код не должен заходить в очень длинный цикл. - person barak manos; 01.06.2014
comment
Ах, да, это вполне возможно, функция GCD, которую я использовал, является частью класса BigInteger библиотеки Juce. Спасибо за информацию! - person Adamski; 01.06.2014
comment
@Adamski: Итак, бессмысленно, что функция GCD не реализована должным образом. Вы проверяли, работает ли код долго во время цикла while или после него? Я проверю значение 1,33333, чтобы увидеть, что за этим стоит. Спасибо. - person barak manos; 01.06.2014
comment
@Adamski: Я проверил код с 1.333333 в качестве входных данных, и он хорошо работает даже при использовании unsigned long long вместо BigInt. Цикл while выполняет 52 итерации, после чего результат 6004798001960786/4503599627370496. Функция GCD возвращает 2, поэтому окончательный результат будет 3002399000980393/2251799813685248. - person barak manos; 01.06.2014

Этот алгоритм разработан Яном Ричардс / Джон Кеннеди не только возвращает хорошие дроби, а также очень хорошо работает с точки зрения скорости. Это код 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         
person Kay Zed    schedule 13.02.2017

У вас будут две основные проблемы, которые сделают это трудным:

1) Плавающая точка не является точным представлением, что означает, что если у вас есть дробная часть «x / y», которая дает значение «z», ваш алгоритм дроби может вернуть результат, отличный от «x / y».

2) В бесконечности иррациональных чисел намного больше, чем рациональных. Рациональное число - это такое число, которое можно представить в виде дроби. Нерациональные существа, которые не могут.

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

person Torlack    schedule 18.09.2008
comment
Число с плавающей запятой (или двойное) - это дробь. Его знаменатель - степень 2. Вот почему они не могут точно представлять некоторые рациональные числа. - person erickson; 19.09.2008

Завершил приведенный выше код и преобразовал его в 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";
    }
person João Lopes    schedule 02.01.2010
comment
Спасибо, я использовал это для Delphi, легче переносить, чем все эти фигурные вещи - person Peter Turner; 26.09.2012

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

person Deepak Joy Cheenath    schedule 09.12.2013

Как утверждали многие люди, вы действительно не можете преобразовать числа с плавающей запятой обратно в дробь (если только она не очень точна, например, 0,25). Конечно, вы можете создать какой-то поиск для большого массива дробей и использовать какую-то нечеткую логику для получения желаемого результата. Опять же, это не будет точно, и вам нужно будет определить нижнюю границу того, насколько большим должен быть знаменатель.

.32 ‹x‹ .34 = 1/3 или что-то в этом роде.

person Tim    schedule 18.09.2008

Вот реализация для рубина 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)
person valodzka    schedule 26.05.2010

Я наткнулся на особенно элегантное решение Haskell, использующее анаморфизм. Это зависит от пакета схем рекурсии.

{-# 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
person Community    schedule 09.09.2017