非舍入分数


22

当您将分数转换为十进制数并想要存储该数时,通常必须舍入该数,因为您只想使用一定数量的内存。假设您只能存储5个十进制数字,则5/3变为1.6667。如果只能存储2个十进制数字,则它将是1.7(现在假设它始终在0到9.99之间...)。

如果您现在尝试使用1.7逆转该过程,并且想要取回小数,那将是困难的,因为您知道1.7只是一个四舍五入的数字。当然,您可以尝试17/10,但与“优雅” 5/3相比,这是一个“丑陋”的分数。

因此,现在的目标是找到分母为b的分数a / b,在正确舍入后得出舍入的十进制数。

细节

输入包含1到5位数字的字符串,介于0(包括)和10(不包括)之间,并带有'。'。在第一个数字之后。假设n表示位数。输出必须是两个整数的列表/数组,[numerator, denominator]或者是一个分子为非负数且分母为正数的有理数据类型(可以创建自己的数据类型或使用内置数据类型)。正确四舍五入为n数字(即n-1小数点后的数字)时,分数分子/分母必须等于输入。

限制:仅允许一个循环语句。这意味着您在整个代码中只能使用一个循环语句(如foror whilegotoetc,以及功能循环如mapfold将代码应用于列表/数组的每个元素),但是您可以随意“滥用”它或使用递归等

您应该编写一个函数。如果您的语言没有功能(即使没有),则可以选择将输入存储在变量中(或通过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中的Foe示例repeat创建了其参数的无限列表。我似乎循环了,但实际上它的时间复杂度为O(1)。但是我想单独对每种情况进行排序总比不允许使用功能语言更好。
骄傲的haskeller 2014年

3
我不喜欢“循环”的当前定义。例如,在Python中,for n in numbers: f(g(n))等效于map(f, map(g, numbers))。功能版本使用map两次,真的应该禁止吗?
flornquake 2014年

1
@MartinBüttner我谈到了由于歧义而不允许使用功能语言的情况
引以为豪的haskeller

1
很抱歉,由于我对函数式编程的知识基本上为零,因此我无法为该讨论做出真正的贡献。如果您不确定该解决方案是否符合“规则”,请始终提交该解决方案!最后,这应该是一个有趣且具有教育意义的挑战!
瑕疵的

2
@Dennis不,那是不幸的措词,您可以按自己喜欢的任何形式提交它,该段的主要思想是,如果您的语言需要更多字节来“读取”输入数字,则您应该不会处于不利地位。
瑕疵的

Answers:


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
Tom Chantler 2014年

1
+1我从没想到会在这里看到SQL语言!!!
瑕疵的

@TomChantler我怀疑你的意思是最终:)
MickyT 2014年

@flawr说实话,我不认为它会工作..虽然蛮力的方法。
MickyT 2014年

12

哈斯克尔,62 59

如果只是名字没那么长...

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

这是一个返回Rational值的函数。

说明: 函数approxRational是一个函数,它接受一个浮点数和一个浮点epsilon并返回最简单的有理数,该有理数在输入的距离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 2014年

4
@COTO好吧,这是Haskell,它充满了学术研究。
骄傲的haskeller 2014年

7

Ruby,127125字节

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 2014年

等一下,输入的是字符串?Dang,这意味着我可以从代码中提取一些东西。
Tally 2014年

您的输出0.001与OP不匹配,因为Rationalize它不受最小化分母的约束。正如我对骄傲的哈斯凯勒(Haskeller)所述,使分母最小化的有理逼近函数是非常深奥的(简而言之,因为这是一种糟糕且效率低的近似数字方法)。我通常不希望它成为标准库函数。
COTO 2014年

@COTO根据文档,它确实最小化了分母。
Martin Ender 2014年

@MartinBüttner:它输出有点有趣1/999。999仅在大约1e-6和2e-6之间的误差时才成为(可接受的)最低分母。错误界限显然是5e-4。因此,无论Mathematica在那种情况下进行的操作,都绝对无法满足规范要求。:P
COTO 2014年

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

GNU dc,72个字节

无回路-直流甚至没有回路。相反,该控件来自单个尾递归宏-直流惯用。

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

ew 此答案部分解释。


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

确实很简单,而且我认为它在任何地方收敛的速度都没有其他解决方案快,因为分子和分母仅增加一个。我主要想找到解决这个问题的简单方法。我将不得不看其他答案,看看那里发生了什么聪明的事情。

输出量

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

这里有人庆祝Pi逼近日吗?


不,我只是庆祝τ逼近天= P但我只注意到|。355/113 - PI | <10 ^ -6 =)
flawr

2

Applescript,> 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

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)$算术运算。

原始-慢版本

该函数计算最小的分母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
您可能应该将新版本移到帖子的
开头

@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只有一个递归帮助器函数,没有比这更多的循环了。
骄傲的haskeller 2014年

好吧,我错了,它实际上具有两个递归辅助函数,但按规范来说还可以
骄傲的haskeller 2014年

我并不是要说任何人的解决方案都是无效的,只是想发布一个没有内置合理性的解决方案:)
RT

当然,但是定义本身没有循环的事实很好,事实上,您在帖子中写道:“据我们所知,现代语言的内部合理化()函数具有许多内部循环。” 所以我检查了一下
骄傲的haskeller 2014年

无论如何,该解决方案如何工作?
骄傲的haskeller 2014年

1

纯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)。
  • 只需一个循环
  • 我们检查有理数是否超过或低于小数,并相应增加分母或分子。

输出:

$ 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]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.