丸められていない分数


22

分数を10進数に変換し、その数値を格納する場合、特定の量のメモリのみを使用するため、多くの場合、丸める必要があります。5桁の10進数しか保存できないとすると、5/3は1.6667になります。2桁の10進数しか保存できない場合は、1.7になります(常に0〜9.99の間にあると仮定します...)。

1.7でそのプロセスを逆にしようとして、端数を取り戻したい場合、1.7は丸められた数値にすぎないことがわかっているため、それは難しい場合があります。もちろん、17/10を試すこともできますが、それは「エレガントな」5/3と比べてかなり「ugい」部分です。

そのため、目標は、分母bが最小の分数a / bを見つけることです。これにより、正しく丸められたときに丸められた10進数が得られます。

詳細

入力には、0(含む)から10(含まない)の「。」の1から5桁までの数字の文字列が含まれます。最初の数字の後。n桁数を表すとしましょう。出力は[numerator, denominator]、分子が非負で分母が正である2つの整数のリスト/配列または合理的なデータ型(独自に作成するか、組み込みを使用できます)である必要があります。分数の分子/分母は、n数字(n-1小数点以下の数字を意味する)に正しく丸められる場合、入力と等しくなければなりません。

制限:許可されるループステートメントは1つだけです。つまり、コード全体で単一のループステートメント(foror whileまたはgotoetcなど、およびリスト/配列のすべての要素にコードを適用する、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
しかし、関数型言語にはループのようなものはありません。haskellの敵の例repeatは、引数の無限リストを作成します。ループしているように見えますが、実際にはO(1)の時間の複雑さがあります。しかし、関数型言語を許可しないよりも、各ケースを個別にソートする方が良いと思います。
誇りに思ってhaskeller 14

3
「ループ」の現在の定義が気に入らない。たとえば、Pythonでは、for n in numbers: f(g(n))と同等map(f, map(g, numbers))です。機能バージョンはmap2回使用しますが、それは本当に許可されませんか?
flornquake 14

1
@MartinBüttner私は曖昧さのために関数型言語が許可されない場合について話しました
誇りに思っているhaskeller 14

1
関数型プログラミングに関する私の知識は基本的にゼロであるため、その議論に実際に貢献できないことを残念に思います。「ルール」に準拠しているかどうかわからない解決策がある場合は、とにかく提出してください!最終的には、楽しくて教育的な挑戦になるはずです!
flawr 14

2
@Dennisいいえ、それは残念な言い回しでした。好きな形で提出できます。その段落の背後にある主な考えは、言語が入力数を「読む」ためにより多くのバイトをとっても不利にならないということでした。
flawr 14

回答:


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
トムチャントラー14

1
+1ここでSQL言語が表示されるとは思わなかった!!!
flawr 14

@TomChantler私はあなたが最終的に意味すると思う:)
MickyT 14

@flawr正直なところ、私はそれがうまくいくとは思いませんでした..しかし、非常に強引な方法です。
MickyT 14

12

ハスケル、62 59

名前だけがそれほど長くなかったら...

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

これはRational値を返す関数です。

説明: この関数approxRationalは、浮動小数点数と浮動小数点数イプシロンを受け取り、入力の距離イプシロンにある最も単純な有理数を返す関数です。基本的に、フロートの最も単純な近似値を「許容誤差」距離の有理数に返します。

この関数を利用するために活用しましょう。そのためには、指定された数値に切り上げられるフロートの面積を把握する必要があります。これを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を認識しないためです。これは、で置き換えるread sことで5バイト固定できますread$s++"0"


これは興味深い機能です。通常、そのような関数は、最小のステップで数に対する最良の合理的な近似を見つける目的で存在します。あるいは、最小の分母を持つ分数を見つけることは、学問的な好奇心です。通常、それを標準ライブラリ関数として見つけることは期待していません。
COTO 14

4
@COTOさて、これはHaskellです、学術研究でいっぱいです。
誇りに思ってhaskeller 14

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結果をとして返す関数を定義します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

Mathematica、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の場合は長すぎます。ところで、仕様で要求されているように、ソリューションが入力として文字列を受け取るようには見えません。
誇りに思ってhaskeller 14

入力は文字列でしたか?ダン、それは私がコードからいくつかのものを引き出すことができることを意味します。
タリー14

の出力は、分母を最小化する制約の下にない0.001ため、OPと一致しませんRationalize。誇り高いhaskellerについて述べたように、分母を最小化することを条件とする合理的な近似関数は非常に難解です(要するに、それは数値を近似するお粗末で非効率的な方法だからです)。通常、それが標準のライブラリ関数になるとは期待していません。
COTO 14

@COTOドキュメントによると、それ分母を最小化します。
マーティンエンダー14

@MartinBüttner:出力するのはちょっとおもしろいです1/999。999は、おおよそ1e-6と2e-6の間のエラーに対してのみ(許容される)最低の分母になります。エラー範囲は明らかに5e-4です。そのため、その場合にMathematicaが何をしていても、仕様どおりには動作しません。:P
COTO 14

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

限り、あなたはカウントされませんようevaltoStringループなど

説明

アプローチは、1から10000を分母として繰り返し、フロートに最も近い分子を計算し、エラーが範囲内にあるかどうかを確認することです。最後に、見つかったすべての分数から最小のペアを選択します。

(⍎x←⍞)画面から文字列入力を取得し、に割り当てx、eval
⍳1e51〜10000の配列を生成します。配列の
{...}¨各要素に対して、関数(⍎x←⍞)と引数(ループ)を使用して関数を呼び出します。

⍺×⍵乗算引数
⌊.5+ラウンドオフ(切り捨て、次に0.5を加えることによって)
n←への割り当てをn
⍺-⍵÷⍨右引数で除算し、左引数から減算
(10*⍴x)×「の長さのパワーに10を掛けx、」
|絶対値をとる
50>未満の50(長さかどうかを確認xより2 DPの。無しよりも、その代わりに0.5のここ50を使用)
:n ⍵⋄''の配列を返す、その後、前回のチェックが真nと右引数、他のリターン空の文字列を。

⍎⍕ toStringそしてその後、evalアレイ内のすべての数値の配列を取得する
2↑第一の分子、分母対見出されるのみ第2の要素を選択


2

GNU dc、72バイト

ループなし-DCにはループさえありません。代わりに、コントロールは単一の末尾再帰マクロ(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
$ 

ふう。この答えの部分的な説明。


2

Mathematica、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]&

本当に単純で、分子と分母は1ずつ増加するだけなので、他のソリューションほど速く収束するとは思わない。私は主にこれに対する簡単な解決策を見つけたかった。私は他の答えを見て、そこでどんな巧妙なことが起こるかを見なければなりません。

出力

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

ここのだれかがPi近似デーを祝いますか?


いいえ、私はタウ近似日を祝っています= Pしかし、私はちょうどそれに気づい|。113分の355 -パイ| <10 ^ -6 =)
flawr

2

Applescript、> 300バイト

これを、必要な種類の丸めをネイティブに行う言語で実行したかったのです。Applescriptが法案に合っていることがわかりました。それから私はenumを見て、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

BC、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)$算術演算のみを実行します。

オリジナル-スローバージョン

この関数は、fractional_decimals(v)桁に丸められた端数n / dが指定された10進値vと等しくなるように、最小の分母nと分母dを計算します。

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
}

私は認める、私は単一の外側のループ内で2番目の内側のループをエミュレートすることで少しlittleしましたが、それ以上のループ文は使用しません。そして、それが実際に$ \ Theta \ left(v \ operatorname {fractional_decimals}(v)^ 2 \ right)$算術演算を行う理由です。


1
おそらく新しいバージョンを投稿の先頭に移動する必要があります
誇りに思ってhaskeller 14

行わ@proudhaskeller
Franki

1

C、233

これは、1の開始分母で合理化関数r()を呼び出すことによって機能します。関数は、分子の増分を開始し、増分ごとに、元と同じ桁数に丸められた結果の数が同じ文字列を持っているかどうかをチェックしますオリジナルとしての表現。分子が大きくなり、結果が元の値より大きくなると、関数は分母を増やして自分自身を呼び出します。

もちろん、これははるかに多くのコードを使用しますが、問題の精神はこの必要最小限のアプローチを免れていると思います。知っている限りでは、現代言語の内部のrationalize()関数には多くの内部ループがあります。

これは、「0」の入力では機能しないことに注意してください。これはフロートを書き込むための標準的な方法ではないため、フロートを文字列に再書き込みすると、結果が「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は再帰ヘルパー関数が1つだけあり、それ以上のループはありません。
誇りに思っているhaskeller 14

まあ、私は間違っていました、実際には2つの再帰ヘルパー関数がありますが、仕様では大丈夫です
誇りに思ってhaskeller 14

私は誰かのソリューションが無効であると言っているのではなく、組み込みの合理化なしで1つを投稿したかっただけです:)
RT

もちろん、定義自体にループがないという事実は素晴らしいことであり、事実、あなたは「最新の言語の内部rationalize()関数には多くの内部ループがあります」と書いています。それでチェックしました。
誇りに思ってhaskeller 14

とにかく、ソリューションはどのように機能しますか?
誇りに思ってhaskeller 14

1

Pure Bash、92バイト

この答えの部分的な説明として、ここではbashに移植されています。

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はそうなので、代わりに最も近い整数に丸めることができます(upまたはdown)。
  • ただ1つのループ
  • 有理数が小数をオーバーシュートまたはアンダーシュートしているかどうかを確認し、それに応じて分母または分子をインクリメントします。

出力:

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

intcへのかなり単純な専用ポートである必要があります
デジタルトラウマ14

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]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.