测试基础 一个有趣的 BUG(精度问题)

CKL的思考 · 2022年06月10日 · 最后由 活着丶 回复于 2022年07月14日 · 26033 次阅读
本帖已被设为精华帖!

最近在协助团队完成 ES 数据的切换(业务数据迁移),过程中遇到一个比较好玩的 BUG ,和大家分享并作为经验记录。

01 问题发现过程

通过前期的方案设计和比较,我们决定通过 elasticdump 工具来做 ES 的数据迁移,这个也是比较普遍的迁移方案,于是就动手实施了,过程中也没遇到什么问题。在最后的数据验证环节,发现有一个 ID 对应不上了,如下图所示,通过对比工具,发现一个长度较大的 ID 发生了偏移,其他的数据都没有问题。这是为什么呢?一头雾水。

根据二分法的排错思路,我们需要先确认是导出数据的问题,还是导入数据的问题。查看导出过程的中间文件,发现在导出的时候就出现了错误。于是怀疑是 elasticdump 导出功能的问题。因为这个出错的字段,主要的特征就是长度比较长(18 位),于是怀疑是精度的问题。就去查了下 elasticdump 的源码,一番查找后,果然发现有人遇到过同样的问题,并已经修复了这个 BUG,并给出了解决方案和一些猜测的原因。于是这个问题就得到了解决。在 elasticdump 的导出命令中,加上--support-big-int 参数,就可以了。

好像也很简单嘛,不是么。其实排错的过程也走过很多弯路,只是现在回顾起来看着比较轻松而以。

02 问题的根因是什么

只解决问题并不是我的风格,总得看看让我绕这么大圈才解决的问题根因是什么嘛。于是查了相关资料(结合上面 GIT 上的对话),可以确认,是因为 elasticdump 中有部分功能是用 JS 写的,而 Js 遵循 IEEE754 规范,采用双精度存储,占用 64 位,从左到右的安排位第一问表示符号位,11 位表示指数,52 位来表示尾数,因此 Js 中能精确表示的最大整数是 253(十进制 为 9007199254740992),那么大于这个数(本文中数值长度 18 位)就可能会丢失精度,因为二进制只有 0 和 1,数值太大,于是就出现了精度丢失的问题。可以在 Chrome Console 里面试了一下,果然是这样,(不是超过了能表示的最大值,而是超过了能精确表示的最大值),和 elasticdump 导出的数据变化基本类似。

再往深了想,为什么用 double 类型会出现这个问题,其他的数据类型是否会有同样的问题呢?这就涉及了数据精度的问题,在这里篇幅有限,就不再展开,有兴趣的同学可以自己去查看相关资料,本质上还是十进制小数与二进制小数相互转换产生的误差。

03 类似的问题有哪些

因为这个问题比较好玩,就又找了一些资料看了下,发现还有两个精度有关的 BUG,还蛮好玩的。

千年虫问题:这个问题相信很多人 IT 人都听说过,简单来说,就是由于前期计算机的存储资源较为昂,在表达时间时,为了节约空间,有位科学家提出了一个方案,把1960年8月11日,简写成 600811。但这样会有一个问题,就是当时被缩写掉的是 19XX 年中 19,如果时间来到 2000 年,程序就无法准确表达时间。比如:2000年1月1日,简写成六位数是 000101。计算机就会怀疑人生,怎么时间倒流了呢?然后就会导致计算机系统发生紊乱。当时大家都觉得自己的程序不会运行到 2000 年,所以就没太放在心上,而大多数后来人习惯了这种记录方式,就忘记了这回事,结果引发了千禧年的大 BUG,造就了多少程序员的不眠之夜。

2038 年问题:现在很多时候,我们在处理时间问题时,都喜欢用时间戳来记录,因为简单方便,不需要考虑时区问题(时区问题很让人头疼的,一不小出就容易出错)。但是这里面会有一个小 BUG 哟。什么是时间戳呢?简单来说就是:以1970年1月1日0 时 0 分 0 秒为起点,然后通过计算秒数来算出当前时间。比如:2021年5月7日15:00:00,换算一下就是 1620370800 秒。但是由于 32 位操作系统所能计算的秒数有限,到2038年1月19日3:14:07,就会达到极限。二进制:01111111 11111111 11111111 11111111,其后一秒,二进制数字会变为 10000000 00000000 00000000 00000000,发生溢出错误,造成系统将时间误解为1901年12月13日20 时 45 分 52 秒,然后系统就会发生各类错误,是不是和上面的千年虫一样?理论上到了 2038 年,人们应该淘汰掉了 32 位操作系统, 64 位操作系统就不存在这个问题。但是从前面的 “千年虫” 事件来看,人类从历史中吸取的唯一教训,就是人类不会吸取任何教训。

04 小结

对于发现的缺陷,不能仅停留在把问题解决了就完事。有时间和精力,还是需要更深层次的去了解缺陷背后的逻辑和根因是什么,触类旁通。以避免更多类似的问题发生。

在写这篇文章的时候,又想起了自己以前做报表相关的业务时,对于时间的精度特别敏感,也会遇到一些关于精度上的取舍问题,这需要我们和业务方面讨论并确认清楚,是精确到秒,还是毫秒,以避免出现数据边界的小问题。

原文链接:https://mp.weixin.qq.com/s/w5g3le-V9hdlp8vQ3ji2Ug

共收到 14 条回复 时间 点赞

好好好好好好好强啊。。

陈恒捷 将本帖设为了精华贴 06月10日 13:51

挺好的 bug 定位和思考,值得大家学习。

PS:千年虫实际上大部分系统都有做好提前防护了,也没有真正产生影响非常大的 bug 吧?

陈恒捷 回复

主要是在手忙脚乱了一阵子,倒没什么影响很大的 BUG。

感谢楼主。第一次被设为精华贴,小激动下。😊

手动点赞👍

时间精度关注的少,一般关注金额精度了,学习了。

2038 年问题 前两天还正好碰到了,点赞

要是每个测试都有 lz 的做事态度和思考方式,得少多少线上 bug 啊

很 nice 的探索精神 学习了

可怜的点工表示做不到啊...

厉害!学习了!

厉害了!👍长见识

对 bug 原因的探索精神,值得学习!工作中遇到过类似的问题,如 C 代码中 unsigned int 定义的实参,参数传递时用了 int 形参,结果数据被截断了部分,不完整。

这个思路不错,好好学学

hah,这问题,我们 19 年服务器改时间,就是不让改到 2038 年,结果用久了还是到了😅

兔子🐰 TesterHome 有奖征文 | 有意思的 bug 中提及了此贴 07月22日 11:04
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册