float를 사람이 읽을 수있는 분수로 변환하는 방법은 무엇입니까?


103

을 가지고 있다고합시다 . 0.33출력해야합니다 1/3.
우리가 가지고 있다면 0.4, 우리는 출력해야합니다 2/5.

아이디어는 사용자가 데이터를 이해하는 더 나은 방법으로 " x 부분 y "을 이해하도록 사람이 읽을 수 있도록 만드는 것입니다.

백분율이 좋은 대체물이라는 것을 알고 있지만 이렇게하는 간단한 방법이 있는지 궁금합니다.


.33=> "1/3"예 우려 나; 나는 기대할 것이다 .33=> "33/100". .33...당연히 당신이 의미한다고 생각 하지만 그것은 질문에 문제를 노출시킵니다. 알고리즘을 결정하기 전에 예상되는 동작을 결정해야합니다. @Debilski의 Python 답변은 .limit_denominator()기본값이 10 ^ 7 인 최대 분모를 사용합니다 . 실제로 아마 좋은 기본, 그러나 이것은 여전히 조심하지 않으면 버그를 소개하고 있습니다 않습니다 반환 "33/100".33경우.
dimo414

무엇으로 language- 을 구체적으로의 기능을 사용할 수 있습니다. 당신이 무엇을 요구하는지 불분명합니다. 만약 그것이 정말로 단순한 모순이 아니라면 말입니다.
론의 후작

답변:


70

나는 David Eppstein의 주어진 실수 C 코드에 대한 합리적인 근사치를 찾는 것이 정확히 당신이 요구하는 것임을 발견했습니다. 연속 분수 이론을 기반으로하며 매우 빠르고 상당히 간결합니다.

특정 분자 및 분모 제한에 맞게 사용자 정의 된 버전을 사용했습니다.

/*
** 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에서 솔루션을 찾는 분들에게는 운이 좋습니다! Christopher Lord는 Ruby gem에서 위의 알고리즘을 구현했습니다. 참조 christopher.lord.ac/fractions-in-rubyrubygems.org/gems/fraction
쉐드

6
이 코드가 잘 처리하지 못하는 경우가 있습니다. 최대 분모가 4 인 -1.3333333이 주어지면 오류가 3.333333e-08이고 -5/4가 오류 = 인 경우 4 / -3을 반환합니다. -8.333330e-02, 맞습니다. 그러나 동일한 최대 분모로 -1.33333337이 주어지면 오류 = 4.218847e-15 및 -4/3 오류로 12121211 / -9090908이되고 -3.666667e-08 오류는 올바르지 않습니다. 이것은 특히 -4/3와 같은 계산 된 부동 소수점 숫자로 알고리즘을 제시 할 때 문제가되며, 이는 이와 같은 잘못된 결과를 생성합니다.
edsko

26

Python 2.6부터는 fractions모듈이 있습니다.

(문서에서 인용.)

>>> 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 귀하의 답변이 만족 하는 OP language agnosticalgorithm태그는 무엇입니까?
vladr

2
@vladr 글쎄요, 제가이 답변을 거의 6 년 전에 썼다는 점을 감안할 때 (그리고 질문을받은 지 1 년이 넘었습니다), 그 당시 제 추론이 무엇이 었는지 더 이상 알 수없는 것 같습니다. 아마도 나는이 주석을 언급하고 있었을 것입니다. stackoverflow.com/questions/95727/… OTOH이 답변이 다른 질문에서 병합되었을 수도 있습니다. 그 모든 세월 후에 누가 말할 수
있습니까

fractions 모듈에서 사용하는 알고리즘에 대해 몇 문장을 추가 할 수 있습니다 (그리고 아마도 Python3에 대한 답변을 업데이트 할 수 있습니다).
einpoklum

21

출력이 인간 독자에게 결과의 순서에 대한 빠른 인상을주는 것이라면 "113/211"과 같은 결과를 반환하는 것은 의미가 없으므로 출력은 한 자리 숫자를 사용하는 것으로 제한되어야합니다 (1 / 10 및 9/10). 그렇다면, 당신은 단지 27가 있음을 관찰 할 수있는 다른 분수.

출력을 생성하는 기본 수학은 절대 변경되지 않으므로 해결책은 이진 검색 트리를 간단히 하드 코딩하여 함수가 최대 log (27) ~ = 4 3/4 비교를 수행하도록하는 것입니다. 다음은 코드의 테스트 된 C 버전입니다.

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

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

    if (d == 0.0)
        return rval;

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

    return rval;
}

3
이것은 우리가 더 필요로하는 일종의 측면 적 사고입니다! 훌륭한 제안입니다.
edsko

1
조금 못 생겼지 만 매우 빠르고 실용적인 방법
Bosak

1
이것은 놀랍도록 간단한 흥미로운 접근 방식입니다. 공간을 절약하기 위해 대신 배열을 이진 검색하거나 이진 트리를 만들 수 있지만 접근 방식은 아마도 조금 더 빠를 것입니다 (반환하기 전에 strcat에 대한 단일 호출을 사용하여 공간을 절약하고 현재 호출되는 var을 할당 할 수 있습니다). 또한 나는 3/10과 7/10을 포함했을 것입니다. 그러나 아마도 그것은 나일 것입니다.
jimhark

1
이 솔루션에서 영감을 받아 짧은 (완전히 최적화되지 않은) 코드를 만들었습니다. 더 넓은 범위의 분수를 포함하도록 쉽게 확장 할 수 있습니다. jsfiddle.net/PdL23/1
Deepak Joy

1
참고 1/1000도 매우 인간적 판독하지만 상기 알고리즘은 단지 매우 조 생산할 것이다 1/10근사; 나는 개선 인간적으로 읽을 수 분모가 하나에서 선택할 수있는 관점에서 만들어진, 및 / 또는 추가 할 수 있다고 생각합니다 <, >, <<, >>접두사가 근사의 거칠기의 아이디어를 제공합니다.
vladr

16

다음은 소수를 분수로 변환하는 수학을 설명하는 링크입니다.

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

다음은 VB를 사용하여 실제로 수행하는 방법에 대한 예제 함수입니다 (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

(Google 검색에서 : 소수를 분수로 변환, 소수를 분수 코드로 변환)


2
이 알고리즘은 f = n / m 일 때 Ω (m) 시간이 걸립니다. 그리고 그것은 당신이 의도하지 않았더라도 많은 것일 수 있습니다 (0.66666666667을 고려하십시오).
einpoklum

10

모든 컴퓨터 과학자가 부동 소수점 산술에 대해 알아야 할 사항 을 읽고 싶을 수 있습니다 .

큰 수를 곱하여 정밀도를 지정해야합니다.

3.141592 * 1000000 = 3141592

그런 다음 분수를 만들 수 있습니다.

3 + (141592 / 1000000)

GCD를 통해 줄이기 ...

3 + (17699 / 125000)

그러나 의도 한 분수 를 얻을 수있는 방법은 없습니다 . 대신 코드 전체 에서 항상 분수를 사용 하고 싶을 수 있습니다 . 오버플로를 방지 할 수있을 때 분수를 줄이는 것을 잊지 마십시오!


9

다음은 devinmoore가 제안한 VB 코드의 Perl 및 Javascript 버전입니다.

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

그리고 거의 동일한 자바 스크립트 :

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 # 구현

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

문제의 일부는 너무 많은 분수가 실제로 분수로 쉽게 해석되지 않는다는 것입니다. 예를 들어 0.33은 1/3이 아니라 33/100입니다. 그러나 초등학교 훈련을 기억한다면 소수 값을 분수로 변환하는 과정이 있지만 대부분의 경우 소수가 0.33에 저장되지 않고 0.329999999999998 또는 그와 같은 값으로 저장되기 때문에 원하는 것을 제공하기 어려울 것입니다.

자신에게 호의를 베풀고 이것에 신경 쓰지 마십시오. 그러나 필요한 경우 다음을 수행 할 수 있습니다.

분수 부분을 제거 할 때까지 원래 값에 10을 곱합니다. 그 숫자를 유지하고 제수로 사용하십시오. 그런 다음 공통 분모를 찾아 일련의 단순화를 수행하십시오.

따라서 0.4는 4/10이됩니다. 그런 다음 낮은 값, 아마도 소수로 시작하는 공약수를 찾습니다. 2로 시작하면 나눗셈의 바닥이 나눗셈 자체와 같은지 확인하여 2가 분자와 분모를 균등하게 나눕니다.

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

따라서 5는 2를 균등하게 나누지 않습니다. 그래서 다음 숫자를 확인합니다. 3이라고 말하면 더 작은 숫자의 제곱근에 도달하거나 그 이상에 도달 할 때까지이 작업을 수행합니다.

그렇게 한 후에는


1
마지막 단계에서 유클리드 알고리즘을 사용하는 것이 좋습니다
Graphics Noob


4

"0.33이 있다고 가정 해 봅시다."1/3 "을 출력해야합니다."

"솔루션"의 정확도는 어느 정도입니까? 0.33은 1/3이 아닙니다. "좋은"(읽기 쉬운) 대답을 어떻게 인식합니까?

무엇이든 가능한 알고리즘은 다음과 같습니다.

Y가 10보다 작은 X / Y 형식에서 가장 가까운 분수를 찾으려면 각 Y에 대해 가능한 9 개의 Y를 모두 반복하여 X를 계산 한 다음 가장 정확한 분수를 선택할 수 있습니다.


3

이를 수행하는 가장 좋은 방법은 먼저 float 값을 ascii 표현으로 변환하는 것입니다. C ++에서는 ostringstream을 사용하거나 C에서는 sprintf를 사용할 수 있습니다. 다음은 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;

직선 C에서도 비슷한 접근 방식을 취할 수 있습니다.

나중에 분수가 가장 낮은 용어인지 확인해야합니다. 이 알고리즘은 정확한 답을 제공합니다. 즉, 0.33은 "1/3"이 아닌 "33/100"을 출력합니다. 그러나 0.4는 "4/10"을 제공하며 최저 용어로 줄이면 "2/5"가됩니다. 이것은 EppStein의 솔루션만큼 강력하지 않을 수 있지만 이것이 더 간단하다고 생각합니다.


8 년 후 나는 당신의 해결책을 가로 질러 왔고, 나는 테스트를했고 지금까지 완벽하게 작동하고 있지만 당신은 그것이 EppStein의 해결책만큼 강력하지 않다고 말했고 왜 그런지 궁금합니다. 당신의 솔루션이 훨씬 더 간단하기 때문에 이것이 선택의 솔루션이 아니어야합니다. 우리는 그것이 작동하고 안전하다면 가능한 한 가장 간단한 코드를하기위한 것이 아닙니까?
HBatalha

3

R에 내장 된 솔루션 :

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

이것은 연속 분수 방법을 사용하며 정밀도를 조정하기위한 옵션 cyclesmax.denominator인수가 있습니다.


또한 library(numbers)contFrac(0.6666); 원하는대로 문자열 출력을 얻으려면 :paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt

2

어떤 수준의 오류를 수용 할 것인지 파악해야합니다. 모든 소수가 단순 분수로 축소되는 것은 아닙니다. 나는 아마도 60과 같이 쉽게 나눌 수있는 숫자를 선택하고 그 값에 가장 가까운 60 분의 1을 알아 낸 다음 분수를 단순화 할 것입니다.


2

다음 단계를 사용하여 모든 프로그래밍 언어에서이 작업을 수행 할 수 있습니다.

  1. 10 ^ x로 곱하고 나눕니다. 여기서 x는 숫자에 남은 소수 자리가 없는지 확인하는 데 필요한 10의 거듭 제곱입니다. 예 : 0.33에 10 ^ 2 = 100을 곱하여 33으로 만들고 같은 값으로 나누면 33/100이됩니다.
  2. 결과에서 더 이상 정수를 얻을 수 없을 때까지 인수 분해하여 결과 분수의 분자와 분모를 줄입니다.
  3. 그 결과 감소 된 분수가 답이되어야합니다.

예 : 0.2 = 0.2 x 10 ^ 1 / 10 ^ 1 = 2 / 10 = 1 / 5

그래서 그것은 '5 개 중 1 개'로 읽을 수 있습니다.


2

한 가지 해결책은 처음에 모든 숫자를 유리수로 저장하는 것입니다. 유리수 산술을위한 라이브러리가 있습니다 (예 : GMP ). OO 언어를 사용하는 경우 유리수 클래스 라이브러리를 사용하여 숫자 클래스를 대체 할 수 있습니다.

특히 금융 프로그램은 이러한 솔루션을 사용하여 정확한 계산을 수행하고 일반 부동 소수점을 사용하여 손실 될 수있는 정밀도를 보존 할 수 있습니다.

물론 훨씬 느리므로 실용적이지 않을 수 있습니다. 수행해야하는 계산의 양과 정밀도가 얼마나 중요한지에 따라 다릅니다.

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

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

2

0.33이 있다고 가정하고 "1/3"을 출력해야합니다. "0.4"가 있으면 "2/5"를 출력해야합니다.

1/3 = 0.3333333 = 0이기 때문에 일반적인 경우는 잘못된 것입니다. (3) 또한, 출력은 항상 분수이기 때문에 10 진수를 정의 된 정밀도로 분수로 변환 할 수 있다는 것을 위의 제안에서 알 수 없습니다.

그러나 Infinite geometric series의 아이디어 , 특히 공식에 기반한 많은 옵션을 사용하여 포괄적 인 기능을 제안합니다 .

여기에 이미지 설명 입력

처음에이 함수는 문자열 표현에서 분수의 기간을 찾으려고합니다. 그 후 위에서 설명한 공식이 적용됩니다.

유리수 코드는 C #의 Stephen M. McKamey 유리수 구현에서 차용했습니다 . 내 코드를 다른 언어로 이식하는 것이 그리 어렵지 않기를 바랍니다.

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

사용의 몇 가지 예가 있습니다.

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;

오른쪽 부분 제로 부분 트리밍이있는 경우 :

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;

최소 기간 제거 :

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.

끝에서 반올림 :

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

가장 흥미로운 경우 :

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의 MathFunctions 라이브러리에서 찾을 수있는 다른 테스트 및 코드 입니다.


2

Ruby에는 이미 내장 된 솔루션이 있습니다.

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

Rails에서 ActiveRecord 숫자 속성도 변환 할 수 있습니다.

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

2

크기에 제한이없는 정수를 저장할 수있는 'BigInt'클래스가 있다고 가정하고 C ++로 응답하십시오.

대신 'unsigned long long'을 사용할 수 있지만 특정 값에 대해서만 작동합니다.

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"을 반환하므로이 경우를 별도로 처리하는 것이 좋습니다.

추신 : 저는이 코드를 제 자신의 'RationalNum'클래스에서 수년 동안 사용해 왔으며 철저한 테스트를 거쳤습니다.


귀하의 예는 값을 찾기 위해 노력하고 매우 긴 루프로 전환되고 작동하지 않는 것 ... 1.25와 같은 다른 간단한 값으로 벌금을하지 .. 1.333333 같은 값을 무너 뜨리는 것
Adamski

@Adamski : 감사합니다. while루프 의 "수렴"기간 double은 일반적으로 64 비트 인의 크기로 제한됩니다 . 따라서 입력 ( val) 의 초기 값에 의존하지 않습니다 . 그러나 GCD함수는 일반적으로 솔루션에 매우 빠르게 수렴되지만이 값에 의존합니다. 이 기능을 제대로 구현하지 않았을 가능성이 있습니까?
바락 마 노스

@Adamski : 또한 답변의 시작 부분에서 언급했듯이 unsigned long long대신을 사용 하는 경우 BigInt모든 입력 값에 대해 반드시 올바른 결과를 산출하지는 않습니다 ...하지만 해당 시나리오에서도 코드는 그렇지 않습니다. "매우 긴 루프로 이동"해야합니다.
바락 마 노스

아 네, 가능합니다. 제가 사용했던 GCD 함수는 Juce 라이브러리 BigInteger 클래스의 일부입니다. 정보 주셔서 감사합니다!
Adamski 2014-06-01

@Adamski : 그래서 함수가 제대로 구현 되지 않았다는 것은 말이 되지 않습니다GCD . 코드가 while루프 도중 또는 이후에 오랫동안 실행되는지 확인 했습니까 ? 이 뒤에 무엇이 있는지 알아보기 위해 1.33333의 값을 확인합니다. 감사.
barak manos 2014-06-01

2

Ian Richards / John Kennedy 의이 알고리즘 은 좋은 분수를 반환 할뿐만 아니라 속도 측면에서도 매우 잘 수행됩니다. 이것은 내가이 대답 에서 가져온 C # 코드 입니다.

double필요한 경우 추가해야하는 NaN 및 +/- 무한대와 같은 특수 값을 제외한 모든 값을 처리 할 수 ​​있습니다 .

그것은을 반환합니다 new Fraction(numerator, denominator). 자신의 유형으로 교체하십시오.

더 많은 예제 값과 다른 알고리즘과의 비교를 보려면 여기로 이동하십시오.

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

이 알고리즘이 반환하는 값의 예 :

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

이를 어렵게 만드는 두 가지 기본 문제가 있습니다.

1) 부동 소수점은 정확한 표현이 아닙니다. 즉, "z"값이되는 "x / y"의 분수가있는 경우 분수 알고리즘이 "x / y"이외의 결과를 반환 할 수 있습니다.

2) 이성보다 비이성적 인 수가 무한대가 많다. 유리수는 분수로 표현할 수있는 숫자입니다. 비합리적인 존재는 할 수 없습니다.

그러나 저렴한 방법으로 부동 소수점은 정확도가 제한되어 있으므로 항상 어떤 형태의 파벌로 나타낼 수 있습니다. (내 생각 엔 ...)


4
float (또는 double) 분수입니다. 그것의 분모는 2의 거듭 제곱입니다. 그래서 그들은 어떤 유리수를 정확하게 표현할 수 없습니다.
erickson

1

위 코드를 완성하고 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";
    }

덕분에, 나는 모든 곱슬 물건보다 포트에 쉽게, 델파이이 사용
피터 터너에게

1

다음은 무차별 대입 방식을 사용하는 자바 스크립트의 빠르고 더러운 구현입니다. 전혀 최적화되지 않았으며 사전 정의 된 분수 범위 내에서 작동합니다 : 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));

이것은 JPS에서 사용하는 접근 방식에서 영감을 얻었습니다.


0

많은 사람들이 말했듯이 부동 소수점을 분수로 다시 변환 할 수 없습니다 (.25와 같이 매우 정확하지 않은 경우). 물론 큰 분수 배열에 대한 검색 유형을 만들고 일종의 퍼지 논리를 사용하여 찾고있는 결과를 생성 할 수 있습니다. 다시 말하지만 이것은 정확하지 않으며 분모가 원하는 크기의 하한을 정의해야합니다.

.32 <x <.34 = 1/3 또는 이와 비슷한 것입니다.



0

아나 모피 즘을 사용하는 특히 우아한 Haskell 솔루션을 발견했습니다. 재귀 계획 패키지 에 따라 다릅니다 .

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

ghci에서 이것을 시도하면 실제로 작동합니다!

λ:> approximate pi 2
22 % 7
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.