Bagaimana cara mengubah pelampung menjadi pecahan yang dapat dibaca manusia?

103

Katakanlah kita punya 0.33, kita perlu mengeluarkan 1/3.
Jika sudah 0.4, kita perlu mengeluarkan 2/5.

Idenya adalah membuatnya dapat dibaca manusia untuk membuat pengguna memahami " bagian x dari y " sebagai cara yang lebih baik untuk memahami data.

Saya tahu bahwa persentase adalah pengganti yang baik, tetapi saya bertanya-tanya apakah ada cara sederhana untuk melakukan ini?

Swaroop CH
sumber
Contoh .33=> "1/3"menyangkut saya; Saya berharap .33=> "33/100". Saya berasumsi yang Anda maksudkan .33..., tentu saja, tetapi hal itu mengungkap masalah dengan pertanyaan - sebelum kita dapat menyelesaikan algoritme, kita perlu memutuskan perilaku yang diharapkan. Jawaban Python @ Debilski menggunakan .limit_denominator()yang defaultnya ke penyebut maksimum 10 ^ 7; mungkin default yang baik dalam praktek, tetapi ini masih bisa memperkenalkan bug jika Anda tidak hati-hati, dan tidak kembali "33/100"dalam .33kasus.
dimo414
Dengan apa pun language- specifc fitur yang tersedia. Tidak jelas apa yang Anda tanyakan, jika memang itu bukan sekadar kontradiksi.
Marquis dari Lorne

Jawaban:

70

Saya telah menemukan pendekatan rasional David Eppstein untuk memberikan kode bilangan real C persis seperti yang Anda minta. Ini didasarkan pada teori pecahan lanjutan dan sangat cepat dan cukup kompak.

Saya telah menggunakan versi ini yang disesuaikan untuk pembilang dan batas penyebut tertentu.

/*
** 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]));
}
Epsilon
sumber
6
Bagi Anda yang mencari solusi di Ruby, kami beruntung! Christopher Lord telah mengimplementasikan algoritme di atas dalam permata Ruby. Lihat christopher.lord.ac/fractions-in-ruby dan rubygems.org/gems/fraction
dicoret
6
Sadarilah bahwa ada beberapa kasus tepi yang kode ini tidak menangani dengan baik: ketika diberikan -1.3333333 dengan penyebut maksimum 4 ia mengembalikan 4 / -3 dengan kesalahan 3.333333e-08 dan -5/4 dengan kesalahan = -8.333330e-02, yang benar. Namun ketika diberikan -1.33333337 dengan penyebut maksimum yang sama, ternyata 12121211 / -9090908 dengan error error = 4.218847e-15 dan -4/3 dengan error -3.666667e-08 yang salah. Ini adalah masalah khususnya saat menyajikan algoritme dengan bilangan floating point yang dihitung seperti -4/3, yang menghasilkan hasil yang salah seperti ini.
edsko
27

Dari Python 2.6 ada fractionsmodulnya.

(Mengutip dari dokumen.)

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

>>> from math import pi, cos
>>> Fraction.from_float(cos(pi/3))
Fraction(4503599627370497, 9007199254740992)
>>> Fraction.from_float(cos(pi/3)).limit_denominator()
Fraction(1, 2)
Debilski
sumber
6
Catatan implementasi dan algoritme di hg.python.org/cpython/file/822c7c0d27d1/Lib/fractions.py#l211
piro
2
@ Debilski manakah dari OP language agnosticdan algorithmtag yang memenuhi jawaban Anda?
vladr
2
@vladr Ya, mengingat saya menulis jawaban ini hampir 6 tahun yang lalu (dan lebih dari satu tahun setelah pertanyaan diajukan), saya rasa saya tidak tahu lagi apa alasan saya saat itu. Kemungkinan besar saya merujuk pada komentar ini: stackoverflow.com/questions/95727/… OTOH Bisa jadi jawaban ini juga telah digabungkan dari pertanyaan lain. Siapa yang tahu setelah bertahun-tahun…
Debilski
Anda dapat menambahkan beberapa kalimat tentang algoritma yang digunakan oleh modul pecahan (dan mungkin memperbarui jawaban Anda untuk Python3).
einpoklum
21

Jika keluarannya adalah untuk memberikan gambaran yang cepat kepada pembaca manusia tentang urutan hasil, tidak masuk akal mengembalikan sesuatu seperti "113/211", jadi keluaran harus membatasi dirinya sendiri untuk menggunakan angka satu digit (dan mungkin 1 / 10 dan 9/10). Jika demikian, Anda dapat mengamati bahwa hanya ada 27 pecahan yang berbeda .

Karena matematika yang mendasari untuk menghasilkan keluaran tidak akan pernah berubah, solusinya bisa dengan membuat kode keras pohon pencarian biner, sehingga fungsi tersebut akan melakukan paling banyak perbandingan log (27) ~ = 4 3/4. Berikut ini adalah versi C kode yang diuji

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

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

    if (d == 0.0)
        return rval;

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

    return rval;
}
JP
sumber
3
Ini adalah jenis pemikiran lateral yang lebih kita butuhkan! Saran yang bagus.
edsko
1
Agak jelek tapi cara yang sangat cepat dan praktis
Bosak
1
Ini adalah pendekatan menarik yang sangat sederhana. Untuk menghemat ruang, Anda dapat mencari array biner, atau membuat pohon biner, tetapi pendekatan Anda mungkin sedikit lebih cepat (Anda dapat menghemat ruang dengan menggunakan satu panggilan ke strcat sebelum kembali dan menetapkan var yang sekarang dipanggil). Juga saya akan memasukkan 3/10 dan 7/10, tapi mungkin itu hanya saya.
jimhark
1
Terinspirasi oleh solusi ini, saya telah membuat kode pendek (tapi sama sekali tidak dioptimalkan). Ini dapat dengan mudah diperpanjang untuk mencakup rentang pecahan yang lebih besar. jsfiddle.net/PdL23/1
Deepak Joy
1
Perhatikan bahwa 1/1000ini juga dapat dibaca secara manusiawi, tetapi algoritme di atas hanya akan menghasilkan 1/10perkiraan yang sangat kasar ; Saya percaya bahwa perbaikan dapat dilakukan dalam hal yang penyebut dibaca secara manusiawi satu dapat memilih dari, dan / atau penambahan <, >, <<, >>prefiks untuk memberikan gambaran tentang kekasaran pendekatan tersebut.
vladr
16

Berikut tautan yang menjelaskan matematika di balik mengubah desimal menjadi pecahan:

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

Dan berikut adalah contoh fungsi bagaimana melakukannya menggunakan VB (dari 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

(Dari pencarian google: ubah desimal menjadi pecahan, ubah desimal menjadi kode pecahan)

devinmoore
sumber
2
Perhatikan algoritma ini membutuhkan waktu Ω (m) ketika f = n / m. Dan itu bisa jadi banyak, bahkan jika Anda tidak menginginkannya (pertimbangkan 0.66666666667).
einpoklum
10

Anda mungkin ingin membaca Yang Harus Diketahui Setiap Ilmuwan Komputer tentang Aritmatika Titik Mengambang .

Anda harus menentukan beberapa presisi dengan mengalikan dengan bilangan besar:

3.141592 * 1000000 = 3141592

maka Anda dapat membuat pecahan:

3 + (141592 / 1000000)

dan mengurangi melalui GCD ...

3 + (17699 / 125000)

tetapi tidak ada cara untuk mengeluarkan pecahan yang dimaksud . Anda mungkin ingin selalu menggunakan pecahan di seluruh kode Anda - ingat saja untuk mengurangi pecahan bila Anda bisa untuk menghindari luapan!

nlucaroni.dll
sumber
9

Berikut adalah versi Perl dan Javascript dari kode VB yang disarankan oleh 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";
}

Dan javascript yang hampir identik:

function dec2frac(d) {

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

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

Implementasi AC #

/// <summary>
/// Represents a rational number
/// </summary>
public struct Fraction
{
    public int Numerator;
    public int Denominator;

    /// <summary>
    /// Constructor
    /// </summary>
    public Fraction(int numerator, int denominator)
    {
        this.Numerator = numerator;
        this.Denominator = denominator;
    }

    /// <summary>
    /// Approximates a fraction from the provided double
    /// </summary>
    public static Fraction Parse(double d)
    {
        return ApproximateFraction(d);
    }

    /// <summary>
    /// Returns this fraction expressed as a double, rounded to the specified number of decimal places.
    /// Returns double.NaN if denominator is zero
    /// </summary>
    public double ToDouble(int decimalPlaces)
    {
        if (this.Denominator == 0)
            return double.NaN;

        return System.Math.Round(
            Numerator / (double)Denominator,
            decimalPlaces
        );
    }


    /// <summary>
    /// Approximates the provided value to a fraction.
    /// http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions
    /// </summary>
    private static Fraction ApproximateFraction(double value)
    {
        const double EPSILON = .000001d;

        int n = 1;  // numerator
        int d = 1;  // denominator
        double fraction = n / d;

        while (System.Math.Abs(fraction - value) > EPSILON)
        {
            if (fraction < value)
            {
                n++;
            }
            else
            {
                d++;
                n = (int)System.Math.Round(value * d);
            }

            fraction = n / (double)d;
        }

        return new Fraction(n, d);
    }
}
Tom
sumber
7

Pohon Stern-Brocot menginduksi cara yang cukup alami untuk memperkirakan bilangan real dengan pecahan dengan penyebut sederhana.

Doug McClean
sumber
6

Sebagian dari masalahnya adalah begitu banyak pecahan sebenarnya tidak mudah diartikan sebagai pecahan. Misalnya 0,33 bukan 1/3, itu 33/100. Tetapi jika Anda mengingat pelatihan sekolah dasar Anda, maka ada proses untuk mengubah nilai desimal menjadi pecahan, namun tidak mungkin memberikan apa yang Anda inginkan karena sebagian besar waktu angka desimal tidak disimpan di 0,33, tetapi 0,329999999999998 atau semacamnya.

Bantulah diri Anda sendiri dan jangan repot-repot dengan ini, tetapi jika perlu, Anda dapat melakukan hal berikut:

Kalikan nilai awal dengan 10 sampai Anda menghilangkan bagian pecahannya. Simpan angka itu, dan gunakan sebagai pembagi. Kemudian lakukan serangkaian penyederhanaan dengan mencari penyebut yang sama.

Jadi 0,4 akan menjadi 4/10. Anda kemudian akan mencari pembagi persekutuan yang dimulai dengan nilai rendah, mungkin bilangan prima. Dimulai dengan 2, Anda akan melihat apakah 2 membagi pembilang dan penyebutnya secara merata dengan memeriksa apakah lantai pembaginya sama dengan pembagi itu sendiri.

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

Jadi 5 tidak membagi 2 secara merata. Jadi, kemudian Anda memeriksa angka berikutnya, katakanlah 3. Anda melakukan ini sampai Anda mencapai atau di atas akar kuadrat dari angka yang lebih kecil.

Setelah Anda melakukan itu maka Anda perlu

Orion Adrian
sumber
1
Saya sarankan menggunakan algoritme euclidean untuk langkah terakhir itu
Graphics Noob
5

Ini bukan "algoritme", hanya solusi Python: http://docs.python.org/library/fractions.html

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

"Katakanlah kita memiliki 0,33, kita perlu mengeluarkan" 1/3 "."

Presisi apa yang Anda harapkan dari "solusi" tersebut? 0,33 tidak sama dengan 1/3. Bagaimana Anda mengenali jawaban yang "baik" (mudah dibaca)?

Apa pun yang terjadi, algoritme yang mungkin dapat berupa:

Jika Anda berharap menemukan pecahan terdekat dalam bentuk X / Y di mana Y kurang dari 10, Anda dapat mengulang semua 9 kemungkinan Y, untuk setiap Y menghitung X, lalu memilih yang paling akurat.

Suma
sumber
3

Saya pikir cara terbaik untuk melakukan ini adalah dengan terlebih dahulu mengubah nilai float Anda menjadi representasi ascii. Di C ++ Anda bisa menggunakan ostringstream atau di C, Anda bisa menggunakan sprintf. Begini tampilannya di 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;

Pendekatan serupa dapat dilakukan pada C.

Setelah itu Anda perlu memeriksa bahwa pecahannya termasuk suku yang paling rendah. Algoritme ini akan memberikan jawaban yang tepat, yaitu 0,33 akan menghasilkan "33/100", bukan "1/3". Namun, 0,4 akan menjadi "4/10", yang bila disederhanakan menjadi "2/5". Ini mungkin tidak sekuat solusi EppStein, tapi saya yakin ini lebih mudah.

bpm
sumber
8 tahun kemudian saya menemukan solusi Anda, saya telah menguji dan sejauh ini berfungsi dengan sempurna, tetapi Anda mengatakan itu tidak sekuat solusi EppStein dan saya bertanya-tanya mengapa. Karena solusi Anda jauh lebih sederhana, bukankah seharusnya ini menjadi solusi pilihan, bukankah kita bermaksud melakukan kode sesederhana mungkin selama berhasil dan aman ??
HBatalha
3

Solusi bawaan di R:

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

Ini menggunakan metode pecahan lanjutan dan memiliki opsional cyclesdan max.denominatorargumen untuk menyesuaikan presisi.

Ben Bolker
sumber
Juga library(numbers)dan contFrac(0.6666); untuk mendapatkan keluaran string yang diinginkan:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt
2

Anda harus mencari tahu tingkat kesalahan yang ingin Anda terima. Tidak semua pecahan desimal akan menjadi pecahan sederhana. Saya mungkin akan memilih angka yang mudah habis dibagi, seperti 60, dan mencari tahu berapa banyak 60 yang paling dekat dengan nilainya, lalu menyederhanakan pecahannya.

Mark Bessey
sumber
2

Anda dapat melakukan ini dalam bahasa pemrograman apa pun menggunakan langkah-langkah berikut:

  1. Kalikan dan Bagi dengan 10 ^ x di mana x adalah pangkat 10 yang diperlukan untuk memastikan bahwa bilangan tersebut tidak memiliki tempat desimal yang tersisa. Contoh: Kalikan 0,33 dengan 10 ^ 2 = 100 sehingga menjadi 33 dan bagi dengan yang sama untuk mendapatkan 33/100
  2. Kurangi pembilang dan penyebut pecahan yang dihasilkan dengan faktorisasi, hingga Anda tidak bisa lagi mendapatkan bilangan bulat dari hasil.
  3. Fraksi tereduksi yang dihasilkan harus menjadi jawaban Anda.

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

Jadi, itu bisa dibaca sebagai '1 bagian dari 5'

Pascal
sumber
2

Salah satu solusinya adalah dengan menyimpan semua bilangan sebagai bilangan rasional di tempat pertama. Ada perpustakaan untuk aritmatika bilangan rasional (misalnya GMP ). Jika menggunakan bahasa OO, Anda mungkin dapat menggunakan pustaka kelas bilangan rasional untuk menggantikan kelas bilangan Anda.

Program keuangan, antara lain, akan menggunakan solusi tersebut untuk dapat membuat kalkulasi yang tepat dan menjaga presisi yang mungkin hilang dengan menggunakan pelampung biasa.

Tentu saja akan jauh lebih lambat jadi mungkin tidak praktis untuk Anda. Tergantung pada seberapa banyak kalkulasi yang perlu Anda lakukan, dan seberapa penting ketepatan itu bagi Anda.

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

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

Katakanlah kita memiliki 0,33, kita perlu mengeluarkan "1/3". Jika kita memiliki "0.4", kita perlu mengeluarkan "2/5".

Ini salah dalam kasus umum, karena 1/3 = 0.3333333 = 0. (3) Selain itu, tidak mungkin untuk mengetahui dari solusi yang disarankan di atas adalah desimal dapat diubah menjadi pecahan dengan ketepatan yang ditentukan, karena keluaran selalu pecahan.

TAPI, saya menyarankan fungsi komprehensif saya dengan banyak opsi berdasarkan gagasan deret geometris tak hingga , khususnya rumus:

masukkan deskripsi gambar di sini

Pada awalnya fungsi ini mencoba mencari periode pecahan dalam representasi string. Setelah itu rumus di atas diterapkan.

Kode bilangan rasional dipinjam dari implementasi bilangan rasional Stephen M. McKamey di C #. Saya berharap tidak terlalu sulit untuk mem-port kode saya ke bahasa lain.

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

Ada beberapa contoh penggunaan:

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;

Casing Anda dengan pemangkasan bagian nol bagian kanan:

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;

Periode demostrasi min:

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.

Pembulatan di akhir:

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

Kasus paling menarik:

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.

Tes dan kode lain dapat ditemukan semua orang di pustaka MathFunctions saya di github .

Ivan Kochurkin
sumber
2

Ruby sudah memiliki solusi bawaan:

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

Di Rails, atribut numerik ActiveRecord juga dapat diubah:

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

Jawab dalam C ++, dengan asumsi Anda memiliki kelas 'BigInt', yang dapat menyimpan bilangan bulat ukuran tak terbatas.

Anda dapat menggunakan 'unsigned long long', tetapi ini hanya akan berfungsi untuk nilai-nilai tertentu.

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) akan mengembalikan "+0/1", jadi Anda mungkin ingin menangani kasus ini secara terpisah.

PS: Saya telah menggunakan kode ini di kelas 'RationalNum' saya sendiri selama beberapa tahun, dan itu telah diuji secara menyeluruh.

barak manos
sumber
Contoh Anda tampaknya rusak pada nilai-nilai seperti 1,333333 .. itu menjadi putaran yang sangat panjang mencoba menemukan nilai dan tampaknya tidak berhasil ... tidak masalah dengan nilai-nilai sederhana lainnya seperti 1,25
Adamski
@Adamski: Terima kasih. Periode "konvergensi" dari whileloop dibatasi oleh ukuran double, yang biasanya 64 bit. Jadi tidak tergantung pada nilai awal dari input ( val). Namun GCDfungsinya bergantung pada nilai ini, meskipun biasanya konvergen ke solusi cukup cepat. Apakah mungkin Anda tidak mengimplementasikan fungsi ini dengan benar?
barak manos
@Adamski: Selain itu, seperti yang saya sebutkan di awal jawaban, jika Anda menggunakan unsigned long longalih-alih BigInt, maka itu tidak akan selalu menghasilkan hasil yang benar untuk setiap nilai input ... Tetapi bahkan di bawah skenario itu, kodenya tidak seharusnya "masuk ke lingkaran yang sangat panjang".
barak manos
Ah ok ya, itu sangat mungkin, fungsi GCD yang saya gunakan adalah bagian dari kelas perpustakaan Juce BigInteger. Terima kasih untuk informasi!
Adamski
@Adamski: Jadi tidak masuk akal bahwa GCDfungsi tersebut tidak diimplementasikan dengan benar. Sudahkah Anda memeriksa apakah kode berjalan untuk waktu yang lama selama whilepengulangan atau setelahnya? Saya akan memeriksa nilai 1,33333, untuk melihat ada apa di balik ini. Terima kasih.
barak manos
2

Algoritma oleh Ian Richards / John Kennedy ini tidak hanya mengembalikan pecahan yang bagus, tetapi juga bekerja dengan sangat baik dalam hal kecepatan. Ini adalah kode C # yang saya ambil dari jawaban ini .

Itu dapat menangani semua doublenilai kecuali nilai khusus seperti NaN dan +/- tak terhingga, yang harus Anda tambahkan jika diperlukan.

Ini mengembalikan a new Fraction(numerator, denominator). Gantikan dengan tipe Anda sendiri.

Untuk nilai contoh lainnya dan perbandingan dengan algoritme lain, buka di sini

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

Contoh nilai yang dikembalikan oleh algoritma ini:

Accuracy: 1.0E-3      | Richards                     
Input                 | Result           Error       
======================| =============================
   3                  |       3/1          0         
   0.999999           |       1/1         1.0E-6     
   1.000001           |       1/1        -1.0E-6     
   0.50 (1/2)         |       1/2          0         
   0.33... (1/3)      |       1/3          0         
   0.67... (2/3)      |       2/3          0         
   0.25 (1/4)         |       1/4          0         
   0.11... (1/9)      |       1/9          0         
   0.09... (1/11)     |       1/11         0         
   0.62... (307/499)  |       8/13        2.5E-4     
   0.14... (33/229)   |      16/111       2.7E-4     
   0.05... (33/683)   |      10/207      -1.5E-4     
   0.18... (100/541)  |      17/92       -3.3E-4     
   0.06... (33/541)   |       5/82       -3.7E-4     
   0.1                |       1/10         0         
   0.2                |       1/5          0         
   0.3                |       3/10         0         
   0.4                |       2/5          0         
   0.5                |       1/2          0         
   0.6                |       3/5          0         
   0.7                |       7/10         0         
   0.8                |       4/5          0         
   0.9                |       9/10         0         
   0.01               |       1/100        0         
   0.001              |       1/1000       0         
   0.0001             |       1/10000      0         
   0.33333333333      |       1/3         1.0E-11    
   0.333              |     333/1000       0         
   0.7777             |       7/9         1.0E-4     
   0.11               |      10/91       -1.0E-3     
   0.1111             |       1/9         1.0E-4     
   3.14               |      22/7         9.1E-4     
   3.14... (pi)       |      22/7         4.0E-4     
   2.72... (e)        |      87/32        1.7E-4     
   0.7454545454545    |      38/51       -4.8E-4     
   0.01024801004      |       2/195       8.2E-4     
   0.99011            |     100/101      -1.1E-5     
   0.26... (5/19)     |       5/19         0         
   0.61... (37/61)    |      17/28        9.7E-4     
                      | 
Accuracy: 1.0E-4      | Richards                     
Input                 | Result           Error       
======================| =============================
   0.62... (307/499)  |     299/486      -6.7E-6     
   0.05... (33/683)   |      23/476       6.4E-5     
   0.06... (33/541)   |      33/541        0         
   1E-05              |       1/99999     1.0E-5     
   0.7777             |    1109/1426     -1.8E-7     
   3.14... (pi)       |     333/106      -2.6E-5     
   2.72... (e)        |     193/71        1.0E-5     
   0.61... (37/61)    |      37/61         0         
Kay Zed
sumber
1

Anda akan menghadapi dua masalah dasar yang akan membuat ini sulit:

1) Titik mengambang bukanlah representasi yang tepat yang berarti bahwa jika Anda memiliki pecahan dari "x / y" yang menghasilkan nilai "z", algoritme pecahan Anda dapat mengembalikan hasil selain "x / y".

2) Ada lebih banyak bilangan irasional tak terhingga daripada rasional. Bilangan rasional adalah bilangan yang dapat direpresentasikan sebagai pecahan. Makhluk irasional yang tidak bisa.

Namun, dengan cara yang murah, karena floating point memiliki akurasi batas, maka Anda selalu dapat merepresentasikannya sebagai beberapa bentuk faksi. (Kupikir...)

Torlack
sumber
4
Pelampung (atau ganda) adalah pecahan. Penyebutnya adalah pangkat 2. Itulah mengapa mereka tidak dapat secara tepat mewakili beberapa bilangan rasional.
erickson
1

Menyelesaikan kode di atas dan mengubahnya menjadi as3

public static function toFrac(f:Number) : String
    {
        if (f>1)
        {
            var parte1:int;
            var parte2:Number;
            var resultado:String;
            var loc:int = String(f).indexOf(".");
            parte2 = Number(String(f).slice(loc, String(f).length));
            parte1 = int(String(f).slice(0,loc));
            resultado = toFrac(parte2);
            parte1 *= int(resultado.slice(resultado.indexOf("/") + 1, resultado.length)) + int(resultado.slice(0, resultado.indexOf("/")));
            resultado = String(parte1) +  resultado.slice(resultado.indexOf("/"), resultado.length)
            return resultado;
        }
        if( f < 0.47 )
            if( f < 0.25 )
                if( f < 0.16 )
                    if( f < 0.13 )
                        if( f < 0.11 )
                            return "1/10";
                        else
                            return "1/9";
                    else
                        if( f < 0.14 )
                            return "1/8";
                        else
                            return "1/7";
                else
                    if( f < 0.19 )
                        return "1/6";
                    else
                        if( f < 0.22 )
                            return "1/5";
                        else
                            return "2/9";
            else
                if( f < 0.38 )
                    if( f < 0.29 )
                        return "1/4";
                    else
                        if( f < 0.31 )
                            return "2/7";
                        else
                            return "1/3";
                else
                    if( f < 0.43 )
                        if( f < 0.40 )
                            return "3/8";
                        else
                            return "2/5";
                    else
                        if( f < 0.44 )
                            return "3/7";
                        else
                            return "4/9";
        else
            if( f < 0.71 )
                if( f < 0.60 )
                    if( f < 0.56 )
                        return "1/2";
                    else
                        if( f < 0.57 )
                            return "5/9";
                        else
                            return "4/7";
                else
                    if( f < 0.63 )
                        return "3/5";
                    else
                        if( f < 0.66 )
                            return "5/8";
                        else
                            return "2/3";
            else
                if( f < 0.80 )
                    if( f < 0.74 )
                        return "5/7";
                    else
                        if(f < 0.78 )
                            return "3/4";
                        else
                            return "7/9";
                else
                    if( f < 0.86 )
                        if( f < 0.83 )
                            return "4/5";
                        else
                            return "5/6";
                    else
                        if( f < 0.88 )
                            return "6/7";
                        else
                            if( f < 0.89 )
                                return "7/8";
                            else
                                if( f < 0.90 )
                                    return "8/9";
                                else
                                    return "9/10";
    }
João Lopes
sumber
Terima kasih, saya menggunakan ini untuk Delphi, lebih mudah untuk melakukan port daripada semua hal keriting itu
Peter Turner
1

Berikut adalah implementasi cepat dan kotor di javascript yang menggunakan pendekatan brute force. Sama sekali tidak dioptimalkan, ia bekerja dalam kisaran pecahan yang telah ditentukan: 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));

Hal ini terinspirasi dari pendekatan yang digunakan JPS.

Deepak Joy
sumber
0

Seperti yang dikatakan banyak orang, Anda benar-benar tidak dapat mengubah floating point kembali menjadi pecahan (kecuali jika sangat persis seperti 0,25). Tentu saja Anda dapat membuat beberapa jenis pencarian untuk sejumlah besar pecahan dan menggunakan semacam logika fuzzy untuk menghasilkan hasil yang Anda cari. Sekali lagi ini tidak akan tepat dan Anda perlu menentukan batas bawah seberapa besar penyebut yang Anda inginkan.

.32 <x <.34 = 1/3 atau sesuatu seperti itu.

Tim
sumber
0

Berikut adalah implementasi untuk ruby http://github.com/valodzka/frac

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

Saya menemukan solusi Haskell yang sangat elegan yang menggunakan anamorphism. Itu tergantung pada paket skema-rekursi .

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

Jika Anda mencobanya di ghci, itu benar-benar berhasil!

λ:> approximate pi 2
22 % 7

sumber