隐藏精度
2016 年 11 月 22 日
有时当我处理一些数据时,这些数据比我预期的更精确。有人可能会认为这是一件好事,毕竟精度是好的,所以越多越好。但隐藏的精度会导致一些细微的错误。
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)); // NOT what I want
上面代码中发生的事情是,我打算通过指定开始日期和结束日期来创建一个包含日期范围。但是,我实际上没有指定日期,而是指定了时间点,所以我没有将结束日期标记为 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); // NOT what I want
当我运行它时,一个日志语句显示 totalCharge
为 0.9999999999999999
。这是因为浮点数不能完全表示许多值,导致一些不可见的精度,这些精度会在尴尬的时候出现。
由此得出的一个结论是,你应该非常谨慎地用浮点数表示货币。(如果你有像美分这样的货币小数部分,那么通常最好对小数部分使用整数,用 500 表示 €5.00,最好在 货币类型 中)。更一般的结论是,浮点数在比较方面很棘手(这就是为什么测试框架断言总是对比较有精度的原因)。