Jak przekonwertować elementy zmiennoprzecinkowe na ułamki czytelne dla człowieka?


105

Powiedzmy, że mamy 0.33, musimy wydrukować 1/3.
Jeśli mamy 0.4, musimy wydrukować 2/5.

Chodzi o to, aby uczynić ją czytelną dla człowieka, aby użytkownik zrozumiał „ x części z y ” jako lepszy sposób rozumienia danych.

Wiem, że wartości procentowe są dobrym zamiennikiem, ale zastanawiałem się, czy istnieje prosty sposób na zrobienie tego?


Przykład .33=> "1/3"dotyczy mnie; Spodziewałbym się .33=> "33/100". Zakładam, że miałeś na myśli .33...oczywiście, ale ujawnia to problem z pytaniem - zanim będziemy mogli zdecydować się na algorytm, musimy zdecydować o oczekiwanym zachowaniu. Odpowiedź @ Debilskiego w Pythonie używa .limit_denominator()domyślnego maksymalnego mianownika 10 ^ 7; chyba dobry domyślny w praktyce, ale to nadal może wprowadzać błędy, jeśli nie jesteś ostrożny, i robi zwrot "33/100"w .33sprawie.
dimo414

Niezależnie od dostępnych funkcji specyficznych dla języka . Nie jest jasne, o co pytasz, jeśli rzeczywiście nie jest to zwykła sprzeczność terminów.
Markiz Lorne

Odpowiedzi:


70

Znalazłem racjonalne przybliżenie Davida Eppsteina do danego kodu C liczby rzeczywistej, które jest dokładnie tym, o co prosisz. Opiera się na teorii ułamków ciągłych i jest bardzo szybki i dość zwarty.

Użyłem wersji tego dostosowanych do określonych limitów licznika i mianownika.

/*
** 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
Dla tych z Was, którzy szukają rozwiązania w Rubim, mamy szczęście! Christopher Lord zaimplementował powyższy algorytm w perełce Ruby. Zobacz christopher.lord.ac/fractions-in-ruby i rubygems.org/gems/fraction
shedd

6
Należy pamiętać, że istnieją przypadki skrajne, w których ten kod nie radzi sobie zbyt dobrze: po podaniu wartości -1,3333333 z maksymalnym mianownikiem równym 4 zwraca 4 / -3 z błędem 3.333333e-08 i -5/4 z błędem = -8,333330e-02, co jest poprawne. Ale gdy podano -1,33333337 z tym samym maksymalnym mianownikiem, zmienia się 12121211 / -9090908 z błędem = 4,218847e-15 i -4/3 z błędem -3,666667e-08, co nie jest poprawne. Jest to szczególnie problematyczne przy prezentowaniu algorytmu z obliczonymi liczbami zmiennoprzecinkowymi, takimi jak -4/3, co daje takie niepoprawne wyniki.
edsko

27

Począwszy od Pythona 2.6 jest fractionsmoduł.

(Cytując z dokumentów).

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

>>> from math import pi, cos
>>> Fraction.from_float(cos(pi/3))
Fraction(4503599627370497, 9007199254740992)
>>> Fraction.from_float(cos(pi/3)).limit_denominator()
Fraction(1, 2)

6
Piro

2
@Debilski, który z PO language agnostici algorithmtagów odpowiada Twojej odpowiedzi?
vladr

2
@vladr Cóż, biorąc pod uwagę, że napisałem tę odpowiedź prawie 6 lat temu (i ponad rok po zadaniu pytania), myślę, że nie wiem już, jakie było moje rozumowanie wtedy. Najprawdopodobniej odnosiłem się do tego komentarza: stackoverflow.com/questions/95727/ ... OTOH Możliwe, że ta odpowiedź została połączona z innym pytaniem. Kto
wie

Możesz dodać kilka zdań na temat algorytmu używanego przez moduł ułamków (i być może zaktualizować swoją odpowiedź dla Python3).
einpoklum

21

Jeśli wynik ma dać czytelnikowi szybkie wrażenie kolejności wyniku, nie ma sensu zwracać czegoś takiego jak "113/211", więc wyjście powinno ograniczać się do używania liczb jednocyfrowych (i może 1 / 10 i 9/10). Jeśli tak, możesz zauważyć, że istnieje tylko 27 różnych frakcji.

Ponieważ matematyka będąca podstawą generowania wyniku nigdy się nie zmieni, rozwiązaniem może być po prostu zaprogramowanie na stałe drzewa wyszukiwania binarnego, tak aby funkcja wykonywała co najwyżej porównania log (27) ~ = 4 3/4. Oto przetestowana wersja kodu w C.

char *userTextForDouble(double d, char *rval)
{
    if (d == 0.0)
        return "0";

    // TODO: negative numbers:if (d < 0.0)...
    if (d >= 1.0)
        sprintf(rval, "%.0f ", floor(d));
    d = d-floor(d); // now only the fractional part is left

    if (d == 0.0)
        return rval;

    if( d < 0.47 )
    {
        if( d < 0.25 )
        {
            if( d < 0.16 )
            {
                if( d < 0.12 ) // Note: fixed from .13
                {
                    if( d < 0.11 )
                        strcat(rval, "1/10"); // .1
                    else
                        strcat(rval, "1/9"); // .1111....
                }
                else // d >= .12
                {
                    if( d < 0.14 )
                        strcat(rval, "1/8"); // .125
                    else
                        strcat(rval, "1/7"); // .1428...
                }
            }
            else // d >= .16
            {
                if( d < 0.19 )
                {
                    strcat(rval, "1/6"); // .1666...
                }
                else // d > .19
                {
                    if( d < 0.22 )
                        strcat(rval, "1/5"); // .2
                    else
                        strcat(rval, "2/9"); // .2222...
                }
            }
        }
        else // d >= .25
        {
            if( d < 0.37 ) // Note: fixed from .38
            {
                if( d < 0.28 ) // Note: fixed from .29
                {
                    strcat(rval, "1/4"); // .25
                }
                else // d >=.28
                {
                    if( d < 0.31 )
                        strcat(rval, "2/7"); // .2857...
                    else
                        strcat(rval, "1/3"); // .3333...
                }
            }
            else // d >= .37
            {
                if( d < 0.42 ) // Note: fixed from .43
                {
                    if( d < 0.40 )
                        strcat(rval, "3/8"); // .375
                    else
                        strcat(rval, "2/5"); // .4
                }
                else // d >= .42
                {
                    if( d < 0.44 )
                        strcat(rval, "3/7"); // .4285...
                    else
                        strcat(rval, "4/9"); // .4444...
                }
            }
        }
    }
    else
    {
        if( d < 0.71 )
        {
            if( d < 0.60 )
            {
                if( d < 0.55 ) // Note: fixed from .56
                {
                    strcat(rval, "1/2"); // .5
                }
                else // d >= .55
                {
                    if( d < 0.57 )
                        strcat(rval, "5/9"); // .5555...
                    else
                        strcat(rval, "4/7"); // .5714
                }
            }
            else // d >= .6
            {
                if( d < 0.62 ) // Note: Fixed from .63
                {
                    strcat(rval, "3/5"); // .6
                }
                else // d >= .62
                {
                    if( d < 0.66 )
                        strcat(rval, "5/8"); // .625
                    else
                        strcat(rval, "2/3"); // .6666...
                }
            }
        }
        else
        {
            if( d < 0.80 )
            {
                if( d < 0.74 )
                {
                    strcat(rval, "5/7"); // .7142...
                }
                else // d >= .74
                {
                    if(d < 0.77 ) // Note: fixed from .78
                        strcat(rval, "3/4"); // .75
                    else
                        strcat(rval, "7/9"); // .7777...
                }
            }
            else // d >= .8
            {
                if( d < 0.85 ) // Note: fixed from .86
                {
                    if( d < 0.83 )
                        strcat(rval, "4/5"); // .8
                    else
                        strcat(rval, "5/6"); // .8333...
                }
                else // d >= .85
                {
                    if( d < 0.87 ) // Note: fixed from .88
                    {
                        strcat(rval, "6/7"); // .8571
                    }
                    else // d >= .87
                    {
                        if( d < 0.88 ) // Note: fixed from .89
                        {
                            strcat(rval, "7/8"); // .875
                        }
                        else // d >= .88
                        {
                            if( d < 0.90 )
                                strcat(rval, "8/9"); // .8888...
                            else
                                strcat(rval, "9/10"); // .9
                        }
                    }
                }
            }
        }
    }

    return rval;
}

3
To jest rodzaj myślenia lateralnego, którego potrzebujemy więcej! Doskonała sugestia.
edsko

1
To trochę brzydki, ale bardzo szybki i praktyczny sposób
Bosak

1
To ciekawe podejście, które jest cudownie proste. Aby zaoszczędzić miejsce, możesz zamiast tego przeszukać binarnie tablicę lub utworzyć drzewo binarne, ale twoje podejście jest prawdopodobnie trochę szybsze (możesz zaoszczędzić miejsce, używając pojedynczego wywołania strcat przed powrotem i przypisując zmienną, gdzie jest teraz nazywana). Dodałbym też 3/10 i 7/10, ale może to tylko ja.
jimhark,

1
Zainspirowany tym rozwiązaniem stworzyłem krótki (ale całkowicie niezoptymalizowany) kod. Można go łatwo rozszerzyć, aby objąć większy zakres frakcji. jsfiddle.net/PdL23/1
Deepak Joy,

1
Zauważ, że 1/1000jest to również bardzo czytelne dla człowieka, ale powyższy algorytm dałby tylko bardzo zgrubne 1/10przybliżenie; Wierzę, że można wprowadzić usprawnienia w zakresie której po ludzku czytelne mianowniki można wybrać z, i / lub dodatek <, >, <<, >>przedrostki, aby dać wyobrażenie o chropowatość zbliżenia.
vladr

16

Oto link wyjaśniający matematykę związaną z zamianą ułamka dziesiętnego na ułamek:

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

A oto przykładowa funkcja pokazująca, jak to zrobić za pomocą VB (z 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

(Z wyszukiwania w Google: zamień ułamek dziesiętny na ułamek, zamień kod dziesiętny na ułamkowy)


2
Zauważ, że ten algorytm wymaga Ω (m) czasu, gdy f = n / m. A to może być dużo, nawet jeśli tego nie zamierzałeś (rozważ 0.66666666667).
einpoklum

10

Możesz przeczytać Co każdy informatyk powinien wiedzieć o arytmetyce zmiennoprzecinkowej .

Będziesz musiał określić pewną precyzję, mnożąc przez dużą liczbę:

3.141592 * 1000000 = 3141592

wtedy możesz zrobić ułamek:

3 + (141592 / 1000000)

i zmniejsz przez GCD ...

3 + (17699 / 125000)

ale nie ma sposobu, aby usunąć zamierzony ułamek. Zamiast tego możesz zawsze chcieć używać ułamków w całym kodzie - pamiętaj tylko, aby zmniejszyć ułamki, kiedy możesz, aby uniknąć przepełnienia!


9

Oto wersje kodu VB w języku Perl i Javascript sugerowane przez 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";
}

I prawie identyczny javascript:

function dec2frac(d) {

    var df = 1;
    var top = 1;
    var bot = 1;

    while (df != d) {
        if (df < d) {
            top += 1;
        }
        else {
            bot += 1;
            top = parseInt(d * bot);
        }
        df = top / bot;
    }
    return top + '/' + bot;
}

9

Wdrożenie 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

Po części problem polega na tym, że tak wielu ułamków nie można łatwo zinterpretować jako ułamków. Np. 0,33 to nie 1/3, to 33/100. Ale jeśli pamiętasz swoje szkolenie w szkole podstawowej, istnieje proces zamiany wartości dziesiętnych na ułamki, jednak jest mało prawdopodobne, aby dało ci to, czego chcesz, ponieważ przez większość czasu liczby dziesiętne nie są przechowywane jako 0,33, ale 0,329999999999998 lub inne.

Zrób sobie przysługę i nie przejmuj się tym, ale jeśli potrzebujesz, możesz wykonać następujące czynności:

Pomnóż oryginalną wartość przez 10, aż usuniesz część ułamkową. Zachowaj tę liczbę i użyj jej jako dzielnika. Następnie wykonaj serię uproszczeń, szukając wspólnych mianowników.

Więc 0,4 byłoby 4/10. Następnie szukałbyś wspólnych dzielników zaczynających się od małych wartości, prawdopodobnie liczb pierwszych. Zaczynając od 2, zobaczysz, czy 2 dzieli równo licznik i mianownik, sprawdzając, czy dolna granica dzielenia jest taka sama jak sama dzielenie.

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

Więc 5 nie dzieli równo 2. Więc sprawdzasz następną liczbę, powiedz 3. Robisz to, aż trafisz na pierwiastek kwadratowy z mniejszej liczby lub powyżej niego.

Po wykonaniu tej czynności potrzebujesz


1
Sugerowałbym użycie algorytmu euklidesowego w tym ostatnim kroku
Grafika Noob


4

"Powiedzmy, że mamy 0,33, musimy wypisać" 1/3 "."

Jaką precyzję oczekuje się od „rozwiązania”? 0,33 nie jest równe 1/3. Jak rozpoznajesz „dobrą” (łatwą do odczytania) odpowiedź?

Bez względu na wszystko, możliwym algorytmem może być:

Jeśli spodziewasz się znaleźć najbliższy ułamek w postaci X / Y, w której Y jest mniejsze niż 10, możesz zapętlić wszystkie 9 możliwych Y, dla każdego Y obliczyć X, a następnie wybrać najbardziej dokładny.


3

Myślę, że najlepszym sposobem na to jest najpierw przekonwertowanie wartości zmiennoprzecinkowej na reprezentację ascii. W C ++ możesz użyć ostringstream lub w C możesz użyć sprintf. Oto, jak by to wyglądało w 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;

Podobne podejście można zastosować w przypadku prostego C.

Następnie musisz sprawdzić, czy ułamek jest najniższy. Ten algorytm da dokładną odpowiedź, tj. 0,33 da wynik „33/100”, a nie „1/3”. Jednak 0,4 dałoby „4/10”, co po zredukowaniu do najniższych wartości to „2/5”. To może nie być tak potężne jak rozwiązanie EppStein, ale uważam, że jest to prostsze.


8 lat później trafiłem na twoje rozwiązanie, przetestowałem i jak do tej pory działa idealnie, ale powiedziałeś, że nie jest tak wydajne jak rozwiązanie EppStein i zastanawiam się, dlaczego. Ponieważ twoje rozwiązanie jest znacznie prostsze, czy nie powinno to być rozwiązaniem z wyboru, czy nie mamy zamiaru robić najprostszego możliwego kodu, o ile działa i jest bezpieczny?
HBatalha

3

Wbudowane rozwiązanie w R:

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

Używa metody ciągłego ułamka i ma opcjonalne cyclesi max.denominatorargumenty do dostosowywania precyzji.


Również library(numbers)i contFrac(0.6666); aby uzyskać żądany łańcuch wyjściowy:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt

2

Musisz dowiedzieć się, jaki poziom błędu chcesz zaakceptować. Nie wszystkie ułamki dziesiętne sprowadzą się do ułamka prostego. Prawdopodobnie wybrałbym łatwo podzielną liczbę, na przykład 60, i obliczyłbym, ile sześćdziesiątych jest najbliżej wartości, a następnie uprościłbym ułamek.


2

Możesz to zrobić w dowolnym języku programowania, wykonując następujące czynności:

  1. Pomnóż i podziel przez 10 ^ x, gdzie x to potęga 10 potrzebna do upewnienia się, że liczba nie ma pozostałych miejsc po przecinku. Przykład: Pomnóż 0,33 przez 10 ^ 2 = 100, aby uzyskać 33 i podziel przez to samo, aby uzyskać 33/100
  2. Zmniejsz licznik i mianownik otrzymanego ułamka przez faktoryzację, aż nie będziesz już mógł otrzymać liczb całkowitych z wyniku.
  3. Wynikowy ułamek zredukowany powinien być twoją odpowiedzią.

Przykład: 0,2 = 0,2 x 10 ^ 1/10 ^ 1 = 2/10 = 1/5

Można to więc odczytać jako „1 część z 5”


2

Jednym z rozwiązań jest po prostu zapisanie wszystkich liczb jako liczb wymiernych. Istnieją biblioteki do arytmetyki liczb wymiernych (np. GMP ). Jeśli używasz języka OO, możesz po prostu użyć biblioteki klas liczb wymiernych, aby zastąpić swoją klasę liczb.

Programy finansowe, między innymi, używałyby takiego rozwiązania, aby móc wykonywać dokładne obliczenia i zachować precyzję, którą można stracić przy użyciu zwykłego float.

Oczywiście będzie to dużo wolniejsze, więc może nie być dla ciebie praktyczne. Zależy od tego, ile obliczeń musisz wykonać i jak ważna jest dla Ciebie precyzja.

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

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

2

Powiedzmy, że mamy 0,33, musimy wypisać "1/3". Jeśli mamy „0,4”, musimy wypisać „2/5”.

W powszechnym przypadku jest to błędne, ponieważ 1/3 = 0,3333333 = 0. (3) Ponadto nie można dowiedzieć się z sugerowanych powyżej rozwiązań, czy ułamek dziesiętny można zamienić na ułamek z określoną dokładnością, ponieważ wynik jest zawsze ułamkowy.

ALE, proponuję moją wszechstronną funkcję z wieloma opcjami opartymi na idei nieskończonych szeregów geometrycznych , w szczególności na formule:

wprowadź opis obrazu tutaj

Na początku ta funkcja próbuje znaleźć okres ułamka w reprezentacji ciągu. Następnie stosuje się opisaną powyżej formułę.

Kod liczb wymiernych jest zapożyczony z implementacji liczb wymiernych Stephena M. McKameya w C #. Mam nadzieję, że przeniesienie mojego kodu na inne języki nie będzie trudne.

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

Oto kilka przykładów zastosowań:

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;

Twoja sprawa z przycinaniem części zerowej prawej części:

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;

Demostracja w minionym okresie:

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.

Zaokrąglenie na końcu:

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

Najciekawszy przypadek:

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.

Inne testy i kod każdy może znaleźć w mojej bibliotece MathFunctions na github .


2

Ruby ma już wbudowane rozwiązanie:

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

W Railsach atrybuty liczbowe ActiveRecord można również konwertować:

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

2

Odpowiedz w C ++, zakładając, że masz klasę „BigInt”, która może przechowywać liczby całkowite o nieograniczonym rozmiarze.

Możesz zamiast tego użyć „unsigned long long”, ale będzie to działać tylko dla niektórych wartości.

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) zwróci „+0/1”, więc możesz zająć się tym przypadkiem osobno.

PS: Używam tego kodu w mojej własnej klasie „RationalNum” od kilku lat i został gruntownie przetestowany.


Twój przykład wydaje się rozkładać na wartościach takich jak 1.333333 .. przechodzi w bardzo długą pętlę podczas próby znalezienia wartości i wydaje się nie działać ... działa dobrze z innymi prostymi wartościami, takimi jak 1,25
Adamski

@Adamski: Dzięki. Okres „zbieżności” whilepętli jest ograniczony rozmiarem double, który wynosi zazwyczaj 64 bity. Nie zależy więc od początkowej wartości input ( val). GCDFunkcja, jednak nie zależy od tej wartości, choć zwykle jest zbieżny do rozwiązania dość szybko. Czy to możliwe, że nie zaimplementowałeś tej funkcji poprawnie?
barak manos

@Adamski: Dodatkowo, jak wspomniałem na początku odpowiedzi, jeśli używasz unsigned long longzamiast BigInt, to niekoniecznie da to poprawny wynik dla każdej wartości wejściowej ... Ale nawet w tym scenariuszu kod nie jest powinien „wejść w bardzo długą pętlę”.
barak manos

Ach ok tak, to jest całkowicie możliwe, funkcja GCD, której używałem, jest częścią klasy BigInteger z biblioteki Juce. Dzięki za informację!
Adamski

@Adamski: Więc nie ma sensu, że GCDfunkcja nie jest poprawnie zaimplementowana. Czy sprawdziłeś, czy kod działa przez długi czas podczas whilepętli, czy po niej? Sprawdzę wartość 1.33333, żeby zobaczyć, co się za tym kryje. Dzięki.
barak manos

2

Ten algorytm autorstwa Iana Richardsa / Johna Kennedy'ego nie tylko zwraca ładne ułamki, ale także działa bardzo dobrze pod względem szybkości. To jest kod C # wzięty z tej odpowiedzi przeze mnie.

Obsługuje wszystkie doublewartości z wyjątkiem specjalnych wartości, takich jak NaN i +/- nieskończoność, które musisz dodać w razie potrzeby.

Zwraca a new Fraction(numerator, denominator). Zastąp własnym typem.

Więcej przykładowych wartości i porównanie z innymi algorytmami można znaleźć tutaj

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

Przykładowe wartości zwracane przez ten algorytm:

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

Będziesz mieć dwa podstawowe problemy, które to utrudnią:

1) Liczba zmiennoprzecinkowa nie jest dokładną reprezentacją, co oznacza, że ​​jeśli masz ułamek „x / y”, który daje wartość „z”, algorytm ułamka może zwrócić wynik inny niż „x / y”.

2) Istnieje nieskończenie wiele więcej liczb niewymiernych niż racjonalnych. Liczba wymierna to taka, którą można przedstawić jako ułamek. Istoty irracjonalne, które nie potrafią.

Jednak w tani sposób, ponieważ zmiennoprzecinkowe mają ograniczoną dokładność, zawsze możesz przedstawić to jako jakąś formę frakcji. (Myślę...)


4
Liczba zmiennoprzecinkowa (lub podwójna) to ułamek. Mianownikiem jest potęga 2. Dlatego nie mogą one dokładnie przedstawiać niektórych liczb wymiernych.
erickson,

1

Ukończono powyższy kod i przekonwertowano go na 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";
    }

Dzięki, użyłem tego w Delphi, łatwiejszym do przeniesienia niż wszystkie te kręcone rzeczy
Peter Turner

1

Oto szybka i brudna implementacja w javascript, która wykorzystuje podejście brutalnej siły. Wcale niezoptymalizowany, działa w predefiniowanym zakresie ułamków: 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));

Jest to inspirowane podejściem stosowanym przez JPS.


0

Jak twierdziło wiele osób, naprawdę nie można przekonwertować liczby zmiennoprzecinkowej z powrotem na ułamek (chyba że jest bardzo dokładny, np. 0,25). Oczywiście możesz stworzyć rodzaj wyszukiwania dla dużej tablicy ułamków i użyć jakiejś logiki rozmytej, aby uzyskać wynik, którego szukasz. Znowu nie byłoby to dokładne i musiałbyś zdefiniować dolne granice tego, jak duży ma być mianownik.

.32 <x <.34 = 1/3 lub coś w tym rodzaju.



0

Natknąłem się na wyjątkowo eleganckie rozwiązanie Haskella wykorzystujące anamorfizm. To zależy od pakietu schematów rekurencji .

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

Jeśli wypróbujesz to w ghci, to naprawdę działa!

λ:> approximate pi 2
22 % 7
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.