😏

为什么要除以 100?

背景

现公司是做电商的,电商就免不了和价格打交道。为了防止进度丢失,方便加减运行的问题,业界通用的方式是使用整数来表示。最小单位为分,123 就表示 “1 元 2 毛 3 分” 或者 ”1.23” 元,这个不需要过多介绍。

但是它们返回给前端用来展示的价格,也是这个 123,而不是转化成 1.23。需要前端再自己做一个转化逻辑。说要前端做这个的原因是防止进度丢失

所以矛盾就出在这里。如果进度会丢失的话,那么 Java 和 JS 是没差别的。它们都采用 IEEE 754 来表示浮点数,价格的 Int 类型和 JS 的 SIM 也类似。那么这个无非就是把转化价格的逻辑从后端交给前端。然后前端又要下载一个类似 BigNumber 的库来处理这些问题。

真相

这就是历史原因。那么有没有考虑过,整数除以 100 真的会丢失精度吗?我测试了十几个数不会。所以我打算写一个程序来证明一下。

如果我将 double(a / 100) 的过程转化成 int(a / 100) + int(a % 100) / 100,接着判断这两个数是否相等是不是就可以了?(这里不考虑 JS,而是考虑 C 语言的情况。a / 100 得到的是整数)

这里看上去有点脱裤子放屁,因为 a / 100 是整数,一个整数模 100,也还是整数。而我们又不能证明 int(a % 100) / 100 不会丢失精度,回到了最开始的问题上。

不过换个角度想,JS 中支持将 “1.23” 这种字符串转化成数字。所以上面的式子改成 Number(a / 100 + "." + String(a % 100)) 是不是就能达到类似的效果了?

所以我写出下面代码。

//生成 00 01 02 03 04 一直到 99
const points = Array.from({ length: 100 }, (_, i) => i).map(i =>
  String(i).padStart(2, '0')
);

// 2 ** 31 - 1 我的计算机算不动,不过 2 ** 25 意思也到了。
for (let i = 0; i < 2 ** 25 - 1; i += 1) {
  if (i / 100 !== Number(`${~~(i / 100)}.${points[i % 100]}`)) {
    console.log('foobar', i);
  }
}

最后 foobar 从来没有被打印出来过。

不过在第 7 行还是要解释下,我们在等式左右两边都用到了 i / 100,看上去很迷惑。当你要知道,确实它的意义是不同的。左边是直接得到浮点数,右边在 ~~ 的作用下会转化成整数。我们肯定不会出现 int(101.0 / 100) == 0 的情况,也不会出现 int(199.0 / 100) == 2 的情况。

所以这是否可以证明一个整数除以一个 100,永远不会丢失精度呢?我相信是的。 那如果这个数不再是 100,而是其他的呢,比如 123?这个时候精度很有可能丢失,因为类似于循环小数这种肯定是无法正确表示的。