Як перетворити поплавці на легкі для читання люди?


103

Скажімо, у нас є 0.33, нам потрібно вивести 1/3.
Якщо у нас є 0.4, нам потрібно вивести2/5 .

Ідея полягає в тому, щоб зробити його зрозумілим для людини, щоб користувач зрозумів " x частини поза y " як кращий спосіб розуміння даних.

Я знаю, що відсотки - це хороший замінник, але мені було цікаво, чи існує простий спосіб зробити це?


Приклад .33=> "1/3"стосується мене; Я б очікував .33=> "33/100". Я припускаю, що ви мали .33...на увазі звичайно, але це ставить питання з питанням - перш ніж ми зможемо вирішити алгоритм, нам потрібно визначитися з очікуваною поведінкою. У відповіді Python @ Debilski використовується те, .limit_denominator()що за замовчуванням використовується максимальний знаменник 10 ^ 7; ймовірно, хороший по замовчуванням на практиці, але це все ще може викликати помилки , якщо ви не будете обережні, і робить повернення "33/100"в .33разі.
dimo414

З будь-яким Мова- , специфічні функції доступні. Незрозуміло, що ви запитуєте, якщо це справді не є простою суперечливістю.
Маркіз Лорн

Відповіді:


70

Я знайшов раціональне наближення Девіда Еппштейна до заданого коду реального числа С саме таким, про що ви просите. Його засновано на теорії безперервних дробів і дуже швидко і досить компактно.

Я використав версії цього персоналізованого для конкретних лімітів чисельника та знаменника.

/*
** 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.ac/fractions-in-ruby і rubygems.org/gems/fraction
пролито

6
Майте на увазі, що є деякі крайні випадки, з якими цей код не дуже добре поводиться: коли його дано -1.3333333 з максимальним знаменником 4, він повертає 4 / -3 з помилкою 3.333333e-08 та -5/4 з помилкою = -8.333330e-02, що правильно. Але коли дано -1,33333337 з однаковим максимальним знаменником, воно виходить 12121211 / -9090908 з помилкою помилки = 4,218847e-15 і -4/3 з помилкою -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)

6
Зауваження щодо реалізації та алгоритму на hg.python.org/cpython/file/822c7c0d27d1/Lib/fractions.py#l211
piro

2
@ Дебільський, який із ОП language agnosticта algorithmтегів задовольняє ваша відповідь?
vladr

2
@vladr Ну, маючи на увазі, що я писав цю відповідь майже 6 років тому (і не один рік після того, як було поставлено запитання), я думаю, я вже не знаю, які тоді були мої міркування. Швидше за все , я мав в виду на цей коментар: stackoverflow.com/questions/95727 / ... OTOH Це може бути також , що ця відповідь була об'єднаний з іншим питанням. Хто може сказати після всіх тих років…
Дебільські

Ви можете додати кілька речень про алгоритм, який використовується модулем дробів (і, можливо, оновити свою відповідь для Python3).
einpoklum

21

Якщо вихід має створити читачеві людину швидкого враження про порядок результату, немає сенсу повертати щось на зразок "113/211", тому вихід повинен обмежуватися використанням одноцифрових чисел (а може бути, 1 / 10 та 9/10). Якщо так, то можна помітити, що існує лише 27 різних фракцій.

Оскільки основна математика для отримання результату ніколи не зміниться, рішенням може бути просто жорстке кодування бінарного дерева пошуку, щоб функція виконувала не більше журналу (27) ~ = 4 3/4 порівнянь. Ось перевірена версія версії коду

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
Це трохи потворно, але дуже швидко і практично
Босак

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
Зверніть увагу, що цей алгоритм займає час Ω (m), коли f = n / m. І це може бути багато, навіть якщо ви цього не мали наміру (врахуйте 0.66666666667).
einpoklum

10

Ви можете прочитати, що повинен знати кожен комп'ютерний арифметик з плаваючою точкою .

Вам потрібно буде вказати деяку точність, помноживши на велику кількість:

3.141592 * 1000000 = 3141592

тоді ви можете зробити дріб:

3 + (141592 / 1000000)

і зменшити за допомогою GCD ...

3 + (17699 / 125000)

але немає можливості вивести передбачувану фракцію. Ви можете замість цього завжди використовувати дроби у коді - просто пам’ятайте, щоб зменшити дроби, коли ви можете уникнути переповнення!


9

Ось версії Perl та Javascript для коду VB, запропонованого devinmoore:

Perl:

sub dec2frac {
    my $d = shift;

    my $df  = 1;
    my $top = 1;
    my $bot = 1;

    while ($df != $d) {
      if ($df < $d) {
        $top += 1;
      }
      else {
         $bot += 1;
         $top = int($d * $bot);
      }
      $df = $top / $bot;
   }
   return "$top/$bot";
}

І майже однаковий javascript:

function dec2frac(d) {

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

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

9

Реалізація AC #

/// <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. Як ви розпізнаєте "добру" (просту для читання) відповідь?

Незважаючи ні на що, можливим алгоритмом може бути:

Якщо ви очікуєте, що ви знайдете найближчий дріб у формі X / Y, де Y менше 10, ви можете циклічно, хоча всі 9 можливих Y, для кожного Y обчислити X, а потім вибрати найбільш точний.


3

Я думаю, що найкращий спосіб зробити це спочатку перетворити своє плаваюче значення в представлення 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;

Аналогічний підхід можна застосувати і в прямому С.

Після цього вам потрібно буде перевірити, чи частка є найнижчою. Цей алгоритм дасть точну відповідь, тобто 0,33 видасть "33/100", а не "1/3". Однак 0,4 дасть "4/10", що при скороченні до найнижчих термінів буде "2/5". Це може бути не настільки потужним, як рішення EppStein, але я вважаю, що це більш просто.


Через 8 років я натрапив на ваше рішення, я протестував, і він працює досконало, але ви сказали, що він не такий потужний, як рішення EppStein, і мені цікаво чому. Оскільки ваше рішення набагато простіше, чи не повинно це бути рішенням вибору, чи не маємо ми на меті робити найпростіший код, доки він працює?
HBatalha

3

Вбудоване рішення в R:

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

Для цього використовується метод продовження дробу та є необов'язковим cyclesта max.denominatorаргументами для регулювання точності.


Також library(numbers)і contFrac(0.6666); щоб отримати вихідний рядок за бажанням:paste(contFrac(0.666, tol=1e-03)$rat, collapse="/")
rbatt

2

Вам доведеться розібратися, який рівень помилки ви готові прийняти. Не всі десяткові дроби зменшуються до простого дробу. Я б, напевно, вибрав число, яке легко ділиться, наприклад, 60, і зрозумів, скільки 60-ти найближче до значення, а потім спростити дріб.


2

Ви можете зробити це будь-якою мовою програмування, виконавши наступні кроки:

  1. Помножте та діліть на 10 ^ х, де х - потужність 10, необхідна для того, щоб число не залишилося десяткових знаків. Приклад: Помножте 0,33 на 10 ^ 2 = 100, щоб зробити його 33, і поділіть його на те саме, щоб отримати 33/100
  2. Зменшіть чисельник та знаменник отриманого дробу за допомогою факторизації, поки ви більше не зможете отримати цілі числа з результату.
  3. Отримана зменшена частка повинна бути вашою відповіддю.

Приклад: 0,2 = 0,2 x 10 ^ 1/10 ^ 1 = 2/10 = 1/5

Отже, це можна прочитати як "1 частина з 5"


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) Більше того, із запропонованих вище рішень неможливо дізнатися, десяткові можуть бути перетворені на дріб з визначеною точністю, оскільки вихід завжди є дробом.

Але я пропоную свою комплексну функцію з багатьма варіантами, заснованими на ідеї нескінченних геометричних рядів , а саме на формулі:

введіть тут опис зображення

Спочатку ця функція намагається знайти період дробу в представленні рядків. Після цього застосовується описана вище формула.

Код раціональних чисел запозичений у впровадження раціональних чисел Стівена М. МакКамі в 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 на github .


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

Відповідь на C ++, припускаючи, що у вас є клас BigInt, який може зберігати цілі числа необмеженого розміру.

Натомість можна використовувати "неподписані довгі довгі", але це працюватиме лише для певних значень.

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", тож ви можете хотіти вирішувати цей випадок окремо.

PS: Я використовував цей код у власному класі "RationalNum" вже кілька років, і він був ретельно перевірений.


Ваш приклад, здається, розбивається на такі величини, як 1.333333 .. він переходить у дуже довгий цикл, намагаючись знайти значення, і, здається, не працює ... робить чудово з іншими простими значеннями, такими як 1,25
Адамський

@Adamski: Дякую Період "конвергенції" whileциклу обмежений розміром double, який, як правило, становить 64 біти. Отже, це не залежить від початкового значення введення ( val). GCDФункція, однак, не залежить від цього значення, хоча , як правило , збігається до вирішення досить швидко. Можливо, ви не виконали цю функцію належним чином?
barak manos

@Adamski: Крім того, як я вже згадував на початку відповіді, якщо ви використовуєте unsigned long longзамість цього BigInt, то це не обов'язково дасть правильний результат для кожного вхідного значення ... Але навіть у такому сценарії код не передбачається "перейти в дуже довгу петлю".
barak manos

Ага так, це цілком можливо, функція GCD, яку я використовував, є частиною класу BigInteger бібліотеки Juce. Спасибі за інформацію!
Адамські

@Adamski: Тому немає сенсу, що GCDфункція не реалізована належним чином. Ви перевіряли, чи працює код тривалий час під час whileциклу чи після нього? Я перевірю значення 1.33333, щоб побачити, що за цим стоїть. Дякую.
barak manos

2

Цей алгоритм Ієна Річардса / Джона Кеннеді не тільки повертає приємні дроби, але і дуже швидко справляється з точки зору швидкості. Це код C #, який взято з цієї відповіді .

Він може впоратися з усіма double значення, крім спеціальних значень, таких як NaN та +/- нескінченність, які вам доведеться додати за потреби.

Він повертає a 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) Плаваюча точка не є точним поданням, що означає, що якщо у вас є частка "x / y", що призводить до значення "z", ваш алгоритм дробу може повернути результат, відмінний від "x / y".

2) Є нескінченність набагато більше ірраціональних чисел, ніж раціональних. Раціональне число - це те, яке можна представити дробом. Нераціонально бути тими, що не може.

Однак дешевим способом, оскільки плаваюча точка має граничну точність, то ви завжди можете представляти її як якусь форму фракції. (Я думаю...)


4
Поплавок (або подвійний) - дріб. Його знаменник - сила 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";
    }

Дякую, я використав це для Delphi, простішого в порту, ніж усіх тих фігурних речей
Пітер Тернер

1

Ось швидка та брудна реалізація в javascript, яка використовує грубу силу підходу. Зовсім не оптимізований, він працює в заздалегідь заданому діапазоні дробів: 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.