Come convertire i float in frazioni leggibili dall'uomo?


103

Diciamo che abbiamo 0.33, dobbiamo produrre 1/3.
Se lo abbiamo 0.4, dobbiamo produrre 2/5.

L'idea è di renderlo leggibile dall'uomo per far capire all'utente " x parti su y " come un modo migliore per comprendere i dati.

So che le percentuali sono un buon sostituto ma mi chiedevo se ci fosse un modo semplice per farlo?


L' esempio .33=> "1/3"mi riguarda; Mi aspetterei .33=> "33/100". Presumo che tu intendessi .33...ovviamente, ma espone un problema con la domanda: prima di poter stabilire un algoritmo, dobbiamo decidere il comportamento previsto. La risposta di @ Debilski in Python usa il .limit_denominator()cui valore predefinito è un denominatore massimo di 10 ^ 7; probabilmente una buona impostazione predefinita, in pratica, ma questo può ancora presentare bug, se non stai attento, e fa ritorno "33/100"nel .33caso.
dimo414

Con tutto ciò non linguistici specifci funzionalità sono disponibili. Non è chiaro cosa stai chiedendo, se davvero non è una semplice contraddizione in termini.
Marchese di Lorne

Risposte:


70

Ho scoperto che l' approssimazione razionale di ricerca di David Eppstein al codice C di un numero reale è esattamente quello che stai chiedendo. È basato sulla teoria delle frazioni continue ed è molto veloce e abbastanza compatto.

Ho usato versioni di questo personalizzato per limiti numeratori e denominatori specifici.

/*
** 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
Per quelli di voi che cercano una soluzione in Ruby, siamo fortunati! Christopher Lord ha implementato l'algoritmo di cui sopra in una gemma Ruby. Vedi christopher.lord.ac/fractions-in-ruby e rubygems.org/gems/fraction
shedd

6
Tieni presente che ci sono alcuni casi limite che questo codice non gestisce molto bene: quando dato -1.3333333 con un denominatore massimo di 4 restituisce 4 / -3 con un errore di 3.333333e-08 e -5/4 con un errore = -8.333330e-02, che è corretto. Ma quando viene fornito -1.33333337 con lo stesso denominatore massimo, diventa 12121211 / -9090908 con un errore di errore = 4.218847e-15 e -4/3 con un errore di -3.666667e-08, che non è corretto. Questo è un problema in particolare quando si presenta l'algoritmo con numeri in virgola mobile calcolati come -4/3, che restituiscono risultati errati come questi.
edsko

27

Da Python 2.6 in poi c'è il fractionsmodulo.

(Citando dalla documentazione.)

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


2
@Debilski quali tag language agnostice OP algorithmsoddisfa la tua risposta?
vladr

2
@vladr Bene, dato che ho scritto questa risposta quasi 6 anni fa (e più di un anno dopo che la domanda era stata posta), immagino di non sapere più quale fosse il mio ragionamento all'epoca. Molto probabilmente mi riferivo a questo commento: stackoverflow.com/questions/95727/… OTOH Potrebbe anche essere che questa risposta sia stata fusa da un'altra domanda. Chi può dirlo dopo tutti quegli anni ...
Debilski

Potresti aggiungere alcune frasi sull'algoritmo utilizzato dal modulo frazioni (e forse aggiornare la tua risposta per Python3).
einpoklum

21

Se l'output deve dare a un lettore umano una rapida impressione dell'ordine del risultato, non ha senso restituire qualcosa come "113/211", quindi l'output dovrebbe limitarsi a utilizzare numeri a una cifra (e forse 1 / 10 e 9/10). Se è così, puoi osservare che ci sono solo 27 diverse frazioni.

Poiché la matematica sottostante per la generazione dell'output non cambierà mai, una soluzione potrebbe essere quella di codificare semplicemente un albero di ricerca binario, in modo che la funzione esegua al massimo i confronti log (27) ~ = 4 3/4. Ecco una versione C testata del codice

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
Questo è il tipo di pensiero laterale di cui abbiamo bisogno di più! Ottimo suggerimento.
edsko

1
È un modo un po 'brutto ma molto veloce e pratico
Bosak

1
Questo è un approccio interessante e meravigliosamente semplice. Per risparmiare spazio potresti invece cercare binario un array o creare un albero binario, ma il tuo approccio è probabilmente un po 'più veloce (potresti risparmiare spazio usando una singola chiamata a strcat prima di tornare e assegnare una var dove è ora chiamata). Inoltre avrei incluso 3/10 e 7/10, ma forse sono solo io.
jimhark

1
Ispirato da questa soluzione, ho creato un codice breve (ma totalmente non ottimizzato). Può essere facilmente esteso per coprire una gamma più ampia di frazioni. jsfiddle.net/PdL23/1
Deepak Joy

1
Si noti che 1/1000è anche molto leggibile umanamente, ma l'algoritmo di cui sopra produrrebbe solo 1/10un'approssimazione molto grossolana ; Ritengo che possono essere apportati miglioramenti in termini di cui denominatori umanamente leggibili si può scegliere, e / o l'aggiunta di <, >, <<, >>prefissi per dare un'idea della ruvidezza della approssimazione.
vladr

16

Ecco un collegamento che spiega la matematica alla base della conversione di un decimale in una frazione:

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

Ed ecco una funzione di esempio su come farlo effettivamente utilizzando VB (da 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

(Dalle ricerche Google: converti decimale in frazione, converti decimale in codice frazionario)


2
Nota che questo algoritmo impiega Ω (m) tempo quando f = n / m. E potrebbe essere molto, anche se non volevi che fosse (considera 0.66666666667).
einpoklum

10

Potresti leggere Quello che ogni informatico dovrebbe sapere sull'aritmetica in virgola mobile .

Dovrai specificare una certa precisione moltiplicando per un numero elevato:

3.141592 * 1000000 = 3141592

quindi puoi fare una frazione:

3 + (141592 / 1000000)

e ridurre tramite GCD ...

3 + (17699 / 125000)

ma non c'è modo per ottenere la frazione prevista . Potresti invece voler usare sempre le frazioni in tutto il codice: ricorda di ridurre le frazioni quando puoi per evitare l'overflow!


9

Ecco le versioni Perl e Javascript del codice VB suggerito da 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 il javascript quasi identico:

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

Implementazione di 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 del problema è che così tante frazioni in realtà non sono facilmente interpretabili come frazioni. Ad esempio 0,33 non è 1/3, è 33/100. Ma se ricordi la tua formazione scolastica elementare, allora c'è un processo di conversione dei valori decimali in frazioni, tuttavia è improbabile che ti dia quello che vuoi poiché la maggior parte delle volte i numeri decimali non sono memorizzati a 0,33, ma 0,329999999999998 o qualcosa del genere.

Fatti un favore e non preoccuparti di questo, ma se ne hai bisogno puoi fare quanto segue:

Moltiplica il valore originale per 10 fino a rimuovere la parte frazionaria. Mantieni quel numero e usalo come divisore. Quindi fai una serie di semplificazioni cercando denominatori comuni.

Quindi 0,4 sarebbe 4/10. Dovresti quindi cercare i divisori comuni che iniziano con valori bassi, probabilmente numeri primi. A partire da 2, vedresti se 2 divide equamente sia il numeratore che il denominatore controllando se il piano di divisione è lo stesso della divisione stessa.

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

Quindi 5 non divide 2 in modo uniforme. Quindi controlli il numero successivo, ad esempio 3. Lo fai finché non raggiungi la radice quadrata del numero più piccolo o al di sopra di essa.

Dopo averlo fatto, allora hai bisogno


1
Suggerirei di utilizzare l'algoritmo euclideo per l'ultimo passaggio
Graphics Noob


4

"Diciamo che abbiamo 0,33, dobbiamo produrre" 1/3 "."

Quale precisione ti aspetti che abbia la "soluzione"? 0,33 non è uguale a 1/3. Come si riconosce una risposta "buona" (facile da leggere)?

Non importa cosa, un possibile algoritmo potrebbe essere:

Se ti aspetti di trovare una frazione più vicina in una forma X / Y dove Y è inferiore a 10, puoi eseguire il ciclo attraverso tutti i 9 Y possibili, per ogni Y calcolare X, quindi selezionare quello più accurato.


3

Penso che il modo migliore per farlo sia prima convertire il tuo valore float in una rappresentazione ASCII. In C ++ potresti usare ostringstream o in C, potresti usare sprintf. Ecco come apparirebbe in 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;

Un approccio simile potrebbe essere adottato nella scala C.

Successivamente dovresti controllare che la frazione sia nei termini più bassi. Questo algoritmo darà una risposta precisa, ovvero 0,33 produrrà "33/100", non "1/3". Tuttavia, 0,4 darebbe "4/10", che se ridotto ai termini più bassi sarebbe "2/5". Potrebbe non essere potente come la soluzione di EppStein, ma credo che sia più semplice.


8 anni dopo ho trovato la tua soluzione, l'ho testata e finora funziona perfettamente, ma hai detto che non è potente come la soluzione di EppStein e mi chiedo perché. Dal momento che la tua soluzione è molto più semplice non dovrebbe essere questa la soluzione scelta, non dovremmo fare il codice più semplice possibile fintanto che funziona ed è sicuro?
HBatalha

3

Una soluzione integrata in R:

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

Questo utilizza un metodo di frazione continua e ha argomenti cyclese facoltativi max.denominatorper la regolazione della precisione.


Inoltre library(numbers)e contFrac(0.6666); per ottenere l'output della stringa come desiderato:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt

2

Dovrai capire quale livello di errore sei disposto ad accettare. Non tutte le frazioni decimali si ridurranno a una semplice frazione. Probabilmente sceglierei un numero facilmente divisibile, come 60, e capire quanti 60esimi sono più vicini al valore, quindi semplificherei la frazione.


2

Puoi farlo in qualsiasi linguaggio di programmazione utilizzando i seguenti passaggi:

  1. Moltiplicare e dividere per 10 ^ x dove x è la potenza di 10 richiesta per assicurarsi che il numero non abbia posizioni decimali rimanenti. Esempio: moltiplicare 0,33 per 10 ^ 2 = 100 per ottenere 33 e dividerlo per lo stesso per ottenere 33/100
  2. Riduci il numeratore e il denominatore della frazione risultante per fattorizzazione, finché non puoi più ottenere numeri interi dal risultato.
  3. La frazione ridotta risultante dovrebbe essere la tua risposta.

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

Quindi, può essere letto come "1 parte su 5"


2

Una soluzione è memorizzare tutti i numeri come numeri razionali in primo luogo. Esistono librerie per l'aritmetica dei numeri razionali (ad esempio GMP ). Se usi un linguaggio OO potresti essere in grado di usare semplicemente una libreria di classi numeriche razionale per sostituire la tua classe numerica.

I programmi finanziari, tra gli altri, utilizzerebbero tale soluzione per essere in grado di eseguire calcoli esatti e preservare la precisione che potrebbe essere persa utilizzando un semplice float.

Ovviamente sarà molto più lento, quindi potrebbe non essere pratico per te. Dipende da quanti calcoli devi fare e quanto è importante per te la precisione.

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

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

2

Diciamo che abbiamo 0,33, dobbiamo produrre "1/3". Se abbiamo "0.4", dobbiamo produrre "2/5".

È sbagliato nel caso comune, a causa di 1/3 = 0,3333333 = 0. (3) Inoltre, è impossibile scoprire dalle soluzioni suggerite sopra se il decimale può essere convertito in frazione con precisione definita, perché l'output è sempre frazione.

MA, suggerisco la mia funzione completa con molte opzioni basate sull'idea di serie geometriche infinite , in particolare sulla formula:

inserisci qui la descrizione dell'immagine

All'inizio questa funzione sta cercando di trovare il periodo di frazione nella rappresentazione di stringa. Dopo di che viene applicata la formula sopra descritta.

Il codice dei numeri razionali è preso in prestito dall'implementazione dei numeri razionali di Stephen M. McKamey in C #. Spero che non sia molto difficile portare il mio codice su altre lingue.

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

Ci sono alcuni esempi di utilizzo:

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;

Il tuo caso con il taglio della parte zero parte destra:

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;

Dimostrazione periodo minimo:

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.

Arrotondamento alla fine:

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

Il caso più 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.

Altri test e codice che tutti possono trovare nella mia libreria MathFunctions su GitHub .


2

Ruby ha già una soluzione integrata:

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

In Rails, anche gli attributi numerici di ActiveRecord possono essere convertiti:

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

2

Rispondi in C ++, assumendo che tu abbia una classe "BigInt", che può memorizzare numeri interi di dimensioni illimitate.

Puoi invece usare "unsigned long long", ma funzionerà solo per determinati valori.

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) restituirà "+0/1", quindi potresti voler gestire questo caso separatamente.

PS: Uso questo codice nella mia classe "RationalNum" da diversi anni ed è stato testato a fondo.


Il tuo esempio sembra scomporre su valori come 1.333333 .. entra in un ciclo molto lungo cercando di trovare il valore e non sembra funzionare ... funziona bene con altri valori semplici come 1.25
Adamski

@ Adamski: grazie. Il periodo di "convergenza" del whileciclo è delimitato dalla dimensione di double, che in genere è di 64 bit. Quindi non dipende dal valore iniziale di input ( val). La GCDfunzione, tuttavia, dipende da questo valore, sebbene di solito converga a una soluzione piuttosto rapida. È possibile che tu non abbia implementato correttamente questa funzione?
barak manos

@Adamski: Inoltre, come ho detto all'inizio della risposta, se stai usando al unsigned long longposto di BigInt, allora non produrrà necessariamente il risultato corretto per ogni valore di input ... Ma anche in questo scenario, il codice non lo è dovrebbe "entrare in un ciclo molto lungo".
barak manos

Ah ok sì, è del tutto possibile, la funzione GCD che stavo usando fa parte della classe BigInteger della libreria Juce. Grazie per l'informazione!
Adamski

@ Adamski: Quindi non ha senso che la GCDfunzione non sia implementata correttamente. Hai controllato se il codice viene eseguito a lungo durante il whileciclo o dopo di esso? Controllerò il valore di 1.33333, per vedere cosa c'è dietro. Grazie.
barak manos

2

Questo algoritmo di Ian Richards / John Kennedy non solo restituisce belle frazioni, ma si comporta anche molto bene in termini di velocità. Questo è il codice C # come preso da questa risposta da me.

Può gestire tutti i doublevalori tranne i valori speciali come NaN e +/- infinito, che dovrai aggiungere se necessario.

Restituisce un file new Fraction(numerator, denominator). Sostituisci con il tuo tipo.

Per ulteriori valori di esempio e un confronto con altri algoritmi, vai qui

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

Valori di esempio restituiti da questo 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

Avrai due problemi di base che renderanno tutto più difficile:

1) La virgola mobile non è una rappresentazione esatta, il che significa che se hai una frazione di "x / y" che risulta in un valore di "z", il tuo algoritmo di frazione potrebbe restituire un risultato diverso da "x / y".

2) Ci sono infiniti molti più numeri irrazionali che razionali. Un numero razionale è quello che può essere rappresentato come una frazione. Essendo irrazionali quelli che non possono.

Tuttavia, in un modo poco costoso, poiché il punto mobile ha un limite di precisione, puoi sempre rappresentarlo come una forma di fazione. (Penso...)


4
Un float (o double) è una frazione. Il suo denominatore è una potenza di 2. Ecco perché non possono rappresentare esattamente alcuni numeri razionali.
erickson

1

Completato il codice sopra e convertito in 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";
    }

Grazie, l'ho usato per Delphi, più facile da portare rispetto a tutta quella roba riccia
Peter Turner

1

Ecco un'implementazione rapida e sporca in JavaScript che utilizza un approccio di forza bruta. Per niente ottimizzato, funziona all'interno di un intervallo predefinito di frazioni: 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));

Questo è ispirato dall'approccio utilizzato da JPS.


0

Come molte persone hanno affermato che non puoi davvero riconvertire un punto mobile in una frazione (a meno che non sia estremamente esatto come .25). Ovviamente potresti creare un qualche tipo di ricerca per una vasta gamma di frazioni e utilizzare una sorta di logica fuzzy per produrre il risultato che stai cercando. Anche in questo caso non sarebbe esatto e dovresti definire un limite inferiore di quanto grande vuoi che vada il denominatore.

.32 <x <.34 = 1/3 o qualcosa del genere.



0

Mi sono imbattuto in una soluzione Haskell particolarmente elegante che utilizza un anamorfismo. Dipende dal pacchetto schemi di ricorsione .

{-# 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 lo provi in ​​ghci, funziona davvero!

λ:> approximate pi 2
22 % 7
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.