Yüzen sayıları insan tarafından okunabilir kesirlere nasıl dönüştürebilirim?


105

Diyelim ki var 0.33, çıktı almamız gerekiyor 1/3.
Varsa 0.4çıktı almalıyız2/5 .

Buradaki fikir, verileri anlamanın daha iyi bir yolu olarak kullanıcının " y'nin x kısmını " anlamasını sağlamak için onu insan tarafından okunabilir hale getirmektir .

Yüzdelerin iyi bir ikame olduğunu biliyorum ama bunu yapmanın basit bir yolu olup olmadığını merak ediyordum.


.33=> "1/3"Örnek endişeleri beni; Beklerim .33=> "33/100". .33...Tabii ki demek istediğini varsayıyorum , ancak bu soruyla ilgili bir sorunu ortaya çıkarıyor - bir algoritmaya karar vermeden önce beklenen davranışa karar vermemiz gerekiyor. @ Debilski'nin Python cevabı, .limit_denominator()varsayılanı 10 ^ 7 maksimum payda olanı kullanır ; pratikte muhtemelen iyi varsayılan, ama bu yine de dikkatli olmazsan böcek tanıtmak ve edebilirsiniz yapar dönüşü "33/100"de .33söz.
dimo414

Ne olursa olsun ile dile specifc özellikleri mevcuttur. Ne sorduğunuz belirsizdir, eğer gerçekten de terimler açısından sadece bir çelişki değilse.
Marquis of Lorne

Yanıtlar:


70

David Eppstein en bulduk verilen gerçek sayı yapmak için bulma rasyonel yaklaşımı size soran tam olarak ne olduğu C kodu. Sürekli kesirler teorisine dayanır ve çok hızlı ve oldukça kompakttır.

Bu özel pay ve payda limitleri için özelleştirilmiş versiyonlarını kullandım.

/*
** 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
Ruby'de bir çözüm arayanlar için şanslıyız! Christopher Lord, yukarıdaki algoritmayı bir Ruby mücevherine uyguladı. Bkz christopher.lord.ac/fractions-in-ruby ve rubygems.org/gems/fraction
Shedd

6
Bu kodun çok iyi işlemediği bazı uç durumlar olduğunu unutmayın: -1.3333333, maksimum payda 4 ile verildiğinde, 3.333333e-08 hatasıyla 4 / -3 ve bir hata ile -5/4 döndürür = -8.333330e-02, ki bu doğru. Fakat aynı maksimum payda ile -1.33333337 verildiğinde, 12121211 / -9090908 hata ile = 4.218847e-15 ve -4/3 ile -3.666667e-08 hata verir ki bu doğru değildir. Bu, özellikle algoritmayı -4/3 gibi hesaplanmış kayan nokta sayılarıyla sunarken bu gibi yanlış sonuçlar veren bir sorundur.
edsko

27

Python 2.6'dan itibaren fractionsmodül var.

(Dokümanlardan alıntılar.)

>>> 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 , cevabınız OP'lerin language agnosticve algorithmetiketlerinden hangisini karşılıyor ?
vladr

2
@vladr Bu cevabı neredeyse 6 yıl önce yazdığım için (ve soru sorulduktan bir yıldan fazla bir süre sonra), sanırım o zamanlar mantığımın ne olduğunu artık bilmiyorum. Muhtemelen şu yoruma atıfta bulunuyordum: stackoverflow.com/questions/95727/… OTOH Bu cevabın başka bir sorudan birleştirilmesi de olabilir.
Bunca

Kesirler modülü tarafından kullanılan algoritma hakkında birkaç cümle ekleyebilirsiniz (ve cevabınızı belki Python3 için güncelleyebilirsiniz).
einpoklum

21

Çıktı, bir insan okuyucusuna sonucun sırasına ilişkin hızlı bir izlenim verecekse, "113/211" gibi bir şey döndürmenin bir anlamı yoktur, bu nedenle çıktı, kendisini tek basamaklı sayılarla (ve belki de 1 / 10 ve 9/10). Öyleyse, yalnızca 27 farklı kesir .

Çıktı oluşturmanın temelindeki matematik asla değişmeyeceğinden, bir çözüm basitçe bir ikili arama ağacını sabit kodlamak olabilir, böylece işlev en fazla log (27) ~ = 4 3/4 karşılaştırmasını gerçekleştirir. İşte kodun test edilmiş bir C versiyonu

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
Bu, daha çok ihtiyacımız olan yanal düşünme türüdür! Mükemmel öneri.
edsko

1
Biraz çirkin ama çok hızlı ve pratik bir yol
Bosak

1
Bu, inanılmaz derecede basit olan ilginç bir yaklaşım. Yer kazanmak için bunun yerine bir dizide ikili arama yapabilir veya bir ikili ağaç oluşturabilirsiniz, ancak yaklaşımınız muhtemelen biraz daha hızlıdır (dönüşten önce strcat'e tek bir çağrı kullanarak ve şimdi çağrıldığı yere bir var atayarak yerden tasarruf edebilirsiniz). Ayrıca 3/10 ve 7 / 10'u da dahil ederdim, ama belki bu sadece benim.
jimhark

1
Bu çözümden ilham alarak kısa (ancak tamamen optimize edilmemiş) bir kod oluşturdum. Daha geniş bir kesir aralığını kapsayacak şekilde kolayca genişletilebilir. jsfiddle.net/PdL23/1
Deepak Joy

1
Bunun 1/1000da insanca okunabilir olduğunu unutmayın , ancak yukarıdaki algoritma yalnızca çok kaba bir 1/10yaklaşım üretecektir ; Ben iyileştirmeler insanca okunabilir paydalar bir seçim yapabilecekleri hangi yönünden yapılan ve / veya eklenmesi inanıyoruz <, >, <<, >>önekler yaklaşım hoyratlık hakkında bir fikir vermek için.
vladr

16

İşte ondalık sayıyı kesire dönüştürmenin arkasındaki matematiği açıklayan bir bağlantı:

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

Ve işte VB kullanarak bunu gerçekten nasıl yapacağınıza dair örnek bir fonksiyon (www.freevbcode.com/ShowCode.asp?ID=582 adresinden):

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

(Google aramalarından: ondalık sayıdan kesire dönüştür, ondalıktan kesir koduna dönüştür)


2
Bu algoritmanın f = n / m olduğunda Ω (m) zaman aldığını unutmayın. Ve bu çok fazla olabilir, istemeseniz bile (0.66666666667'yi düşünün).
einpoklum

10

Her Bilgisayar Bilimcisinin Kayan Nokta Aritmetiği Hakkında Bilmesi Gerekenler'i okumak isteyebilirsiniz. .

Büyük bir sayıyla çarparak bir kesinlik belirtmeniz gerekecek:

3.141592 * 1000000 = 3141592

o zaman bir kesir yapabilirsiniz:

3 + (141592 / 1000000)

ve GCD aracılığıyla azaltın ...

3 + (17699 / 125000)

ama amaçlanan kısmı çıkarmanın bir yolu yok . Bunun yerine kodunuzda her zaman kesirler kullanmak isteyebilirsiniz - taşmayı önlemek için mümkün olduğunda kesirleri azaltmayı unutmayın!


9

İşte devinmoore tarafından önerilen VB kodunun Perl ve Javascript sürümleri:

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

Ve neredeyse aynı 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

AC # uygulaması

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

Sorunun bir kısmı, bu kadar çok kesirin aslında kesir olarak kolayca yorumlanmamasıdır. Örneğin, 0.33 1/3 değil, 33/100. Ancak ilkokul eğitiminizi hatırlıyorsanız, ondalık değerleri kesirlere dönüştürme süreci vardır, ancak çoğu zaman ondalık sayılar 0.33'te değil, 0.329999999999998 veya benzeri yerlerde saklandığından istediğinizi vermeniz pek olası değildir.

Kendinize bir iyilik yapın ve bununla uğraşmayın, ancak gerekirse aşağıdakileri yapabilirsiniz:

Kesirli kısmı kaldırana kadar orijinal değeri 10 ile çarpın. Bu sayıyı saklayın ve bölen olarak kullanın. Ardından ortak paydaları arayarak bir dizi basitleştirme yapın.

Yani 0,4, 4/10 olur. Daha sonra düşük değerlerle başlayan ortak bölenleri, muhtemelen asal sayıları ararsınız. 2'den başlayarak, bölme tabanının bölümün kendisiyle aynı olup olmadığını kontrol ederek 2'nin hem payı hem de paydayı eşit olarak böldüğünü görürsünüz.

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

Yani 5, 2'yi eşit olarak bölmez. Sonra bir sonraki sayıyı kontrol edersiniz, 3 deyin. Küçük sayının kareköküne ulaşana kadar bunu yaparsınız.

Bunu yaptıktan sonra ihtiyacın var


1
O son adım için öklid algoritmasını kullanmanızı öneririm
Graphics Noob


4

"Diyelim ki 0.33'ümüz var," 1/3 "çıktı vermemiz gerekiyor."

"Çözümün" ne kadar hassas olmasını bekliyorsunuz? 0.33 1 / 3'e eşit değildir. "İyi" (okunması kolay) bir yanıtı nasıl anlarsınız?

Ne olursa olsun, olası bir algoritma şunlar olabilir:

Y'nin 10'dan küçük olduğu bir X / Y biçiminde en yakın kesri bulmayı bekliyorsanız, her Y hesaplaması için 9 olası Y'nin tümü arasında döngü oluşturabilir ve ardından en doğru olanı seçebilirsiniz.


3

Bence bunu yapmanın en iyi yolu, önce float değerinizi bir ascii gösterimine dönüştürmektir. C ++ 'da ostringstream veya C' de sprintf kullanabilirsiniz. C ++ 'da şöyle görünecektir:

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;

Benzer bir yaklaşım düz C'de de alınabilir.

Daha sonra, kesrin en düşük terimlerle olduğunu kontrol etmeniz gerekir. Bu algoritma kesin bir cevap verecektir, yani 0.33, "1/3" değil, "33/100" çıktı verecektir. Bununla birlikte, 0.4, en düşük terimlere indirgendiğinde "2/5" olan "4/10" verir. Bu, EppStein'in çözümü kadar güçlü olmayabilir, ancak bunun daha basit olduğuna inanıyorum.


8 yıl sonra çözümünüzle karşılaştım, test ettim ve şu ana kadar mükemmel çalışıyor, ancak EppStein'in çözümü kadar güçlü olmadığını söylediniz ve nedenini merak ediyorum. Çözümünüz çok daha basit olduğundan, bu tercih edilen çözüm olmamalı, çalıştığı ve güvenli olduğu sürece mümkün olan en basit kodu yapmak istemedik mi ??
HBatalha

3

R'de yerleşik bir çözüm:

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

Bu, sürekli bir kesir yöntemi kullanır ve hassasiyeti ayarlamak için isteğe bağlı cyclesve max.denominatorbağımsız değişkenlere sahiptir .


Ayrıca library(numbers)ve contFrac(0.6666); dize çıktısını istediğiniz gibi almak için:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt

2

Hangi hata düzeyini kabul etmek istediğinizi bulmanız gerekecek. Tüm ondalık kesirler basit bir kesire indirgenmez. Muhtemelen 60 gibi kolayca bölünebilen bir sayı seçer ve değere en yakın 60'ıncı sayıyı bulur ve sonra kesri sadeleştiririm.


2

Bunu, aşağıdaki adımları kullanarak herhangi bir programlama dilinde yapabilirsiniz:

  1. 10 ^ x ile Çarp ve Böl, burada x, sayının ondalık basamağının kalmadığından emin olmak için gereken 10'un kuvvetidir. Örnek: 33 yapmak için 0.33 ile 10 ^ 2 = 100'ü çarpın ve 33/100 elde etmek için aynısı ile bölün
  2. Sonuçtan artık tamsayılar elde edemeyene kadar, elde edilen kesirin payını ve paydasını çarpanlara ayırarak azaltın.
  3. Ortaya çıkan indirgenmiş kesir cevabınız olmalıdır.

Örnek: 0,2 = 0,2 x 10 ^ 1/10 ^ 1 = 2/10 = 1/5

Bu, '5 üzerinden 1' olarak okunabilir


2

Çözümlerden biri, tüm sayıları ilk etapta rasyonel sayılar olarak saklamaktır. Rasyonel sayı aritmetiği için kütüphaneler vardır (örneğin, GMP ). Bir OO dili kullanıyorsanız, sayı sınıfınızı değiştirmek için sadece bir rasyonel sayı sınıfı kitaplığı kullanabilirsiniz.

Finans programları, diğerlerinin yanı sıra, tam hesaplamalar yapabilmek ve düz bir şamandıra kullanarak kaybedilebilecek hassasiyeti koruyabilmek için böyle bir çözüm kullanır.

Tabii ki çok daha yavaş olacak, bu yüzden sizin için pratik olmayabilir. Ne kadar hesaplama yapmanız gerektiğine ve hassasiyetin sizin için ne kadar önemli olduğuna bağlıdır.

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

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

2

Diyelim ki 0.33'ümüz var, "1/3" çıktı vermemiz gerekiyor. "0.4" e sahipsek, "2/5" çıktısı vermemiz gerekir.

Genel durumda yanlıştır, çünkü 1/3 = 0,3333333 = 0. (3) Üstelik, yukarıda önerilen çözümlerden ondalık çözümlerin tanımlanmış hassasiyetle kesire dönüştürülebileceğini anlamak imkansızdır, çünkü çıktı her zaman kesirdir.

AMA, Sonsuz geometrik seriler fikrine dayalı , özellikle formüle dayalı birçok seçenekle kapsamlı işlevimi öneriyorum :

görüntü açıklamasını buraya girin

İlk başta bu işlev dizge gösteriminde kesir periyodunu bulmaya çalışıyor. Bundan sonra yukarıda tarif edilen formül uygulanır.

Rasyonel sayılar kodu Stephen M. McKamey rasyonel sayı uygulamasından C # ' da ödünç alınmıştır . Umarım kodumu diğer dillere taşımak çok zor değildir.

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

Bazı kullanım örnekleri var:

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;

Sağ parça sıfır parça kırpmalı durumunuz:

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;

Minimum dönem gösterimi:

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.

Sonunda yuvarlama:

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

En ilginç durum:

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.

Github'daki MathFunctions kitaplığımda herkesin bulabileceği diğer testler ve kodlar .


2

Ruby zaten yerleşik bir çözüme sahiptir:

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

Rails'te, ActiveRecord sayısal öznitelikleri de dönüştürülebilir:

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

2

Sınırsız boyutlu tamsayıları depolayabilen bir 'BigInt' sınıfınız olduğunu varsayarak, C ++ ile yanıtlayın.

Bunun yerine 'unsigned long long' kullanabilirsiniz, ancak bu yalnızca belirli değerler için çalışacaktır.

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) "+0/1" değerini döndürecektir, bu nedenle bu durumu ayrı ayrı ele almak isteyebilirsiniz.

Not: Bu kodu kendi 'RationalNum' sınıfımda birkaç yıldır kullanıyorum ve kapsamlı bir şekilde test edildi.


Örneğiniz 1.333333 gibi değerlere göre kırılıyor gibi görünüyor .. değeri bulmaya çalışırken çok uzun bir döngüye giriyor ve işe yaramıyor gibi görünüyor ... 1.25
Adamski

@Adamski: Teşekkürler. whileDöngünün "yakınsama" periyodu double, tipik olarak 64 bit olan boyutuyla sınırlıdır . Dolayısıyla, input ( val) 'un başlangıç ​​değerine bağlı değildir . GCDGenellikle oldukça hızlı bir çözüme indirger rağmen fonksiyon Ancak, bu değerin bağlıdır yok. Bu işlevi doğru şekilde uygulamamış olmanız mümkün mü?
barak manos

@Adamski: Ayrıca cevabın başında da belirttiğim gibi unsigned long longyerine kullanıyorsanız BigInther girdi değeri için mutlaka doğru sonucu vermeyecektir ... Ama bu senaryo altında bile kod değil "çok uzun bir döngüye girmesi" gerekiyordu.
barak manos

Ah tamam evet, bu tamamen mümkün, kullandığım GCD işlevi Juce kitaplığı BigInteger sınıfının bir parçası. Bilgi için teşekkürler!
Adamski

@Adamski: o yüzden değil mantıklı GCDfonksiyonu düzgün uygulanmadı. Kodun whiledöngü sırasında veya sonrasında uzun süre çalışıp çalışmadığını kontrol ettiniz mi? Bunun arkasında ne olduğunu görmek için 1.33333 değerini kontrol edeceğim. Teşekkürler.
barak manos

2

Ian Richards / John Kennedy'nin bu algoritması sadece güzel kesirler vermekle kalmaz, aynı zamanda hız açısından da çok iyi performans gösterir. Bu cevaptan alınan C # kodudur benim tarafımdan .

Her şeyi halledebilir doubleGerekirse eklemeniz gereken NaN ve +/- sonsuz gibi özel değerler dışındaki değerleri .

Bir new Fraction(numerator, denominator). Kendi türünüzle değiştirin.

Daha fazla örnek değer ve diğer algoritmalarla karşılaştırma için buraya gidin

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

Bu algoritma tarafından döndürülen örnek değerler:

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

Bunu zorlaştıracak iki temel probleminiz olacak:

1) Kayan nokta tam bir gösterim değildir, yani "x / y" nin bir kesirine sahipseniz "z" değerine sahipseniz, kesir algoritmanız "x / y" dışında bir sonuç döndürebilir.

2) Rasyonelden çok daha fazla irrasyonel sayı vardır. Rasyonel sayı, kesir olarak temsil edilebilen sayıdır. Mantıksız olamayanlar.

Bununla birlikte, ucuz bir şekilde, kayan noktanın doğruluğu sınırlandığından, onu her zaman bir tür ihtilaf olarak temsil edebilirsiniz. (Bence...)


4
Düz bir (veya çift) olan bir fraksiyonu. Paydası 2'nin kuvvetidir. Bu yüzden bazı rasyonel sayıları tam olarak temsil edemezler.
erickson

1

Yukarıdaki kodu tamamladı ve as3'e dönüştürdü

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

Teşekkürler, bunu Delphi için kullandım, taşıması tüm o kıvırcık şeylerden daha kolay
Peter Turner

1

İşte javascript'te kaba kuvvet yaklaşımı kullanan hızlı ve kirli bir uygulama. Hiç optimize edilmemiş, önceden tanımlanmış bir kesir aralığı içinde çalışır: 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));

Bu, JPS tarafından kullanılan yaklaşımdan esinlenmiştir.


0

Birçok insanın belirttiği gibi, bir kayan noktayı bir kesire dönüştüremezsiniz (son derece kesin değilse .25). Elbette, geniş bir kesir dizisi için bir tür arama oluşturabilir ve aradığınız sonucu üretmek için bir tür bulanık mantık kullanabilirsiniz. Yine de bu kesin olmaz ve paydanın ne kadar büyük olmasını istediğinize dair daha düşük bir sınır tanımlamanız gerekir.

.32 <x <.34 = 1/3 veya bunun gibi bir şey.



0

Bir anamorfizmi kullanan özellikle zarif bir Haskell çözümüyle karşılaştım. Özyineleme şemaları paketine bağlıdır .

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

Bunu ghci'de denerseniz, gerçekten işe yarıyor!

λ:> approximate pi 2
22 % 7
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.