JsonObject.optLong 导致的Bug
问题背景
业务场景存在一个JSB,FE同学传了个JSON到端上,端上测试的时候都是正确的,有天调试Server反馈说id值找不到,端上id值回传错了,开始排查代码。
发现Jsb传输过来的Json都是String to String的格式, 类似这样
1 | { |
断点发现,FE传过来的值是123456789087979797,但是端上取了之后,传给server的值是123456789087979792,于是排查转换过程。
端上在取id的时候,使用了org.json.JSONObject#optLong(java.lang.String) 方法,造成整数转换错误。
源码分析
1 | org.json.JSONObject#optLong(java.lang.String) |
原理分析
为什么Long转Double会失真,我们先复习一下Double的表示方式, 那就是IEEE754标准,
所有的浮点数都要表示成 尾数和指数的形式,这里就会有一个问题,尾数保留的精度。我们以前面的Long值123456789087979797举例,转换二进制, 总共有57位
1 | 110110110100110110100101110101010101100110000100100010101 |
换成标准形式,尾数只能存52位,后面的得丢掉,整数就在这里失真了
1 | 1.1011011010011011010010111010101010110011000010010001|0101(丢掉) |
再转换成Long值就是 123456789087979792
源码中,并不是double转long造成的精度丢失,而是double表示long的时候,尾数精度丢失
1 | return (long) Double.parseDouble((String) value); |
double的尾数精度只有52位,2^(52)< 16个9,所以十进制下,double能准确表示小数的精度在15位,还有个经问题0.1+0.2 = 0.30000…4也是尾数精度导致的。本质上计算机存储的数都是离散的,不是连续的。
总结
每次使用 org.json.JSONObject#optLong的时候,留意一下目标值的类型,你可能恰好取对了值,但不总是对的,一旦值过大的case, 就会触发bug。