Làm thế nào để chuyển đổi float thành phân số mà con người có thể đọc được?


103

Giả sử chúng ta có 0.33, chúng ta cần xuất ra 1/3.
Nếu chúng ta có 0.4, chúng ta cần xuất ra 2/5.

Ý tưởng là làm cho nó có thể đọc được để làm cho người dùng hiểu " x phần trong y " như một cách hiểu dữ liệu tốt hơn.

Tôi biết rằng tỷ lệ phần trăm là một sự thay thế tốt nhưng tôi đã tự hỏi liệu có cách nào đơn giản để làm điều này không?


Các .33=> "1/3"ví dụ mối quan tâm tôi; Tôi sẽ mong đợi .33=> "33/100". Tôi cho rằng ý .33...của bạn là tất nhiên, nhưng nó cho thấy một vấn đề với câu hỏi - trước khi chúng tôi có thể giải quyết một thuật toán, chúng tôi cần quyết định về hành vi mong đợi. Câu trả lời Python của @ Debilski sử dụng .limit_denominator()mặc định là mẫu số tối đa là 10 ^ 7; có thể là một mặc định tốt trong thực tế, nhưng điều này vẫn có thể tạo ra lỗi nếu bạn không cẩn thận và sẽ quay lại "33/100"trong .33trường hợp này.
dimo414

Với bất kỳ ngôn ngữ nào - các tính năng cụ thể có sẵn. Không rõ những gì bạn đang hỏi, nếu thực sự đó không phải là một sự mâu thuẫn đơn thuần.
Marquis of Lorne,

Câu trả lời:


70

Tôi đã tìm thấy giá trị gần đúng hợp lý của David Eppstein đối vớisố thực C đã cho là chính xác những gì bạn đang yêu cầu. Nó dựa trên lý thuyết về phân số liên tục và rất nhanh và khá nhỏ gọn.

Tôi đã sử dụng các phiên bản của tùy chỉnh này cho các giới hạn tử số và mẫu số cụ thể.

/*
** 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
Đối với những người bạn đang tìm kiếm giải pháp trong Ruby, chúng tôi rất may mắn! Christopher Lord đã thực hiện thuật toán trên trong một viên ngọc Ruby. Xem christopher.lord.ac/fraction-in-rubyrubygems.org/gems/fraction
đổ vào

6
Lưu ý rằng có một số trường hợp cạnh mà mã này không xử lý tốt lắm: khi cho -1.3333333 với mẫu số tối đa là 4, nó trả về 4 / -3 với lỗi là 3.333333e-08 và -5/4 với lỗi = -8.333330e-02, chính xác. Nhưng khi cho -1.33333337 với cùng mẫu số lớn nhất, nó sẽ biến thành 12121211 / -9090908 với sai số = 4.218847e-15 và -4/3 với sai số là -3.666667e-08, điều này không đúng. Đây là một vấn đề đặc biệt khi trình bày thuật toán với các số dấu phẩy động được tính toán như -4/3, mang lại kết quả không chính xác như thế này.
edsko

26

Từ Python 2.6 trở đi có fractionsmô-đun.

(Trích dẫn từ tài liệu.)

>>> 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
Các ghi chú về triển khai và thuật toán tại hg.python.org/cpython/file/822c7c0d27d1/Lib/fraction.py#l211
piro Ngày

2
@Debilski câu trả lời của bạn thỏa mãn những thẻ OP language agnosticalgorithmthẻ nào?
vladr

2
@vladr Chà, vì tôi đã viết câu trả lời này gần 6 năm trước (và hơn một năm sau khi câu hỏi được đặt ra), tôi đoán tôi không biết lý do của mình hồi đó là gì nữa. Có lẽ tôi đang đề cập đến nhận xét này: stackoverflow.com/questions/95727/… OTOH Cũng có thể câu trả lời này đã được hợp nhất từ ​​một câu hỏi khác. Ai có thể nói sau
ngần ấy

Bạn có thể thêm một vài câu về thuật toán được sử dụng bởi mô-đun phân số (và có thể cập nhật câu trả lời của bạn cho Python3).
einpoklum

21

Nếu đầu ra cung cấp cho người đọc một ấn tượng nhanh về thứ tự của kết quả, nó sẽ không có ý nghĩa gì khi trả về một cái gì đó như "113/211", vì vậy đầu ra nên tự giới hạn ở việc sử dụng các số có một chữ số (và có thể là 1 / 10 và 9/10). Nếu vậy, bạn có thể quan sát rằng chỉ có 27 phân số khác nhau .

Vì toán học cơ bản để tạo ra kết quả sẽ không bao giờ thay đổi, nên giải pháp có thể chỉ là mã hóa cứng cây tìm kiếm nhị phân, để hàm sẽ thực hiện nhiều nhất các phép so sánh log (27) ~ = 4 3/4. Đây là phiên bản C đã thử nghiệm của mã

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
Đây là kiểu tư duy bên mà chúng ta cần nhiều hơn nữa! Đề xuất tuyệt vời.
edsko

1
Nó hơi xấu xí nhưng rất nhanh và thực tế
Bosak

1
Đây là một cách tiếp cận thú vị mà đơn giản đến kinh ngạc. Để tiết kiệm dung lượng, thay vào đó, bạn có thể tìm kiếm nhị phân một mảng hoặc tạo một cây nhị phân, nhưng cách tiếp cận của bạn có thể nhanh hơn một chút (bạn có thể tiết kiệm dung lượng bằng cách sử dụng một lệnh gọi strcat trước khi trả về và gán một var nơi nó hiện được gọi). Ngoài ra, tôi sẽ bao gồm 3/10 và 7/10, nhưng có lẽ đó chỉ là tôi.
jimhark

1
Lấy cảm hứng từ giải pháp này, tôi đã tạo một đoạn mã ngắn (nhưng hoàn toàn không được tối ưu hóa). Nó có thể dễ dàng được mở rộng để bao gồm một loạt các phân số lớn hơn. jsfiddle.net/PdL23/1
Deepak Joy

1
Lưu ý rằng 1/1000con người cũng có thể đọc được, nhưng thuật toán trên sẽ chỉ tạo ra một 1/10xấp xỉ rất thô ; Tôi tin rằng những cải tiến có thể được thực hiện trong điều khoản trong đó mẫu số mà con người có thể đọc được một người có thể chọn từ, và / hoặc bổ sung <, >, <<, >>tiền tố để đưa ra một ý tưởng về thô của xấp xỉ.
vladr

16

Đây là một liên kết giải thích toán học đằng sau việc chuyển đổi một số thập phân thành một phân số:

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

Và đây là một chức năng ví dụ về cách thực sự làm điều đó bằng VB (từ 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

(Từ các tìm kiếm của google: chuyển đổi thập phân thành phân số, chuyển đổi mã thập phân thành phân số)


2
Lưu ý rằng thuật toán này mất Ω (m) thời gian khi f = n / m. Và đó có thể là rất nhiều, ngay cả khi bạn không có ý định đó (hãy xem xét 0,66666666667).
einpoklum

10

Bạn có thể muốn đọc Những điều mà mọi nhà khoa học máy tính nên biết về số học dấu chấm động .

Bạn sẽ phải chỉ định một số độ chính xác bằng cách nhân với một số lớn:

3.141592 * 1000000 = 3141592

thì bạn có thể tạo ra một phân số:

3 + (141592 / 1000000)

và giảm qua GCD ...

3 + (17699 / 125000)

nhưng không có cách nào để lấy ra phân số dự định . Thay vào đó, bạn có thể muốn luôn sử dụng phân số trong toàn bộ mã của mình - chỉ cần nhớ giảm phân số khi bạn có thể để tránh tràn!


9

Dưới đây là các phiên bản Perl và Javascript của mã VB do devinmoore đề xuất:

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

Và javascript gần như giống hệt nhau:

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

Triển khai 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

Một phần của vấn đề là rất nhiều phân số không thực sự dễ dàng được hiểu thành phân số. Ví dụ: 0,33 không phải là 1/3 mà là 33/100. Nhưng nếu bạn nhớ quá trình đào tạo ở trường tiểu học của mình, thì có một quá trình chuyển đổi các giá trị thập phân thành phân số, tuy nhiên nó không chắc mang lại cho bạn những gì bạn muốn vì hầu hết thời gian các số thập phân không được lưu trữ ở 0,33 mà là 0,329999999999998 hoặc một số tương tự.

Hãy tự giúp mình và đừng bận tâm đến điều này, nhưng nếu cần thì bạn có thể làm như sau:

Nhân giá trị ban đầu với 10 cho đến khi bạn loại bỏ phần phân số. Giữ nguyên số đó và sử dụng nó làm số chia. Sau đó, thực hiện một loạt các đơn giản hóa bằng cách tìm kiếm các mẫu số chung.

Vì vậy, 0,4 sẽ là 4/10. Sau đó, bạn sẽ tìm các ước số chung bắt đầu bằng các giá trị thấp, có thể là số nguyên tố. Bắt đầu với 2, bạn sẽ xem liệu 2 có chia đều cả tử số và mẫu số hay không bằng cách kiểm tra xem tầng của phép chia có giống với phép chia không.

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

Vì vậy 5 không chia 2 đều. Vì vậy, sau đó bạn kiểm tra số tiếp theo, giả sử 3. Bạn làm điều này cho đến khi bạn đạt bằng hoặc cao hơn căn bậc hai của số nhỏ hơn.

Sau khi bạn làm điều đó thì bạn cần


1
Tôi khuyên bạn nên sử dụng thuật toán euclid cho bước cuối cùng đó
Graphics Noob


4

"Giả sử chúng ta có 0,33, chúng ta cần xuất ra" 1/3 "."

Bạn mong đợi "giải pháp" có độ chính xác nào? 0,33 không bằng 1/3. Làm thế nào để bạn nhận ra một câu trả lời "tốt" (dễ đọc)?

Không có vấn đề gì, một thuật toán khả thi có thể là:

Nếu bạn muốn tìm một phân số gần nhất ở dạng X / Y trong đó Y nhỏ hơn 10, thì bạn có thể lặp lại tất cả 9 Y có thể, đối với mỗi Y tính X, rồi chọn cái chính xác nhất.


3

Tôi nghĩ rằng cách tốt nhất để làm điều này là trước tiên chuyển đổi giá trị float của bạn thành biểu diễn ascii. Trong C ++, bạn có thể sử dụng ostringstream hoặc trong C, bạn có thể sử dụng sprintf. Đây là cách nó sẽ trông như thế nào trong 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;

Một cách tiếp cận tương tự có thể được thực hiện trong C.

Sau đó, bạn cần kiểm tra xem phân số có ở mức thấp nhất hay không. Thuật toán này sẽ đưa ra câu trả lời chính xác, tức là 0,33 sẽ xuất ra "33/100", không phải "1/3". Tuy nhiên, 0,4 sẽ cho "4/10", khi giảm xuống các điều khoản thấp nhất sẽ là "2/5". Điều này có thể không mạnh mẽ như giải pháp của EppStein, nhưng tôi tin rằng điều này đơn giản hơn.


8 năm sau, tôi gặp giải pháp của bạn, tôi đã thử nghiệm và nó đang hoạt động hoàn hảo cho đến nay, nhưng bạn nói rằng nó không mạnh bằng giải pháp của EppStein và tôi tự hỏi tại sao. Vì giải pháp của bạn đơn giản hơn nhiều nên đây không phải là giải pháp được lựa chọn, không phải chúng tôi muốn tạo mã đơn giản nhất có thể miễn là nó hoạt động và an toàn ??
HBatalha

3

Một giải pháp tích hợp trong R:

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

Điều này sử dụng phương pháp phân số liên tục và có tùy chọn cyclesmax.denominatorđối số để điều chỉnh độ chính xác.


Ngoài ra library(numbers)contFrac(0.6666); để có được đầu ra chuỗi như mong muốn:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt

2

Bạn sẽ phải tìm ra mức độ lỗi mà bạn sẵn sàng chấp nhận. Không phải tất cả các phân số thập phân đều giảm thành phân số đơn giản. Tôi có lẽ sẽ chọn một số dễ chia, như 60, và tìm xem có bao nhiêu phần 60 gần nhất với giá trị, sau đó đơn giản hóa phân số.


2

Bạn có thể thực hiện việc này bằng bất kỳ ngôn ngữ lập trình nào bằng các bước sau:

  1. Nhân và Chia cho 10 ^ x trong đó x là lũy thừa của 10 cần thiết để đảm bảo rằng số đó không còn chữ số thập phân. Ví dụ: Nhân 0,33 với 10 ^ 2 = 100 để được 33 và chia cho cùng để được 33/100
  2. Giảm tử số và mẫu số của phân số kết quả bằng cách phân tích thừa số, cho đến khi bạn không còn có thể nhận được số nguyên từ kết quả.
  3. Kết quả phân số rút gọn sẽ là câu trả lời của bạn.

Ví dụ: 0,2 = 0,2 x 10 ^ 1/10 ^ 1 = 2/10 = 1/5

Vì vậy, đó có thể được đọc là '1 phần trong số 5'


2

Một giải pháp là chỉ lưu trữ tất cả các số dưới dạng số hữu tỉ ngay từ đầu. Có các thư viện cho số học hữu tỉ (ví dụ: GMP ). Nếu sử dụng ngôn ngữ OO, bạn có thể chỉ sử dụng thư viện lớp số hữu tỉ để thay thế lớp số của mình.

Các chương trình tài chính, trong số những chương trình khác, sẽ sử dụng một giải pháp như vậy để có thể tính toán chính xác và duy trì độ chính xác có thể bị mất bằng cách sử dụng phao cơ.

Tất nhiên nó sẽ chậm hơn rất nhiều vì vậy nó có thể không thực tế cho bạn. Phụ thuộc vào lượng tính toán bạn cần thực hiện và độ chính xác quan trọng đối với bạn.

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

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

2

Giả sử chúng ta có 0,33, chúng ta cần xuất ra "1/3". Nếu chúng ta có "0,4", chúng ta cần xuất ra "2/5".

Nó sai trong trường hợp phổ biến, vì 1/3 = 0.3333333 = 0. (3) Hơn nữa, không thể tìm ra từ các giải pháp được đề xuất ở trên là số thập phân có thể được chuyển đổi thành phân số với độ chính xác xác định, vì đầu ra luôn là phân số.

NHƯNG, tôi đề xuất hàm toàn diện của mình với nhiều tùy chọn dựa trên ý tưởng về chuỗi hình học Vô hạn , cụ thể là trên công thức:

nhập mô tả hình ảnh ở đây

Lúc đầu, hàm này cố gắng tìm chu kỳ của phân số trong biểu diễn chuỗi. Sau đó, công thức được mô tả ở trên được áp dụng.

Mã số hữu tỉ được mượn từ cách triển khai số hữu tỉ Stephen M. McKamey trong C #. Tôi hy vọng không quá khó để chuyển mã của tôi sang các ngôn ngữ khác.

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

Có một số ví dụ về việc sử dụng:

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;

Trường hợp của bạn với phần bên phải phần không được cắt tỉa:

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;

Thời gian tạm dừng tối thiểu:

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.

Làm tròn ở cuối:

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

Trường hợp thú vị nhất:

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.

Các bài kiểm tra và mã khác mà mọi người có thể tìm thấy trong thư viện MathFunctions của tôi trên github .


2

Ruby đã có một giải pháp tích hợp sẵn:

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

Trong Rails, các thuộc tính số ActiveRecord cũng có thể được chuyển đổi:

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

2

Trả lời trong C ++, giả sử rằng bạn có lớp 'BigInt', lớp này có thể lưu trữ các số nguyên có kích thước không giới hạn.

Thay vào đó, bạn có thể sử dụng 'unsigned long long', nhưng nó sẽ chỉ hoạt động cho một số giá trị nhất định.

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) sẽ trả về "+0/1", vì vậy bạn có thể muốn xử lý trường hợp này riêng.

Tái bút: Tôi đã sử dụng mã này trong lớp 'RationalNum' của riêng mình trong vài năm và nó đã được kiểm tra kỹ lưỡng.


Ví dụ của bạn dường như bị phá vỡ trên các giá trị như 1.333333 .. nó đi vào một vòng lặp rất dài cố gắng để tìm ra giá trị và dường như không làm việc ... không tốt với giá trị đơn giản khác như 1,25
Adamski

@Adamski: Cảm ơn. Chu kỳ "hội tụ" của whilevòng lặp được giới hạn bởi kích thước double, thường là 64 bit. Vì vậy nó không phụ thuộc vào giá trị ban đầu của input ( val). Các GCDchức năng, tuy nhiên, không phụ thuộc vào giá trị này, mặc dù nó thường tụ tới một giải pháp khá nhanh chóng. Có thể là bạn đã không thực hiện chức năng này đúng cách?
barak manos

@Adamski: Ngoài ra, như tôi đã đề cập ở phần đầu của câu trả lời, nếu bạn đang sử dụng unsigned long longthay vì BigInt, thì nó sẽ không nhất thiết mang lại kết quả chính xác cho mọi giá trị đầu vào ... Nhưng ngay cả trong trường hợp đó, mã vẫn không được cho là "đi vào một vòng lặp rất dài".
barak manos

À, vâng, điều đó hoàn toàn có thể xảy ra, hàm GCD mà tôi đang sử dụng là một phần của lớp BigInteger của thư viện Juce. Cảm ơn vì thông tin!
Adamski

@Adamski: Vì vậy không có nghĩa là GCDchức năng không được triển khai đúng cách. Bạn đã kiểm tra xem mã chạy trong một thời gian dài trong whilevòng lặp hoặc sau nó? Tôi sẽ kiểm tra giá trị 1.33333, để xem điều gì đằng sau điều này. Cảm ơn.
barak manos

2

Thuật toán này của Ian Richards / John Kennedy không chỉ trả về các phân số đẹp mà nó còn hoạt động rất tốt về mặt tốc độ. Đây là mã C # được lấy từ câu trả lời này của tôi.

Nó có thể xử lý tất cả double giá trị ngoại trừ các giá trị đặc biệt như NaN và +/- infinity mà bạn sẽ phải thêm nếu cần.

Nó trả về một new Fraction(numerator, denominator) . Thay thế bằng loại của riêng bạn.

Để biết thêm các giá trị mẫu và so sánh với các thuật toán khác, hãy truy cập vào đây

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

Các giá trị mẫu được thuật toán này trả về:

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ạn sẽ gặp phải hai vấn đề cơ bản sẽ khiến việc này trở nên khó khăn:

1) Dấu phẩy động không phải là một biểu diễn chính xác có nghĩa là nếu bạn có một phần của "x / y" dẫn đến giá trị là "z", thì thuật toán phân số của bạn có thể trả về một kết quả khác với "x / y".

2) Có vô số số vô tỉ hơn số hữu tỉ. Một số hữu tỉ là một số có thể được biểu diễn dưới dạng phân số. Phi lý trí là những người không thể.

Tuy nhiên, theo một cách rẻ tiền, vì dấu phẩy động có giới hạn độ chính xác, nên bạn luôn có thể biểu diễn nó dưới dạng một số phe phái. (Tôi nghĩ...)


4
Một float (hoặc double) một phân số. Mẫu số của nó là một lũy thừa của 2. Đó là lý do tại sao chúng không thể biểu diễn chính xác một số số hữu tỉ.
erickson

1

Đã hoàn thành mã trên và chuyển đổi thành 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";
    }

Cảm ơn, tôi sử dụng này cho Delphi, dễ dàng hơn để cổng hơn tất cả những thứ xoăn mà
Peter Turner

1

Đây là một cách triển khai nhanh chóng và bẩn thỉu trong javascript sử dụng cách tiếp cận brute force. Không hề được tối ưu hóa, nó hoạt động trong một phạm vi phân số được xác định trước: 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));

Điều này được lấy cảm hứng từ cách tiếp cận được sử dụng bởi JPS.


0

Như nhiều người đã nói, bạn thực sự không thể chuyển đổi một dấu phẩy động trở lại một phân số (trừ khi nó cực kỳ chính xác như .25). Tất nhiên, bạn có thể tạo một số kiểu tra cứu một mảng lớn các phân số và sử dụng một số loại logic mờ để tạo ra kết quả bạn đang tìm kiếm. Một lần nữa, điều này sẽ không chính xác và bạn sẽ cần phải xác định các giới hạn thấp hơn về độ lớn mà bạn muốn mẫu số đi.

.32 <x <.34 = 1/3 hoặc tương tự.



0

Tôi đã bắt gặp một giải pháp Haskell đặc biệt thanh lịch sử dụng phép biến hình. Nó phụ thuộc vào gói chương trình đệ quy .

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

Nếu bạn thử điều này trên ghci, nó thực sự hiệu quả!

λ:> approximate pi 2
22 % 7
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.