¿Cómo convertir flotantes en fracciones legibles por humanos?

103

Digamos que tenemos 0.33, necesitamos generar 1/3.
Si es así 0.4, tenemos que generar 2/5.

La idea es hacerlo legible por humanos para que el usuario comprenda " x partes de y " como una mejor forma de comprender los datos.

Sé que los porcentajes son un buen sustituto, pero me preguntaba si había una forma sencilla de hacer esto.

Swaroop CH
fuente
El ejemplo .33=> "1/3"me concierne; Yo esperaría .33=> "33/100". Supongo que quiso decir, .33...por supuesto, pero expone un problema con la pregunta: antes de que podamos establecer un algoritmo, debemos decidir el comportamiento esperado. La respuesta de Python de @ Debilski utiliza .limit_denominator()el valor predeterminado de un denominador máximo de 10 ^ 7; probablemente un buen valor por defecto en la práctica, pero esto todavía puede introducir errores si no tiene cuidado, y lo hace de retorno "33/100"en el .33caso.
dimo414
Con todas las funciones específicas del idioma que estén disponibles. No está claro lo que está preguntando, si es que no es una mera contradicción de términos.
Marqués de Lorne

Respuestas:

70

He descubierto que la aproximación racional de David Eppstein al código C del número real dado es exactamente lo que estás pidiendo. Se basa en la teoría de las fracciones continuas y es muy rápido y bastante compacto.

He usado versiones de esto personalizadas para límites específicos de numerador y 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]));
}
Épsilon
fuente
6
Para aquellos de ustedes que buscan una solución en Ruby, ¡estamos de suerte! Christopher Lord ha implementado el algoritmo anterior en una gema Ruby. Ver christopher.lord.ac/fractions-in-ruby y rubygems.org/gems/fraction
shedd
6
Tenga en cuenta que hay algunos casos extremos que este código no maneja muy bien: cuando se le da -1.3333333 con un denominador máximo de 4, devuelve 4 / -3 con un error de 3.333333e-08 y -5/4 con un error = -8.333330e-02, que es correcto. Pero cuando se le da -1.33333337 con el mismo denominador máximo, se convierte en 12121211 / -9090908 con un error de error = 4.218847e-15 y -4/3 con un error de -3.666667e-08, que no es correcto. Este es un problema en particular cuando se presenta el algoritmo con números de punto flotante calculados como -4/3, que arroja resultados incorrectos como estos.
edsko
27

Desde Python 2.6 en adelante está el fractionsmódulo.

(Citando de los 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)
Debilski
fuente
6
Notas de implementación y algoritmo en hg.python.org/cpython/file/822c7c0d27d1/Lib/fractions.py#l211
piro
2
@Debilski ¿cuál de los OP language agnosticy algorithmetiquetas satisface su respuesta?
vladr
2
@vladr Bueno, dado que escribí esta respuesta hace casi 6 años (y más de un año después de que se hizo la pregunta), creo que ya no sé cuál era mi razonamiento en ese entonces. Lo más probable es que me estuviera refiriendo a este comentario: stackoverflow.com/questions/95727/… OTOH También podría ser que esta respuesta se haya combinado de otra pregunta. ¿Quién puede decirlo después de todos esos años?
Debilski
Podría agregar algunas oraciones sobre el algoritmo utilizado por el módulo de fracciones (y quizás actualizar su respuesta para Python3).
einpoklum
21

Si el resultado es para darle a un lector humano una impresión rápida del orden del resultado, no tiene sentido devolver algo como "113/211", por lo que el resultado debería limitarse a usar números de un dígito (y tal vez 1 / 10 y 9/10). Si es así, puede observar que solo hay 27 fracciones diferentes .

Dado que la matemática subyacente para generar la salida nunca cambiará, una solución podría ser simplemente codificar un árbol de búsqueda binario, de modo que la función funcione como máximo log (27) ~ = 4 3/4 comparaciones. Aquí hay una versión C probada del 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;
}
JP
fuente
3
¡Este es el tipo de pensamiento lateral que necesitamos más! Excelente sugerencia.
edsko
1
Es un poco feo pero muy rápido y práctico
Bosak
1
Este es un enfoque interesante que es maravillosamente simple. Para ahorrar espacio, podría buscar binariamente una matriz o crear un árbol binario, pero su enfoque probablemente sea un poco más rápido (podría ahorrar espacio usando una sola llamada a strcat antes de regresar y asignar una var donde ahora se llama). También habría incluido 3/10 y 7/10, pero tal vez sea solo yo.
jimhark
1
Inspirado por esta solución, he creado un código corto (pero totalmente no optimizado). Se puede ampliar fácilmente para cubrir una gama más amplia de fracciones. jsfiddle.net/PdL23/1
Deepak Joy
1
Tenga en cuenta que 1/1000también es muy legible por humanos, pero el algoritmo anterior solo produciría una 1/10aproximación muy burda ; Creo que se pueden hacer mejoras en términos de los cuales denominadores humanamente legible, uno puede escoger de, y / o la adición de <, >, <<, >>prefijos para dar una idea de la tosquedad de la aproximación.
vladr
16

Aquí hay un enlace que explica las matemáticas detrás de la conversión de un decimal a una fracción:

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

Y aquí hay una función de ejemplo sobre cómo hacerlo realmente usando VB (de 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 las búsquedas de Google: convertir decimal a fracción, convertir decimal a código de fracción)

Devinmoore
fuente
2
Tenga en cuenta que este algoritmo toma Ω (m) tiempo cuando f = n / m. Y eso podría ser mucho, incluso si no lo pretendía (considere 0,66666666667).
einpoklum
10

Es posible que desee leer Lo que todo científico informático debería saber sobre la aritmética de coma flotante .

Tendrá que especificar cierta precisión multiplicando por un número grande:

3.141592 * 1000000 = 3141592

entonces puedes hacer una fracción:

3 + (141592 / 1000000)

y reducir vía GCD ...

3 + (17699 / 125000)

pero no hay forma de sacar la fracción deseada . Es posible que desee utilizar siempre fracciones en todo el código en su lugar, ¡solo recuerde reducir las fracciones cuando pueda para evitar el desbordamiento!

nlucaroni
fuente
9

Aquí están las versiones de Perl y Javascript del código VB sugeridas 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";
}

Y el javascript casi 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;
}
mivk
fuente
9

Implementación 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);
    }
}
Tom
fuente
7

El árbol de Stern-Brocot induce una forma bastante natural de aproximar números reales por fracciones con denominadores simples.

Doug McClean
fuente
6

Parte del problema es que muchas fracciones no son fáciles de interpretar como fracciones. Por ejemplo, 0,33 no es 1/3, es 33/100. Pero si recuerda su capacitación en la escuela primaria, entonces hay un proceso para convertir valores decimales en fracciones, sin embargo, es poco probable que le dé lo que desea, ya que la mayoría de las veces los números decimales no se almacenan en 0.33, sino en 0.329999999999998 o algo así.

Hágase un favor y no se moleste con esto, pero si es necesario, puede hacer lo siguiente:

Multiplica el valor original por 10 hasta que elimines la parte fraccionaria. Conserve ese número y utilícelo como divisor. Luego haz una serie de simplificaciones buscando denominadores comunes.

Entonces 0.4 sería 4/10. Luego, buscaría divisores comunes que comiencen con valores bajos, probablemente números primos. Comenzando con 2, verá si 2 divide el numerador y el denominador de manera uniforme al verificar si el piso de la división es el mismo que la división en sí.

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

Entonces, 5 no divide a 2 uniformemente. Entonces marca el siguiente número, digamos 3. Haz esto hasta que llegues a la raíz cuadrada del número más pequeño o por encima de ella.

Después de hacer eso, entonces necesitas

Orion Adrian
fuente
1
Sugeriría usar el algoritmo euclidiano para ese último paso
Graphics Noob
4

"Digamos que tenemos 0.33, necesitamos generar" 1/3 "".

¿Qué precisión espera que tenga la "solución"? 0.33 no es igual a 1/3. ¿Cómo reconoce una respuesta "buena" (fácil de leer)?

Pase lo que pase, un posible algoritmo podría ser:

Si espera encontrar una fracción más cercana en una forma X / Y donde Y es menor que 10, entonces puede recorrer las 9 posibles Y, para cada Y calcular X, y luego seleccionar la más precisa.

Suma
fuente
3

Creo que la mejor manera de hacer esto es convertir primero su valor flotante en una representación ascii. En C ++ puedes usar ostringstream o en C, puedes usar sprintf. Así es como se vería en 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;

Se podría adoptar un enfoque similar en C.

Luego, deberá verificar que la fracción esté en los términos más bajos. Este algoritmo dará una respuesta precisa, es decir, 0,33 daría como resultado "33/100", no "1/3". Sin embargo, 0.4 daría "4/10", que cuando se reduce a los términos más bajos sería "2/5". Puede que esto no sea tan poderoso como la solución de EppStein, pero creo que es más sencillo.

bpm
fuente
8 años después me crucé con su solución, la he probado y está funcionando perfectamente hasta ahora, pero dijo que no es tan poderosa como la solución de EppStein y me pregunto por qué. Dado que su solución es mucho más simple, ¿no debería ser esta la solución elegida? ¿No estamos destinados a hacer el código más simple posible siempre que funcione y sea seguro?
HBatalha
3

Una solución integrada en R:

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

Este utiliza un método fracción continua y tiene opcional cyclesy max.denominatorargumentos para el ajuste de la precisión.

Ben Bolker
fuente
También library(numbers)y contFrac(0.6666); para obtener la salida de la cadena como se desea:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt
2

Tendrá que averiguar qué nivel de error está dispuesto a aceptar. No todas las fracciones decimales se reducirán a una fracción simple. Probablemente elegiría un número fácilmente divisible, como 60, y calcularía cuántos 60 está más cerca del valor, luego simplificaría la fracción.

Mark Bessey
fuente
2

Puede hacer esto en cualquier lenguaje de programación siguiendo los siguientes pasos:

  1. Multiplica y divide por 10 ^ x donde x es la potencia de 10 requerida para asegurarte de que el número no tenga posiciones decimales restantes. Ejemplo: multiplica 0.33 por 10 ^ 2 = 100 para hacer 33 y divídelo por lo mismo para obtener 33/100
  2. Reduzca el numerador y el denominador de la fracción resultante por factorización, hasta que ya no pueda obtener números enteros del resultado.
  3. La fracción reducida resultante debería ser su respuesta.

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

Entonces, eso se puede leer como '1 parte de 5'

Pascal
fuente
2

Una solución es almacenar todos los números como números racionales en primer lugar. Hay bibliotecas para aritmética de números racionales (por ejemplo, GMP ). Si usa un lenguaje OO, es posible que pueda usar una biblioteca de clases de números racionales para reemplazar su clase de números.

Los programas financieros, entre otros, utilizarían una solución de este tipo para poder realizar cálculos exactos y preservar la precisión que se puede perder con un simple flotador.

Por supuesto, será mucho más lento, por lo que puede que no sea práctico para usted. Depende de la cantidad de cálculos que necesite hacer y de la importancia que tenga la precisión para usted.

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

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

Digamos que tenemos 0.33, necesitamos generar "1/3". Si tenemos "0.4", necesitamos generar "2/5".

Es incorrecto en el caso común, debido a 1/3 = 0.3333333 = 0. (3) Además, es imposible averiguar a partir de las soluciones sugeridas anteriormente si el decimal se puede convertir a fracción con precisión definida, porque la salida siempre es fracción.

PERO, sugiero mi función integral con muchas opciones basadas en la idea de series geométricas infinitas , específicamente en la fórmula:

ingrese la descripción de la imagen aquí

Al principio, esta función intenta encontrar un período de fracción en la representación de cadena. Después se aplica la fórmula descrita anteriormente.

El código de números racionales se toma prestado de la implementación de números racionales de Stephen M. McKamey en C #. Espero que no sea muy difícil migrar mi código a otros idiomas.

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

Hay algunos ejemplos 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;

Su caso con el recorte de piezas cero de la pieza correcta:

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;

Demostración 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.

Redondeo al final:

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

El caso más interesante:

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.

Otras pruebas y códigos que todos pueden encontrar en mi biblioteca MathFunctions en github .

Ivan Kochurkin
fuente
2

Ruby ya tiene una solución incorporada:

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

En Rails, los atributos numéricos de ActiveRecord también se pueden convertir:

product.size = 0.33
product.size.to_r.to_s # => "33/100"
Josh W Lewis
fuente
2

Responda en C ++, asumiendo que tiene una clase 'BigInt', que puede almacenar enteros de tamaño ilimitado.

En su lugar, puede usar 'unsigned long long', pero solo funcionará para ciertos 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
}

Por cierto, GetRational (0.0) devolverá "+0/1", por lo que es posible que desee manejar este caso por separado.

PD: He estado usando este código en mi propia clase 'RationalNum' durante varios años y se ha probado a fondo.

barak manos
fuente
Su ejemplo parece descomponerse en valores como 1.333333 ... entra en un ciclo muy largo tratando de encontrar el valor y no parece funcionar ... funciona bien con otros valores simples como 1.25
Adamski
@Adamski: Gracias. El período de "convergencia" del whilebucle está limitado por el tamaño de double, que normalmente es de 64 bits. Por tanto, no depende del valor inicial de la entrada ( val). La GCDfunción, sin embargo, no depende de este valor, aunque por lo general converge a una solución bastante rápido. ¿Es posible que no hayas implementado esta función correctamente?
barak manos
@Adamski: Además, como mencioné al principio de la respuesta, si está usando en unsigned long longlugar de BigInt, entonces no necesariamente dará el resultado correcto para cada valor de entrada ... Pero incluso en ese escenario, el código no es se supone que "entra en un bucle muy largo".
barak manos
Ah, sí, eso es totalmente posible, la función GCD que estaba usando es parte de la clase BigInteger de la biblioteca Juce. ¡Gracias por la información!
Adamski
@Adamski: Entonces no tiene sentido que la GCDfunción no esté implementada correctamente. ¿Ha comprobado si el código se ejecuta durante mucho tiempo durante el whilebucle o después? Verificaré el valor de 1.33333 para ver qué hay detrás de esto. Gracias.
barak manos
2

Este algoritmo de Ian Richards / John Kennedy no solo devuelve buenas fracciones, sino que también funciona muy bien en términos de velocidad. Este es el código C # tomado de esta respuesta por mí.

Puede manejar todos los doublevalores excepto los valores especiales como NaN y +/- infinito, que tendrá que agregar si es necesario.

Devuelve un new Fraction(numerator, denominator). Reemplace por su propio tipo.

Para obtener más valores de ejemplo y una comparación con otros algoritmos, vaya aquí

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 ejemplo devueltos 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         
Kay Zed
fuente
1

Vas a tener dos problemas básicos que dificultarán esto:

1) El punto flotante no es una representación exacta, lo que significa que si tiene una fracción de "x / y" que da como resultado un valor de "z", su algoritmo de fracción puede devolver un resultado distinto de "x / y".

2) Hay infinitos muchos más números irracionales que racionales. Un número racional es aquel que se puede representar como una fracción. Ser irracionales los que no pueden.

Sin embargo, de una manera barata, dado que el punto flotante tiene una precisión límite, siempre puedes representarlo como alguna forma de facción. (Yo creo que...)

Torlack
fuente
4
Un flotador (o doble) es una fracción. Su denominador es una potencia de 2. Es por eso que no pueden representar exactamente algunos números racionales.
erickson
1

Completó el código anterior y lo convirtió a 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";
    }
João Lopes
fuente
Gracias, usé esto para Delphi, más fácil de portar que todas esas cosas rizadas
Peter Turner
1

Aquí hay una implementación rápida y sucia en javascript que utiliza un enfoque de fuerza bruta. Nada optimizado, funciona dentro de un rango predefinido de fracciones: 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));

Esto está inspirado en el enfoque utilizado por JPS.

Deepak Joy
fuente
0

Como muchas personas han dicho, realmente no se puede convertir un punto flotante en una fracción (a menos que sea extremadamente exacto como 0,25). Por supuesto, puede crear algún tipo de búsqueda para una gran variedad de fracciones y usar algún tipo de lógica difusa para producir el resultado que está buscando. Nuevamente, esto no sería exacto y necesitaría definir un límite inferior de cuán grande desea que sea el denominador.

.32 <x <.34 = 1/3 o algo así.

Tim
fuente
0

Aquí está la implementación para 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)
Valodzka
fuente
0

Me encontré con una solución Haskell especialmente elegante que utiliza un anamorfismo. Depende del paquete de esquemas de recursividad .

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

Si prueba esto en ghci, ¡realmente funciona!

λ:> approximate pi 2
22 % 7

fuente