Como converter flutuadores em frações legíveis por humanos?


103

Digamos que temos 0.33, precisamos produzir 1/3.
Se tivermos 0.4, precisamos produzir 2/5.

A ideia é torná-lo legível para que o usuário entenda " x partes de y " como uma maneira melhor de entender os dados.

Eu sei que as porcentagens são um bom substituto, mas gostaria de saber se existe uma maneira simples de fazer isso?


O .33=> "1/3"exemplo me preocupa; Eu esperaria .33=> "33/100". Suponho que você quisesse dizer .33..., é claro, mas isso expõe um problema com a questão - antes de podermos definir um algoritmo, precisamos decidir sobre o comportamento esperado. A resposta de @Debilski em Python usa o .limit_denominator()padrão para um denominador máximo de 10 ^ 7; provavelmente um bom padrão na prática, mas isso ainda pode introduzir erros se você não tiver cuidado, e faz o retorno "33/100"no .33caso.
dimo414

Com tudo o que language- specifc recursos estão disponíveis. Não esteja claro o que você está perguntando, se realmente não for uma mera contradição de termos.
Marquês de Lorne

Respostas:


70

Eu descobri que a aproximação racional encontrada de David Eppstein para um determinado código C de número real é exatamente o que você está pedindo. É baseado na teoria das frações contínuas e muito rápido e bastante compacto.

Usei versões customizadas para limites específicos de numerador e denominador.

/*
** 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]));
}

6
Para quem procura uma solução em Ruby, estamos com sorte! Christopher Lord implementou o algoritmo acima em uma gema Ruby. Veja christopher.lord.ac/fractions-in-ruby e rubygems.org/gems/fraction
shedd

6
Esteja ciente de que existem alguns casos extremos que este código não lida muito bem: quando dado -1,3333333 com um denominador máximo de 4, ele retorna 4 / -3 com um erro de 3,333333e-08 e -5/4 com um erro = -8.333330e-02, que está correto. Mas quando dado -1,33333337 com o mesmo denominador máximo, ele transforma 12121211 / -9090908 com um erro de erro = 4,218847e-15 e -4/3 com um erro de -3,6666667e-08, o que não é correto. Este é um problema em particular ao apresentar o algoritmo com números de ponto flutuante computados, como -4/3, que produz resultados incorretos como esses.
edsko

27

Do Python 2.6 em diante, existe o fractionsmódulo.

(Citando os documentos.)

>>> 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)

6
Notas de implementação e algoritmo em hg.python.org/cpython/file/822c7c0d27d1/Lib/fractions.py#l211
piro

2
@Debilski qual dos OPs language agnostice algorithmtags sua resposta satisfaz?
vladr

2
@vladr Bem, dado que escrevi esta resposta há quase 6 anos (e mais de um ano depois que a pergunta foi feita), acho que não sei mais qual era o meu raciocínio naquela época. Provavelmente eu estava me referindo a este comentário: stackoverflow.com/questions/95727/… OTOH Também pode ser que esta resposta tenha sido mesclada de outra pergunta. Quem pode dizer depois de todos esses anos ...
Debilski

Você pode adicionar algumas frases sobre o algoritmo usado pelo módulo de frações (e atualizar sua resposta para Python3, talvez).
einpoklum

21

Se a saída é para dar a um leitor humano uma impressão rápida da ordem do resultado, não faz sentido retornar algo como "113/211", então a saída deve se limitar a usar números de um dígito (e talvez 1 / 10 e 9/10). Nesse caso, você pode observar que existem apenas 27 frações diferentes .

Como a matemática subjacente para gerar a saída nunca mudará, uma solução poderia ser simplesmente codificar permanentemente uma árvore de pesquisa binária, de modo que a função executasse no máximo log (27) ~ = 4 3/4 comparações. Aqui está uma versão C testada do código

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;
}

3
Esse é o tipo de pensamento lateral de que precisamos mais! Excelente sugestão.
edsko

1
É um pouco feio, mas é um jeito muito rápido e prático
Bosak

1
Esta é uma abordagem interessante e maravilhosamente simples. Para economizar espaço, você poderia, em vez disso, pesquisar um array binário ou criar uma árvore binária, mas sua abordagem é provavelmente um pouco mais rápida (você poderia economizar espaço usando uma única chamada para strcat antes de retornar e atribuir uma var onde agora é chamada). Também teria incluído 3/10 e 7/10, mas talvez seja só eu.
Jimhark de

1
Inspirado por esta solução, criei um código curto (mas totalmente não otimizado). Ele pode ser facilmente estendido para cobrir uma gama maior de frações. jsfiddle.net/PdL23/1
Deepak Joy

1
Observe que 1/1000também é muito legível, mas o algoritmo acima produziria apenas uma 1/10aproximação muito grosseira ; Acredito que melhorias podem ser feitas em termos da qual denominadores humanamente legíveis um pode escolher, e / ou a adição de <, >, <<, >>prefixos para dar uma idéia da grossura do aproximação.
vladr

16

Aqui está um link que explica a matemática por trás da conversão de um decimal em uma fração:

http://www.webmath.com/dec2fract.html

E aqui está um exemplo de função de como realmente fazer isso usando VB (em 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

(A partir de pesquisas do google: converta decimal em fração, converta decimal em código de fração)


2
Observe que este algoritmo leva Ω (m) tempo quando f = n / m. E isso pode ser muito, mesmo que você não pretendesse (considere 0,666666666667).
einpoklum

10

Você pode querer ler O que todo cientista da computação deve saber sobre aritmética de ponto flutuante .

Você terá que especificar alguma precisão multiplicando por um grande número:

3.141592 * 1000000 = 3141592

então você pode fazer uma fração:

3 + (141592 / 1000000)

e reduzir via GCD ...

3 + (17699 / 125000)

mas não há como retirar a fração pretendida . Você pode querer sempre usar frações em todo o seu código - apenas lembre-se de reduzir as frações quando puder para evitar o estouro!


9

Aqui estão as versões Perl e Javascript do código VB sugerido por 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";
}

E o javascript quase idêntico:

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;
}

9

Implementação AC #

/// <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);
    }
}


6

Parte do problema é que muitas frações não são realmente facilmente interpretadas como frações. Por exemplo, 0,33 não é 1/3, é 33/100. Mas se você se lembrar de seu treinamento na escola primária, então há um processo de conversão de valores decimais em frações. No entanto, é improvável que você obtenha o que deseja, já que na maioria das vezes os números decimais não são armazenados em 0,33, mas em 0,329999999999998 ou algo parecido.

Faça um favor a si mesmo e não se preocupe com isso, mas se precisar, você pode fazer o seguinte:

Multiplique o valor original por 10 até remover a parte fracionária. Mantenha esse número e use-o como divisor. Em seguida, faça uma série de simplificações procurando denominadores comuns.

Portanto, 0,4 seria 4/10. Em seguida, você procuraria divisores comuns começando com valores baixos, provavelmente números primos. Começando com 2, você veria se 2 divide o numerador e o denominador igualmente, verificando se o piso da divisão é o mesmo da própria divisão.

floor(5/2) = 2
5/2 = 2.5

Portanto, 5 não divide 2 igualmente. Então você verifica o próximo número, digamos 3. Faça isso até atingir a raiz quadrada ou acima do número menor.

Depois de fazer isso, você precisa


1
Eu sugiro usar o algoritmo euclidiano para a última etapa
Graphics Noob


4

"Digamos que temos 0,33, precisamos produzir" 1/3 "."

Que precisão você espera que a "solução" tenha? 0,33 não é igual a 1/3. Como você reconhece uma resposta "boa" (fácil de ler)?

Não importa o que aconteça, um algoritmo possível poderia ser:

Se você espera encontrar a fração mais próxima na forma X / Y onde Y é menor que 10, então você pode fazer um loop por todos os 9 Ys possíveis, para cada Y computar X, e então selecionar o mais preciso.


3

Acho que a melhor maneira de fazer isso é primeiro converter seu valor float em uma representação ASCII. Em C ++ você pode usar ostringstream ou em C, você pode usar sprintf. Veja como ficaria em 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;

Uma abordagem semelhante poderia ser adotada em linha reta C.

Depois disso, você precisará verificar se a fração está em termos mais baixos. Este algoritmo fornecerá uma resposta precisa, ou seja, 0,33 produziria "33/100", não "1/3". No entanto, 0,4 daria "4/10", que quando reduzido aos termos mais baixos seria "2/5". Isso pode não ser tão poderoso quanto a solução de EppStein, mas acredito que seja mais simples.


8 anos depois, descobri sua solução, testei e está funcionando perfeitamente até agora, mas você disse que não é tão poderosa quanto a solução de EppStein e me pergunto por quê. Já que sua solução é muito mais simples, não deveria ser essa a solução escolhida, não pretendemos fazer o código mais simples possível, contanto que funcione e seja seguro?
HBatalha

3

Uma solução integrada em R:

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

Este utiliza um método de fração contínua e tem opcional cyclese max.denominatorargumentos para ajustar a precisão.


Também library(numbers)e contFrac(0.6666); para obter a saída da string conforme desejado:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt

2

Você terá que descobrir que nível de erro está disposto a aceitar. Nem todas as frações decimais se reduzirão a uma fração simples. Eu provavelmente escolheria um número facilmente divisível, como 60, e descobriria quantos 60 está mais próximo do valor e, em seguida, simplificaria a fração.


2

Você pode fazer isso em qualquer linguagem de programação usando as seguintes etapas:

  1. Multiplique e divida por 10 ^ x, onde x é a potência de 10 necessária para garantir que o número não tenha casas decimais restantes. Exemplo: multiplique 0,33 por 10 ^ 2 = 100 para chegar a 33 e divida pelo mesmo para obter 33/100
  2. Reduza o numerador e o denominador da fração resultante por fatoração, até que você não possa mais obter inteiros do resultado.
  3. A fração reduzida resultante deve ser sua resposta.

Exemplo: 0,2 = 0,2 x 10 ^ 1/10 ^ 1 = 2/10 = 1/5

Então, isso pode ser lido como '1 parte de 5'


2

Uma solução é simplesmente armazenar todos os números como números racionais em primeiro lugar. Existem bibliotecas para aritmética de números racionais (por exemplo, GMP ). Se estiver usando uma linguagem OO, você poderá usar apenas uma biblioteca de classes de números racionais para substituir sua classe de números.

Os programas de finanças, entre outros, usariam essa solução para fazer cálculos exatos e preservar a precisão que pode ser perdida com o uso de um float simples.

Claro que será muito mais lento, por isso pode não ser prático para você. Depende de quantos cálculos você precisa fazer e da importância da precisão para você.

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

print (c.asFraction)  --->  "1/3"
print (c.asFloat) ----> "0.333333"

2

Digamos que temos 0,33, precisamos produzir "1/3". Se tivermos "0,4", precisamos gerar "2/5".

É errado no caso comum, porque 1/3 = 0,33333333 = 0. (3) Além disso, é impossível descobrir a partir das soluções sugeridas acima se o decimal pode ser convertido em fração com precisão definida, porque a saída é sempre fração.

MAS, eu sugiro minha função abrangente com muitas opções baseadas na ideia de séries geométricas infinitas , especificamente na fórmula:

insira a descrição da imagem aqui

A princípio, essa função tenta encontrar o período da fração na representação da string. Depois disso, a fórmula descrita acima é aplicada.

O código de números racionais foi emprestado da implementação de números racionais de Stephen M. McKamey em C #. Espero que não seja muito difícil portar meu código para outras linguagens.

/// <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;
}

Existem alguns exemplos de usos:

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;

Seu caso com recorte da parte zero da parte direita:

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;

Demonstração de período mínimo:

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.

Arredondamento no final:

Rational<long>.FromDecimal(0.8888888888888888888888888889m, out r));
// then r == 8 == 9;

O caso mais interessante:

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.

Outros testes e códigos que todos podem encontrar em minha biblioteca MathFunctions no github .


2

Ruby já tem uma solução integrada:

0.33.rationalize.to_s # => "33/100"
0.4.rationalize.to_s # => "2/5"

No Rails, os atributos numéricos ActiveRecord também podem ser convertidos:

product.size = 0.33
product.size.to_r.to_s # => "33/100"

2

Responda em C ++, supondo que você tenha uma classe 'BigInt', que pode armazenar inteiros de tamanho ilimitado.

Você pode usar 'unsigned long long' em vez disso, mas só funcionará para certos valores.

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) retornará "+0/1", então você pode querer lidar com este caso separadamente.

PS: Tenho usado esse código em minha própria classe 'RationalNum' por vários anos e ele foi testado exaustivamente.


Seu exemplo parece quebrar em valores como 1,333333 .. ele entra em um loop muito longo tentando encontrar o valor e parece não funcionar ... funciona bem com outros valores simples como 1,25
Adamski

@Adamski: Obrigado. O período de "convergência" do whileloop é limitado pelo tamanho de double, que normalmente é de 64 bits. Portanto, não depende do valor inicial da entrada ( val). A GCDfunção, no entanto, depende desse valor, embora geralmente converta para uma solução muito rápida. É possível que você não implementou essa função corretamente?
barak manos

@Adamski: Além disso, como mencionei no início da resposta, se você estiver usando em unsigned long longvez de BigInt, não necessariamente produzirá o resultado correto para cada valor de entrada ... Mas mesmo nesse cenário, o código não é deveria "entrar em um loop muito longo".
barak manos

Ah ok sim, isso é totalmente possível, a função GCD que eu estava usando faz parte da classe BigInteger da biblioteca Juce. Obrigado pela informação!
Adamski

@Adamski: Portanto, não faz sentido que a GCDfunção não seja implementada corretamente. Você verificou se o código é executado por muito tempo durante o whileloop ou depois dele? Vou verificar o valor de 1,33333, para ver o que está por trás disso. Obrigado.
barak manos

2

Este algoritmo de Ian Richards / John Kennedy não só retorna boas frações, mas também tem um desempenho muito bom em termos de velocidade. Este é o código C # retirado desta resposta minha.

Ele pode lidar com todos os doublevalores, exceto valores especiais como NaN e +/- infinito, que você terá que adicionar se necessário.

Ele retorna a new Fraction(numerator, denominator). Substitua pelo seu próprio tipo.

Para obter mais valores de exemplo e uma comparação com outros algoritmos, clique aqui

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);
}

Valores de exemplo retornados por este algoritmo:

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         

1

Você terá dois problemas básicos que tornarão isso difícil:

1) O ponto flutuante não é uma representação exata, o que significa que se você tiver uma fração de "x / y" que resulta em um valor de "z", seu algoritmo de fração pode retornar um resultado diferente de "x / y".

2) Existem infinitos, muitos mais números irracionais do que racionais. Um número racional é aquele que pode ser representado como uma fração. Ser irracionais que não podem.

No entanto, de uma forma barata, uma vez que o ponto flutuante tem precisão limite, você sempre pode representá-lo como alguma forma de facção. (Eu acho que...)


4
Um float (ou duplo) é uma fração. Seu denominador é uma potência de 2. É por isso que eles não podem representar exatamente alguns números racionais.
Erickson

1

Concluiu o código acima e o converteu em 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";
    }

Obrigado, usei isso para Delphi, mais fácil de portar do que todo aquele material cacheado
Peter Turner

1

Aqui está uma implementação rápida e suja em javascript que usa uma abordagem de força bruta. Nem um pouco otimizado, ele funciona dentro de um intervalo predefinido de frações: 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));

Isso é inspirado na abordagem usada pelo JPS.


0

Como muitas pessoas afirmaram, você realmente não pode converter um ponto flutuante de volta em uma fração (a menos que seja extremamente exato, como 0,25). Claro que você pode criar algum tipo de pesquisa para um grande array de frações e usar algum tipo de lógica difusa para produzir o resultado que está procurando. Novamente, isso não seria exato e você precisaria definir um limite inferior de quão grande você deseja que o denominador vá.

0,32 <x <0,34 = 1/3 ou algo parecido.



0

Me deparei com uma solução Haskell especialmente elegante que faz uso de um anamorfismo. Depende do pacote de esquemas de recursão .

{-# 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)

Se você experimentar no ghci, realmente funciona!

λ:> approximate pi 2
22 % 7
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.