Некруглі фракції


22

Коли ви перетворюєте дріб у десяткове число і хочете зберегти це число, вам часто доводиться округляти його, оскільки ви хочете використовувати лише певну кількість пам'яті. Скажімо, ви можете зберігати лише 5 десяткових цифр, тоді 5/3 стає 1,6667. Якщо ви можете зберігати лише 2 десяткових цифри, це буде 1,7 (тепер припустимо, що це завжди між 0 і 9,99 ...).

Якщо ви зараз спробуєте змінити цей процес на 1.7 і хочете повернути свою дріб, це може бути складно, оскільки ви знаєте, що 1.7 - це лише округлене число. Звичайно, ви можете спробувати 17/10, але це досить "потворна" частка порівняно з "елегантною" 5/3.

Таким чином, мета полягає у знаходженні дробу a / b з найменшим знаменником b, що при правильному округлянні призводить до десяткового закругленого числа.

Деталі

Вхід містить рядок з числом від 1 до 5 цифр, що знаходиться між 0 (включаючи) і 10 (не включаючи) із знаком '.' після першої цифри. Скажімо, nпозначає кількість цифр. Вихід повинен бути списком / масивом з двох цілих чисел [numerator, denominator]або раціональним типом даних (ви можете створити свій власний або використовувати вбудований), де чисельник невід'ємний, а знаменник - позитивний. Чисельник / знаменник дробу повинен дорівнювати вводу, коли його правильно округлити до nцифр (що означає n-1цифри після десяткової крапки).

Обмеження: дозволено лише одне твердження циклу. Це означає , що ви можете використовувати тільки один єдиний оператор (як захоплення петлі forабо whileабо gotoт.п., а також функціональні петлі , як mapабо foldщо застосовується код до кожного елементу списку / масиву) в цілому коди, але ви можете «зловживання» його або використовувати рекурсію тощо.

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

Округлення

Округлення повинно відповідати "звичайним" правилам округлення, тобто якщо остання цифра, яка буде відрізана, дорівнює 5 або більше, ви будете округляти, і ви будете округляти в будь-яких інших випадках, наприклад:

4.5494 призведе до округлення до

  • 1 цифра: 5
  • 2 цифри: 4.5
  • 3 цифри: 4,55
  • 4 цифри: 4.549

Приклади

Додайте наступні тестові випадки та інші "цікаві":

Input 1.7     Output 5/3
Input 0.      Output 0/1
Input 0.001   Output 1/667
Input 3.1416  Output 355/113

1
Але у функціональних мовах не існує такого поняття, як цикл. Приклад Foe в haskell repeatстворює нескінченний список його аргументів. Я, здається, циклічно, але він фактично має складність у часі O (1). Але я гадаю, що сортування кожного випадку окремо краще, ніж не допускати функціональних мов.
гордий haskeller

3
Мені не подобається поточне визначення "циклу". Наприклад, у Python for n in numbers: f(g(n))еквівалентно map(f, map(g, numbers)). Функціональна версія використовує mapдвічі, чи справді це заборонено?
flornquake

1
@ MartinBüttner Я говорив про те, що функціональні мови будуть заборонені через неоднозначність
гордий haskeller

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

2
@Dennis Ні, це не прикро формулювання, ви можете подати його в будь-якій формі, яка вам подобається. Основна ідея цього абзацу полягала в тому, що у вас не повинно бути недоліків, якщо ваша мова займає більше байтів для «читання» вхідного номера.
недолік

Відповіді:


4

CJam, 41 40 36 байт

Q'./1=,:L0\{;)_Qd*mo_d2$/LmOQd-}g'/@

Відповідно, рядок введення зберігається в Q, що явно дозволено питанням. Спробуйте в Інтернеті.

Тестові кейси

$ for d in 1.7 0. 0.001 3.1416; do cjam <(echo "\"$d\":Q;
> Q'./1=,:L0\{;)_Qd*mo_d2$/LmOQd-}g'/@
> "); echo; done
5/3
0/1
1/667
355/113

Як це працює

Q'./1=,:L  " Count the number of characters after the dot and store it in L.     ";
0\         " Push 0 (denominator) and swap it with L (dummy value).              ";
{          "                                                                     ";
  ;        " Discard the topmost item from the stack (numerator or dummy value). ";
  )        " Increment the denominator.                                          ";
  _Qd*mo   " Multiply a copy by Double(Q) and round.                             ";
  _d2$/    " Cast a copy to Double and it divide it by the denominator.          ";
  LmO      " Round to L digits.                                                  ";
  Qd       " If the result is not Double(Q),                                     ";
}g         " repeat the loop.                                                    ";
./@        " Push a slash and rotate the denominator on top of it.               ";

15

T-SQL 254

Хоча T-SQL насправді не підходить для подібних речей, спробувати цікаво. Чим ефективніше погано, тим вищий знаменник. Він обмежений знаменником 1000.

Вхід - плаваюча змінна @

WITH e AS(SELECT *FROM(VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(0))n(n)),t AS(SELECT ROW_NUMBER()OVER(ORDER BY(SELECT \))N FROM e a,e b,e c,e d)SELECT TOP 1concat(n.n,'/',d.n)FROM t d,t n WHERE round(n.n/(d.n+.0),len(parsename(@,1)))=@ ORDER BY d.n,n.n

Розбивка запиту

WITH                                      -- Start CTE(Common Table Expression)
 e AS(                                    --Create a set of 10 rows
   SELECT *
   FROM(VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(0))n(n)
 ),
 t AS(                                    
   SELECT ROW_NUMBER()OVER(ORDER BY(SELECT \))N 
   FROM e a,e b,e c,e d                   --Cross join e to produce 1000 numbered rows
 )
SELECT 
  TOP 1                                   --Grab first result
  concat(n.n,'/',d.n)                     --Build output
FROM t d,t n                              --Cross join t against itself for denominator and numerator
WHERE round(
  n.n/(d.n+.0),                           --Force float division with +.0
  len(parsename(@,1))                     --Get rounding length
  )=@                                     --Filter where the rounded result = input
ORDER BY d.n,n.n                          --Order by denominator then numerator

+1. Я це люблю. Я поклав, 3.14159і це гідно дало мені355/113
Том Чантлер,

1
+1 Я ніколи не очікував тут побачити мову SQL !!!
flawr

@TomChantler Я підозрюю, що ти маєш на увазі :)
MickyT

@flawr Якщо чесно, я не думав, що це буде працювати .. Хоча дуже жорстокий метод.
MickyT

12

Хаскелл, 62 59

якби тільки імена не були такими довгими ...

import Data.Ratio
f s=approxRational(read s)$50/10^length s

це функція, що повертає Rationalзначення.

Пояснення: функція approxRational- це функція, яка приймає число поплавця і поплавковий епсилон і повертає найпростіший раціональний, який знаходиться в epsilon відстані вхідного сигналу. в основному, повертає найпростіше наближення поплавця до раціонального на відстані "прощаючої помилки".

давайте використовувати цю функцію для нашого використання. для цього нам потрібно буде розібратися, яка площа поплавців, що округлиться до заданої кількості. то введення цього у approxRationalфункцію отримає нам відповідь.

спробуємо, наприклад, 1,7. найнижчий поплавок, який кругляє до 1,7, - 1,65. будь-яка нижча не буде округлою до 1,7. аналогічно, верхня межа поплавків, яка кругла до 1,7, дорівнює 1,75.
обидві межі є межами вхідного числа +/- 0,05. можна легко показати, що ця відстань завжди є 5 * 10 ^ -(the length of the input - 1)(-1 - це тому, що на вході завжди є "."). звідси код досить простий.

тестові справи:

*Main> map f ["1.7", "0.001", "3.1416"]
[5 % 3,1 % 667,355 % 113]

на жаль, він не працює на "0". тому що функція паркера Haskell не розпізнає a .в кінці поплавця. це можна встановити на 5 байтів, замінивши read sна read$s++"0".


Це цікава функція. Зазвичай такі функції існують для того, щоб знайти найкраще раціональне наближення до числа за найменші кроки, що доказово виконується з використанням усічених подань на тривалі дроби. Крім того, пошук дробу з найменшим знаменником - це більше академічна цікавість. Один звичайно не сподівався б знайти його як стандартну функцію бібліотеки.
COTO

4
@COTO Ну, це Хаскелл, він повний академічних досліджень.
гордий haskeller

7

Рубі, 127 125 байт

f=->n{b=q=r=(m=n.sub(?.,'').to_r)/d=10**p=n.count('0-9')-1
b=r if(r=(q*d-=1).round.to_r/d).round(p).to_f.to_s==n while d>1
b}

Визначає функцію, fяка повертає результат у вигляді a Rational. Наприклад, якщо ви додасте цей код

p f["1.7"]
p f["0."]
p f["0.001"]
p f["3.1416"]

Ви отримуєте

(5/3)
(0/1)
(1/667)
(355/113)

Петля над знаменниками. Я починаю з повної фракції, наприклад, 31416/10000для останнього прикладу. Потім я декрементую знаменник, пропорційно декрементуючи числівник (і округляючи його). Якщо отримані раціональні округлення збігаються з вхідним числом, я пам’ятаю новий кращий дріб.


4

Математика, 49 53 символи

Rationalize[ToExpression@#,5 10^(1-StringLength@#)]&@

Використання:

Rationalize[ToExpression@#,5 10^(1-StringLength@#)]&@"1.7"

Вихід:

5/3

Тестові приклади:

input: 1.7     output: 5/3
input: 0.      output: 0
input: 0.001   output: 1/999
input: 3.1416  output: 355/113

Справа 0,001 вважає мене дивним; оскільки функція раціоналізації не працювала відповідно до її опису, коли вона не знайшла випадку 1/667. Він повинен виводити число з найменшим знаменником, який знаходиться у визначених межах.


2
ха-ха, я використовував саме таке рішення. занадто погано в Haskell, це довше. btw, це не схоже на те, що рішення приймає рядок як вхід, як того вимагає spec.
гордий haskeller

Зачекайте, вхід був рядок? Данг, це означає, що я можу витягнути деякі речі з коду.
Tally

Ваш результат для 0.001не відповідає ОП, оскільки Rationalizeне обмежується мінімізацією знаменника. Як я вже згадував гордий haskeller, функція раціонального наближення, що підлягає мінімізації знаменника, є високоезотеричною (коротше, тому що це хитрий і неефективний спосіб наближення чисел). Я зазвичай не очікував, що це буде стандартна функція бібліотеки.
COTO

@COTO Згідно з документами, це все ж мінімізує знаменник.
Мартін Ендер

@ MartinBüttner: Це цікаво, що це дає результати 1/999. 999 стає (прийнятним) найнижчим знаменником лише для помилки між приблизно 1e-6 та 2e-6. Обмежена помилка очевидно 5e-4. Отже, що б Mathematica не робила в цьому випадку, воно точно не працює. : P
COTO

4

Python 2.7+, 111 символів

Доказ того, що ви можете написати жахливий код будь-якою мовою:

def f(s):
 t,e,y=float(s),50*10**-len(s),1;n=d=x=0
 while x|y:n,d=n+x,d+y;a=1.*n/d;x,y=a<t-e,a>t+e
 return n,d

Вихідні дані

>>> [f(s) for s in ("1.7", "0.", "0.001", "3.1416")]
[(5, 3), (0, 1), (1, 667), (355, 113)]

3

APL, 50

2↑⍎⍕(⍎x←⍞){50>|(10*⍴x)×⍺-⍵÷⍨n←⌊.5+⍺×⍵:n ⍵⋄''}¨⍳1e5

До тих пір, поки ви не будете рахувати evalі toStringяк петлі

Пояснення

Підхід полягає в тому, щоб повторити від 1 до 10000 як знаменник і обчислити чисельник, який найбільше відповідає плавцю, а потім перевірити, чи помилка не в межах. Нарешті, виберіть найменшу пару з усіх знайдених фракцій.

(⍎x←⍞)Візьміть рядовий вхід з екрана, призначте його xта eval
⍳1e5Створіть масив від 1 до 10000
{...}¨Для кожного елемента масиву викликте функцію виклику за допомогою нього (⍎x←⍞)та аргументи (цикл)

⍺×⍵Помножте аргументи
⌊.5+Закруглюйте (додаючи 0,5, то округлюючи вниз)
n←Призначте n
⍺-⍵÷⍨ділити на правий аргумент, а потім відніміть від лівого аргументу
(10*⍴x)×Помножте на 10 на потужність "довжина x"
|Візьміть абсолютне значення
50>Перевірте, якщо менше 50 (довжина становить xще 2 ніж номер dp, тому використовуйте тут 50 замість 0,5)
:n ⍵⋄''Якщо попередня перевірка повертає значення true, поверніть масив nта правильний аргумент, інакше поверніть порожній рядок.

⍎⍕ toStringа потім evalдля отримання масиву всіх чисел у масиві
2↑Виберіть лише перші 2 елементи, що знайдено першу пару чисельник-знаменник


2

GNU dc, 72 байти

Немає циклів - dc навіть не має їх. Натомість контроль надходить з одного хвостово-рекурсивного макро - ідіоматичного для постійного струму.

?dXAr^d2*sf*sq1sd0[ld1+sd]sD[r1+r]sN[dlf*ld/1+2/dlq>Ndlq<Dlq!=m]dsmxpldp

Вихід:

$ for n in 1.7 0. 0.001 3.1416; do echo "    n = $n:"; dc unround.dc <<< $n; done
    n = 1.7:
5
3
    n = 0.:
0
1
    n = 0.001:
1
667
    n = 3.1416:
355
113
$ 

Phew. Часткове пояснення у цій відповіді .


2

Математика, 111 символів

f=Module[{a=0,b=1,k},While[Round[a/b,10^-(StringLength[#]-2)]!=(k=ToExpression)@#,If[N[a/b]>k@#,b++,a++]];a/b]&

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

Вихідні дані

f/@{"1.7","0.0","0.001","3.1416","3.14"}
{5/3, 0, 1/667, 355/113, 22/7}

Хтось тут святкує День наближення Пі ?


Ні, я лише святкую день наближення тау. = P Але я щойно помітив, що | 355/113 - пі | <10 ^ -6 =)
недолік

2

Яблукопис,> 300 байт

Я хотів зробити це мовою, яка в основному робить потрібний тип округлення. Виявляється, Applescript підходить до рахунку. Тоді я побачив перелік rounding as taught in school, і не втримався від його використання, незважаючи на очевидну неконкурентоспроможність Applescript для цілей гольфу:

on u(q)
    set n to 0
    set d to 1
    set x to 0
    set AppleScript's text item delimiters to "."
    set f to 10 ^ (q's text item 2's length)
    repeat until x = q as real
        set x to (round n * f / d rounding as taught in school) / f
        if x < q then set n to n + 1
        if x > q then set d to d + 1
    end repeat
    return {n, d}
end u

log my u("1.7")
log my u("0.")
log my u("0.001")
log my u("3.1416")

Це можна трохи більше пограти в гольф, але, мабуть, не варто.

Вихід:

(*5, 3*)
(*0, 1*)
(*1, 667*)
(*355, 113*)

2

До н.е., 151 148 байт

Редагування - швидша та скорочена версія

define f(v){s=scale(x=v);for(i=r=1;i<=10^s;i+=1){t=v*i+1/2;scale=0;p=t/=1;scale=s+1;t=t/i+10^-s/2;scale=s;t=t/1-v;if((t*=-1^(t<0))<r){r=t;n=p;d=i}}}

той же тестовий випадок.

Багато схожий на попередню версію, але замість того, щоб спробувати всі можливі n / d комбінації, ми піднімаємося над залишками v та зворотних коефіцієнтів кратних m == v * d та знаменників d. Знову точність обчислення однакова.

Ось це розплутано:

define f(v)
{
    s= scale(x=v)
    for( i=r=1; i <= 10^s; i+=1 ){
        t= v * i +1/2
        scale=0
        m=t/=1 # this rounded multiple becomes nominator if
               # backward quotient is first closest to an integer
        scale=s+1
        t= t / i +10^-s/2 # divide multiple back by denominator, start rounding again...
        scale=s
        t= t/1 - v # ...rounding done. Signed residue of backward quotient
        if( (t*= -1^(t < 0)) < r ){
            r=t
            n=m
            d=i
        }
    }
}

Ця версія дійсно має єдиний цикл і виконує лише арифметичні операції $ \ Theta \ left (\ operatorname {fractional_decimals} (v) \ right) $.

Оригінал - повільна версія

Ця функція обчислює найменший номінатор n та знаменник d таким чином, що частка n / d, округлена до цифр fractional_decimals (v), дорівнює заданому десятковому значенню v.

define f(v){s=scale(v);j=0;for(i=r=1;j<=v*10^s;){scale=s+1;t=j/i+10^-s/2;scale=s;t=t/1-v;if((t*=-1^(t<0))<r){r=t;n=j;d=i};if((i+=1)>10^s){i=1;j+=1}};v}

тестовий випадок:

define o(){ print "Input ",x,"\tOutput ",n,"/",d,"\n" }
f(1.7); o()
> 0
> Input 1.7       Output 5/3
> 0
f(0.); o()
> 0
> Input 0 Output 0/1
> 0
f(0.001); o()
> 0
> Input .001      Output 1/667
> 0
f(3.1416); o()
> 0
> Input 3.1416    Output 355/113
> 0

І ось воно розплутано:

define f(v)
{
    s=scale(x=v) # save in global for later print
    j=0
    # do a full sequential hill-climb over the residues r of v and all possible
    # fractions n / d with fractional_decimals(v) == s precision.
    for( i=r=1; j <= v * 10^s; ){
        scale=s+1
        t= j / i +10^-s/2 # start rounding...
        scale=s
        t= t/1 - v # ...rounding done. New residue, but still signed
        if( (t*= -1^(t < 0)) < r ){ # absolute residue better?
            # climb hill
            r=t
            n=j
            d=i
        }
        if( (i+=1) > 10^s ){ # next inner step. End?
            # next outer step
            i=1
            j+=1
        }
    }
    v
}

Зізнаюся, я трохи обдурив, емулюючи другий внутрішній цикл всередині однієї зовнішньої петлі, але не використовуючи жодних подальших тверджень про циклі. І тому він фактично виконує арифметичні операції $ \ Theta \ left (v \ operatorname {fractional_decimals} (v) ^ 2 \ right) $ арифметичні операції.


1
ви, ймовірно, повинні перемістити нову версію на передню частину поста
гордий haskeller

@proudhaskeller зроблено
Франки

1

С, 233

Це працює, викликаючи функцію раціоналізації r () зі стартовим знаменником 1. Функція починає збільшувати чисельник і перевіряти на кожному кроці, чи отримане число, округлене до тієї ж кількості цифр, що і оригінал, має ту саму рядок подання як оригінал. Після того, як чисельник збільшений настільки, що результат більший за вихідний, функція збільшує знаменник і викликає себе.

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

Зауважте, що це не працює для введення "0". тому що це не стандартний спосіб запису float, тому коли він повторно записує float у рядок, результат ніколи не буде "0.".

Специфікації хочуть функцію, яка повертає значення, а не просто друк на екран, отже, передача аргументів.

Код (необережений):

void r(char* x, int* a, int* b) {
    int i = -1;
    char z[32];
    double v =atof(x);
    while(1) {
        i++;
        double y = ((double)i)/((double)(*b));
        double w;
        sprintf(z, "%.*f", strlen(strchr(x,'.'))-1, y);
        if(strcmp(x, z)==0) {
            *a = i;
            return;
        }
        w = atof(z);
        if(w > v) {
            (*b)++;
            r(x, a, b);
            return;
        }
    }
}

Використання:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char* argv[]) {
    int num;
    int denom = 1; // start with a denominator of 1
    r(argv[1], &num, &denom);
    printf("%d/%d\n", num, denom);
    return 0;
}

Код для гольфу:

typedef double D;
void r(char*x,int*a,int*b){int i=-1;char z[32];D v=atof(x);while(1){i++;D y=((D)i)/((D)(*b));D w;sprintf(z,"%.*f",strlen(strchr(x,'.'))-1,y);if(!strcmp(x,z)){*a=i;return;}w=atof(z);if(w>v){(*b)++;r(x,a,b);return;}}}

насправді, в реалізації бібліотеки Haskell ( hackage.haskell.org/package/base-4.7.0.1/docs/src/… ) визначення approxRationalмає лише одну рекурсивну функцію помічника, і не більше циклу, ніж це.
гордий haskeller

ну, я помилявся, він насправді має дві рекурсивні функції помічника, але за специфікацією це нормально
гордий haskeller

Я не намагався сказати, що рішення когось недійсні, просто хотів опублікувати його без вбудованої раціоналізації :)
RT

Звичайно, але той факт, що в самій дефініції немає циклів, це приємно, і це фактично, ви писали у своєму дописі "для всіх, що ми знаємо, внутрішні раціоналізуючі () функції сучасних мов мають безліч внутрішніх циклів". тому я перевірив це.
гордий haskeller

все одно, як працює рішення?
гордий haskeller

1

Чистий Баш, 92 байти

Як часткове пояснення цієї відповіді , ось вона передана для удару:

f=${1#*.}
q=${1//.}
for((n=0,d=1;x-q;x=2*10**${#f}*n/d+1>>1,n+=x<q,d+=x>q));{ :;}
echo $n/$d

Зокрема:

  • bash має арифметику лише для цілих чисел. Отже, ми відповідним чином масштабуємо все на 2 * 10 ^ (кількість дробових цифр).
  • bash округляє вниз до найближчого цілого числа; 2 у виразі вище, тому ми можемо замість цього округлити до найближчого цілого числа ( вгору або вниз ).
  • Всього одна петля
  • ми перевіряємо, чи раціонально переступає чи підкреслює десятковий чи збільшуємо знаменник чи чисельник відповідно.

Вихід:

$ for n in 1.7 0. 0.001 3.1416; do echo "    n = $n:"; ./unround.sh $n; done
    n = 1.7:
5/3
    n = 0.:
0/1
    n = 0.001:
1/667
    n = 3.1416:
355/113
$ 

Має бути досить простий-тільки intпорт до c
Digital Trauma

1

JavaScript (E6) 85

F=r=>(l=>{for(n=r,d=1;l&&r!=((n=r*d+1/2|0)/d).toFixed(l);d++);})(r.length-2)||[n|0,d]

Безумовно

F=r=>{
  l = r.length-2; // decimal digits
  if (l==0) return [r|0, 1] // if no decimal return the same (conv to number) with denominator 1

  // loop for increasing denominator 
  for(d = 2; 
      r != ( // loop until find an equal result
      // given R=N/D ==> N=R*D
      (n=r*d+1/2|0) // find possible numerator, rounding (+0.5 and trunc)
      /d).toFixed(l); // calc result to given decimals
      d++);
  return [n,d]
}

Тест в консолі FireFox / FireBug

;["1.7","0.","0.001","3.1416","9.9999"].forEach(v => console.log(v,F(v)))

Вихідні дані

1.7 [5, 3]
0. [0, 1]
0.001 [1, 667]
3.1416 [355, 113]
9.9999 [66669, 6667]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.