隐藏的精度(HiddenPrecision)


/**
 * 献给我最尊敬的偶像Martin Fowler
 * 原文出处:https://martinfowler.com/bliki/HiddenPrecision.html
 * @author dogstar.huang <chanzonghuang@gmail.com> 2017-03-22
 */

本翻译已征得Martin Fowler同意,并链接在博客原文下方。

有时当我在处理某些数据时,这些数据比我想象中的更精确。有人可能会觉得这应该是件好事,毕竟精确度是有益的,所以越精细越好。但隐藏的精度可能会导致一些微妙的缺陷。

const validityStart = new Date("2016-10-01");   // JavaScript  
const validityEnd = new Date("2016-11-08");  
const isWithinValidity = aDate => (aDate >= validityStart && aDate <= validityEnd);  
const applicationTime = new Date("2016-11-08 08:00");

assert.notOk(isWithinValidity(applicationTime));  // 并不是我想要的  

我打算通过指定开始日期和结束日期来创建一个闭区间的日期范围,这就是上面代码所做的事。然而,实际上我并指定的不是日期,而是指定了某个时刻,即我没有把结束日期标识为11月8号,而是标识为11月8号的凌晨00点00分。因此,11月8日那天的任何时间(除了午夜之外)都超出了原本打算包含它在内的日期范围。

对于日期,隐藏的精度是一个公共的问题,因为有一个实际上提供了类似这样时刻的日期创建功能是很可悲的。这是糟糕命名的一个例子,确实也是普遍糟糕日期和时间建模的一个例子。

日期是隐藏精度问题一个不错的例子,但另一个罪魁祸首则是浮点数。

const tenCharges = [  
  0.10, 0.10, 0.10, 0.10, 0.10,
  0.10, 0.10, 0.10, 0.10, 0.10,
];
const discountThreshold = 1.00;  
const totalCharge = tenCharges.reduce((acc, each) => acc += each);  
assert.ok(totalCharge < discountThreshold);   // 并不是我想要的  

当我运行这些代码时,有一条日记显示totalCharge0.9999999999999999。这是因为很多变量无法用浮点数精确表示,导致了少量看不见的精度在尴尬的时候却显而易见。

这里其中的一个结论是,使用浮点数来表示金额要非常非常谨慎(如果你有一个带有类似分这样分数部分的货币,那么通常最好用整数来表示分数,如用500表示€5.00,带上金额类型则更好)。更一般的结论是,浮点在比较时是巧妙的(这就是为什么测试框架的断言针对比较总是会有一个精确度)。

致谢

Arun Murali,James Birnie,Ken McCormack,和Matteo Vaccari在我们内部的邮件列表讨论了这篇文章的草稿。

dogstar

一位喜欢翻译的开发人员,艾翻译创始人之一。

广州