كيفية تحويل العوامات إلى كسور يمكن قراءتها بواسطة الإنسان؟

StackOverflow https://stackoverflow.com/questions/95727

سؤال

لنفترض أن لدينا 0.33، ونحن بحاجة إلى إخراج "1/3".
إذا كان لدينا "0.4"، فنحن بحاجة إلى إخراج "2/5".

تتمثل الفكرة في جعلها قابلة للقراءة من قبل الإنسان لجعل المستخدم يفهم "أجزاء x من y" كطريقة أفضل لفهم البيانات.

أعلم أن النسب المئوية هي بديل جيد ولكني أتساءل عما إذا كانت هناك طريقة بسيطة للقيام بذلك؟

هل كانت مفيدة؟

المحلول

لقد وجدت ديفيد ابستين إيجاد التقريب العقلاني لعدد حقيقي معين رمز 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]));
}

نصائح أخرى

من 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)

إذا كان الإخراج هو إعطاء القارئ البشري انطباعًا سريعًا عن ترتيب النتيجة، فليس من المنطقي إرجاع شيء مثل "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;
}

إليك رابط يشرح العمليات الحسابية وراء تحويل الكسور العشرية إلى كسر:

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

(من بحث جوجل:تحويل العلامة العشرية إلى كسر، تحويل العلامة العشرية إلى رمز الكسر)

قد ترغب في القراءة ما يجب أن يعرفه كل عالم كمبيوتر عن حساب النقطة العائمة.

سيتعين عليك تحديد بعض الدقة عن طريق الضرب بعدد كبير:

3.141592 * 1000000 = 3141592

ثم يمكنك عمل كسر:

3 + (141592 / 1000000)

وتقليل عبر GCD ...

3 + (17699 / 125000)

ولكن لا توجد وسيلة للحصول على منوي جزء خارج.قد ترغب في ذلك دائماً استخدم الكسور في التعليمات البرمجية الخاصة بك بدلاً من ذلك - تذكر فقط تقليل الكسور عندما تستطيع لتجنب تجاوز السعة!

فيما يلي إصدارات Perl وJavascript من كود VB الذي اقترحه devinmoore:

بيرل:

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

تنفيذ C#

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

ال شجرة ستيرن بروكوت يستحث طريقة طبيعية إلى حد ما لتقريب الأعداد الحقيقية عن طريق الكسور ذات المقامات البسيطة.

جزء من المشكلة هو أن الكثير من الكسور لا يمكن تفسيرها بسهولة على أنها كسور.على سبيل المثال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.افعل ذلك حتى تصل إلى الجذر التربيعي للرقم الأصغر أو أعلى منه.

بعد أن تفعل ذلك فأنت بحاجة

هذه ليست "خوارزمية"، بل مجرد حل بايثون:http://docs.python.org/library/fractions.html

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

"لنفترض أن لدينا 0.33، نحتاج إلى إخراج "1/3"."

ما هي الدقة التي تتوقع أن يتمتع بها "الحل"؟0.33 لا يساوي 1/3.كيف تتعرف على الإجابة "الجيدة" (سهلة القراءة)؟

بغض النظر عن ذلك، يمكن أن تكون الخوارزمية المحتملة:

إذا كنت تتوقع العثور على أقرب كسر في نموذج X/Y حيث Y أقل من 10، فيمكنك تكرار كل Ys التسعة الممكنة، لكل حساب Y X، ثم تحديد الكسر الأكثر دقة.

حل مدمج في R:

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

يستخدم هذا طريقة الكسر المستمر وله اختياري cycles و max.denominator الحجج لضبط الدقة.

سيتعين عليك معرفة مستوى الخطأ الذي ترغب في قبوله.لن يتم اختزال جميع الكسور العشرية إلى كسر بسيط.ربما سأختار رقمًا يسهل القسمة عليه، مثل 60، وأكتشف عدد أجزاء الستين الأقرب إلى القيمة، ثم أقوم بتبسيط الكسر.

يمكنك القيام بذلك بأي لغة برمجة باتباع الخطوات التالية:

  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"

أحد الحلول هو تخزين جميع الأرقام كأرقام منطقية في المقام الأول.توجد مكتبات لحساب الأعداد المنطقية (على سبيل المثال ممارسات التصنيع الجيدة).إذا كنت تستخدم لغة OO، فقد تتمكن فقط من استخدام مكتبة فئة الأعداد النسبية لاستبدال فئة الأرقام الخاصة بك.

ستستخدم البرامج المالية، من بين برامج أخرى، مثل هذا الحل لتتمكن من إجراء حسابات دقيقة والحفاظ على الدقة التي قد يتم فقدها باستخدام التعويم البسيط.

بالطبع سيكون أبطأ كثيرًا لذا قد لا يكون عمليًا بالنسبة لك.يعتمد ذلك على مقدار الحسابات التي يتعين عليك إجراؤها، ومدى أهمية الدقة بالنسبة لك.

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

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

أعتقد أن أفضل طريقة للقيام بذلك هي أولاً تحويل القيمة العائمة إلى تمثيل 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 سوف يخرج "33/100" ، وليس "1/3". ومع ذلك ، فإن 0.4 من شأنه أن يعطي "4/10" ، والتي عندما تقل إلى أدنى مصطلحات ستكون "2/5". قد لا يكون هذا قويًا مثل حل إيبشتاين ، لكنني أعتقد أن هذا أكثر وضوحًا.

لدى روبي بالفعل حل مدمج:

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"

أجب بلغة C++، بافتراض أن لديك فئة "BigInt"، والتي يمكنها تخزين أعداد صحيحة غير محدودة الحجم.

يمكنك استخدام "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
}

راجع للشغل، GetRational(0.0) سيعود "+0/1"، لذلك قد ترغب في التعامل مع هذه الحالة بشكل منفصل.

ملاحظة.:لقد كنت أستخدم هذا الرمز في صفي "RationalNum" لعدة سنوات، وقد تم اختباره بدقة.

هذه الخوارزمية بواسطة إيان ريتشاردز / جون كينيدي لا يُرجع أجزاءً جميلة فحسب، بل إنه يؤدي أيضًا أداءً جيدًا جدًا من حيث السرعة.هذا هو رمز 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) النقطة العائمة ليست تمثيلًا دقيقًا مما يعني أنه إذا كان لديك كسر من "x/y" ينتج عنه قيمة "z"، فقد ترجع خوارزمية الكسر نتيجة أخرى غير "x/y".

2) هناك ما لا نهاية من الأعداد غير المنطقية أكثر من العدد العقلاني.العدد النسبي هو العدد الذي يمكن تمثيله ككسر.كائن غير عقلاني الذي لا يستطيع.

ومع ذلك، بطريقة رخيصة نوعًا ما، نظرًا لأن النقطة العائمة لها دقة محدودة، فيمكنك دائمًا تمثيلها كشكل من أشكال الفصائل.(أظن...)

أكملت الكود أعلاه وقمت بتحويله إلى 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";
    }

لنفترض أن لدينا 0.33، ونحن بحاجة إلى إخراج "1/3".إذا كان لدينا "0.4" ، فنحن بحاجة إلى إخراج "2/5".

من الخطأ في الحالة المشتركة ، بسبب 1/3 = 0.3333333 = 0. (3) ، من المستحيل معرفة ذلك من الحلول المذكورة أعلاه يمكن تحويل عشرية إلى جزء بدقة محددة ، لأن الإخراج دائمًا ما يكون جزءًا.

ولكن، أقترح وظيفتي الشاملة مع العديد من الخيارات بناءً على فكرة سلسلة هندسية لا نهاية لها, ، وتحديداً على الصيغة:

enter image description here

في البداية تحاول هذه الوظيفة العثور على فترة الكسر في تمثيل السلسلة.بعد ذلك يتم تطبيق الصيغة الموصوفة أعلاه.

يتم استعارة رمز الأرقام العقلانية من ستيفن م.ماكامي تنفيذ الأعداد العقلانية في 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;
}

هناك بعض الأمثلة على الاستخدامات:

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.

اختبارات ورموز أخرى يمكن للجميع العثور عليها مكتبتي MathFunctions على جيثب.

فيما يلي تطبيق سريع وقذر في جافا سكريبت يستخدم أسلوب القوة الغاشمة.لم يتم تحسينه على الإطلاق، فهو يعمل ضمن نطاق محدد مسبقًا من الكسور: 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.

كما ذكر العديد من الأشخاص، لا يمكنك حقًا تحويل النقطة العائمة إلى كسر (ما لم تكن دقيقة للغاية مثل .25).بالطبع يمكنك إنشاء نوع من البحث عن مجموعة كبيرة من الكسور واستخدام نوع من المنطق الغامض لإنتاج النتيجة التي تبحث عنها.مرة أخرى، لن يكون هذا دقيقًا وستحتاج إلى تحديد حدود أدنى لمدى الحجم الذي تريد أن يصل إليه المقام.

.32 < x < .34 = 1/3 أو شيء من هذا القبيل.

هنا هو التنفيذ لروبي 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)

لقد عثرت على حل هاسكل الأنيق بشكل خاص والذي يستخدم الصورة المشوهة.ان ذلك يعتمد على مخططات العودية طَرد.

{-# 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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top