问题很简单,就是设计一个函数,给定n和x,返回对浮点数x取n位有效数字的结果。(隐含要求是:返回的结果被打印出来应该是reasonable的)
函数签名如下:
precise = (n) -> (x) -> x'
这个函数的两个参数被我设计成了科里化的形式,因为precise(n)
返回一个函数,这个函数的语义是“对参数x取n位有效数字”,这个语义还是比较明确的。
这个问题首先能想到有三种解决方法,按照解决方案从优到劣依次为:
然后,开始爬坑了:
首先,浏览了一遍Math的API,没有发现可以直接解决问题的东西。
于是,打算自己通过数值计算实现一个,然后,就写了下面的代码:
precise = (n) -> (x) ->
r = floor(Math.log10(x))
a = (10 ** (n - 1 - r))
floor(x * a) / a
但是,发现有些数据打印出的结果会是这样:
## precise(1)(100000) ==> 99999.99999999999
虽然说,数值计算必然会有误差,但是,如果结果是要显示为一个reasonable的数值的话,这种情况还是无法接受。
分析以上代码,发现如果想要结果reasonable的话,依赖这样一个假设:floor(x * a) / a
(即 整数 / 小数)的结果都是reasonable的。但是我们发现:
## 1 / 0.00001 ==> 99999.99999999999
所以这个假设是不成立的。
然后,尝试把除法改为乘法调整为这个样子:
precise = (n) -> (x) ->
r = floor(Math.log10(x))
a = (10 ** -(n - 1 - r))
floor(x / a) * a
虽然上面那个测试样例通过了,但是总觉得这个方法正确的可能性不大,所以又试了很多测试数据,终于被我找到了bug:
## precise(1)(0.0000052345) ==> 0.0000049999999999999996
另外,其中的floor(Math.log10(x))
这一步其实也是有问题的,这里做了个测试,测试发现,小于但是十分接近10的整数次幂的数,处理时会出现精度问题。(当然Math.log10还有一些浏览器兼容问题,但都可以解决,问题不大)
最终,发现想通过数值计算实现这个需求似乎走不通,因为不管怎么样,要得到结果最后一步都需要经过浮点数计算,而一旦进行浮点数计算就会产生精度损失,然后就会产生一个unreasonable的结果。
正当我打算走“先转字符串处理”这条路的时候,@文祎骁 同学帮我发现了toExponential这个好东西,当然用toPrecision也一样。
于是最后这样子就实现了:
precise = (n) -> (x) ->
parseFloat x.toPrecision(n)