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

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

A idéia é torná-lo legível para o usuário entender “x partes de y” como uma maneira melhor de entender os dados.

Eu sei que porcentagens são um bom substituto, mas eu queria saber se havia uma maneira simples de fazer isso?

Eu descobri que a aproximação racional de David Eppstein ao dado número C real é exatamente o que você está pedindo. Sua base na teoria de frações contínuas e muito rápido e bastante compacto.

Eu usei versões deste personalizado 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 rd ** 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  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: %srd\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])); } 

Do Python 2.6 existe o módulo de fractions .

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

Se a saída for 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). Se sim, 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 uma tree de busca binária, de modo que a function executaria no máximo log (27) ~ = 4 3/4 comparações. Aqui está uma versão testada em C 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; } 

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

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

E aqui está uma function de exemplo para como realmente fazer isso usando o VB (de http://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 

(De pesquisas no Google: converta decimal em fração, converta decimal em código de fração)

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 número grande:

 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 obter a fração desejada. Você pode querer sempre usar frações em todo o seu código – apenas lembre-se de reduzir as frações quando puder evitar o transbordamento!

Implementação AC #

 ///  /// Represents a rational number ///  public struct Fraction { public int Numerator; public int Denominator; ///  /// Constructor ///  public Fraction(int numerator, int denominator) { this.Numerator = numerator; this.Denominator = denominator; } ///  /// Approximates a fraction from the provided double ///  public static Fraction Parse(double d) { return ApproximateFraction(d); } ///  /// Returns this fraction expressed as a double, rounded to the specified number of decimal places. /// Returns double.NaN if denominator is zero ///  public double ToDouble(int decimalPlaces) { if (this.Denominator == 0) return double.NaN; return System.Math.Round( Numerator / (double)Denominator, decimalPlaces ); } ///  /// Approximates the provided value to a fraction. /// http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions ///  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); } } 

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

A Árvore de Stern-Brocot induz uma maneira razoavelmente natural de aproximar números reais por frações com denominadores simples.

Parte do problema é que muitas frações não são facilmente interpretadas como frações. Por exemplo, 0,33 não é 1/3, é 33/100. Mas se você se lembra do seu treinamento na escola primária, então há um processo de conversão de valores decimais em frações, mas é improvável que você dê o que você quer, já que a maioria dos números decimais não são armazenados em 0.33, mas 0.329999999999998 ou algo assim.

Faça um favor a si mesmo e não se incomode com isso, mas se você precisar, então 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 por denominadores comuns.

Então, 0,4 seria 4/10. Você procuraria por 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 uniformemente, verificando se o piso da divisão é o mesmo que a própria divisão.

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

Então, 5 não divide 2 uniformemente. Então, você verifica o próximo número, digamos 3. Você faz isso até atingir a raiz quadrada do menor número.

Depois disso, você precisa

Este não é um “algoritmo”, apenas uma solução Python: http://docs.python.org/library/fractions.html

 >>> from fractions import Fraction >>> Fraction('3.1415926535897932').limit_denominator(1000) Fraction(355, 113) 

“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, um algoritmo possível poderia ser:

Se você espera encontrar uma fração mais próxima em um formulário X / Y, onde Y é menor que 10, então você pode distribuir todos os 9 possíveis Ys, para cada Y calcular X, e então selecionar o mais preciso.

Uma solução integrada em R:

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

Isso usa um método de fração contínua e tem cycles opcionais e argumentos max.denominator para ajustar a precisão.

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

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

  1. Multiplique e Divida por 10 ^ x, em que 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 torná-lo 33 e divida-o pelo mesmo para obter 33/100
  2. Reduza o numerador e o denominador da fração resultante por fatorização, até que você não consiga mais obter números 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”

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

Os programas de finanças, entre outros, usariam essa solução para poder fazer cálculos exatos e preservar a precisão que pode ser perdida usando um flutuador 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 quão importante é a precisão para você.

 a = rational(1); b = rational(3); c = a / b; print (c.asFraction) ---> "1/3" print (c.asFloat) ----> "0.333333" 

Acho que a melhor maneira de fazer isso é primeiro converter seu valor flutuante em uma representação ascii. Em C ++ você poderia usar o ostringstream ou em C, você poderia usar o 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(num) < < "/" << pow(10, pow_ten - 1) << endl; 

Uma abordagem semelhante poderia ser tomada em C. reta

Depois disso, você precisaria verificar se a fração está em termos mais baixos. Esse algoritmo dará uma resposta precisa, ou seja, 0,33 produziria "33/100", não "1/3". No entanto, 0,4 daria "4/10", que quando reduzido para os termos mais baixos seria "2/5". Isso pode não ser tão poderoso quanto a solução da EppStein, mas acredito que isso seja mais direto.

O Ruby já possui uma solução incorporada:

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

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

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

Responda em C ++, supondo que você tenha uma class ‘BigInt’, que pode armazenar números inteiros de tamanho ilimitado.

Você pode usar ‘unsigned long long’ em vez disso, mas ele só funcionará para determinados 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: Eu tenho usado esse código em minha própria class ‘RationalNum’ há vários anos, e ele foi testado completamente.

Esse algoritmo de Ian Richards / John Kennedy não apenas retorna boas frações, mas também funciona muito bem em termos de velocidade. Este é o código C # tirado desta resposta por mim.

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

Retorna uma new Fraction(numerator, denominator) . Substitua pelo seu próprio tipo.

Para mais exemplos de valores 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); } 

Exemplo de valores 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 

Você terá dois problemas básicos que dificultarão isso:

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

2) Existem infinidades muito mais números irracionais do que racionais. Um número racional é aquele que pode ser representado como uma fração. Irracional sendo aqueles que não podem.

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

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

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

Está errado no caso comum, por causa de 1/3 = 0.3333333 = 0. (3) Além disso, é impossível descobrir a partir das soluções sugeridas acima que decimal pode ser convertido em fração com precisão definida, porque a saída é sempre fracionária.

MAS, eu sugiro a minha function 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

No início, esta function está tentando encontrar um período de fração na representação de string. Depois disso, a fórmula acima descrita é aplicada.

O código de números racionais é 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 em outros idiomas.

 ///  /// Convert decimal to fraction ///  /// decimal value to convert /// result fraction if conversation is succsess /// precision of considereation frac part of value /// trim zeroes on the right part of the value or not /// minimum period repeating /// precision for determination value to real if period has not been founded ///  public static bool FromDecimal(decimal value, out Rational 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(); 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(long.Parse(strs[0]), 1, false); if (fracPartTrimEnd.Length != 0) result = new Rational(ulong.Parse(fracPartTrimEnd), TenInPower(fracPartTrimEnd.Length)); return true; } } return true; } catch { return false; } } public static Rational FromDecimal(string intPart, string fracPart, string periodPart) { Rational firstFracPart; if (fracPart != null && fracPart.Length != 0) { ulong denominator = TenInPower(fracPart.Length); firstFracPart = new Rational(ulong.Parse(fracPart), denominator); } else firstFracPart = new Rational(0, 1, false); Rational secondFracPart; if (periodPart != null && periodPart.Length != 0) secondFracPart = new Rational(ulong.Parse(periodPart), TenInPower(fracPart.Length)) * new Rational(1, Nines((ulong)periodPart.Length), false); else secondFracPart = new Rational(0, 1, false); var result = firstFracPart + secondFracPart; if (intPart != null && intPart.Length != 0) { long intPartLong = long.Parse(intPart); result = new Rational(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.FromDecimal(0.33333333m, out r, 8, false); // then r == 1 / 3; Rational.FromDecimal(0.33333333m, out r, 9, false); // then r == 33333333 / 100000000; 

Seu caso com corte de parte zero da parte direita:

 Rational.FromDecimal(0.33m, out r, 28, true); // then r == 1 / 3; Rational.FromDecimal(0.33m, out r, 28, true); // then r == 33 / 100; 

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

 Rational.FromDecimal(0.123412m, out r, 28, true, 1.5m)); // then r == 1234 / 9999; Rational.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.FromDecimal(0.8888888888888888888888888889m, out r)); // then r == 8 == 9; 

O caso mais interessante:

 Rational.FromDecimal(0.12345678m, out r, 28, true, 2, 9); // then r == 12345678 / 100000000; Rational.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.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 na minha biblioteca MathFunctions no github .

Aqui está uma implementação rápida e suja em javascript que usa uma abordagem de força bruta. Não otimizado, funciona dentro de um intervalo pré-definido 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 pela abordagem usada pelo JPS.

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ê poderia criar algum tipo de pesquisa para uma grande variedade de frações e usar algum tipo de lógica difusa para produzir o resultado que você está procurando. Novamente, isso não seria exato e você precisaria definir limites mais baixos de quão grande você quer que o denominador vá.

.32

Aqui está a implementação do ruby http://github.com/valodzka/frac

 Math.frac(0.2, 100) # => (1/5) Math.frac(0.33, 10) # => (1/3) Math.frac(0.33, 100) # => (33/100) 

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

 {-# 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 xn = collapseFraction $ take n (continuedFraction x) 

Se você tentar isso no ghci, realmente funciona!

 λ:> approximate pi 2 22 % 7