最近在开发的过程中遇到一个问题,服务端返回了一个JSON对象,在用JSON库解析的时候出现了一个诡异的问题
服务端返回的原始JSON是
{
"value": "15493409528126467"
}
在程序中直接调用JSON中的方法
jsonObject.optLong("value");
打印的日志如下
origin value=15493409528126467
parse value=15493409528126468
通过JSON的方法取到的结果出现了不一致的情况。应该是转换的时候精度出现了损失,一步一步跟到内部的实现方法,先看optLong()
public long optLong(String name) {
return optLong(name, 0L);
}
这里调用了两个参数的optLong()方法,在不传默认值的时候如果出错的话这个方法会返回0。再往里跟,这个方法内部调用了JSON.toLong()方法。
public long optLong(String name, long fallback) {
Object object = opt(name);
Long result = JSON.toLong(object);
return result != null ? result : fallback;
}
接着向里面走
static Long toLong(Object value) {
if (value instanceof Long) {
return (Long) value;
} else if (value instanceof Number) {
return ((Number) value).longValue();
} else if (value instanceof String) {
try {
return (long) Double.parseDouble((String) value);
} catch (NumberFormatException ignored) {
}
}
return null;
}
看来问题是出在这里了,当输入的参数为String的时候,toLong()方法会使用Double.parseDouble()方法解析,而我们知道double的精度是会有损失的,在Google的文档上有这么一句话
When the requested type is a long, other Number types will be coerced using longValue. Strings that can be coerced using valueOf(String) will be, and then cast to long. This two-step conversion is lossy for very large values. For example, the string "9223372036854775806" yields the long 9223372036854775807.
在取非常大的数字的时候,会先转换成String,再通过parseDouble()方法转成long,这期间就造成了精度损失。
解决方法
先得到String类型的值,再将String转成long
String str = jsonObject.optString("value");
long result = Long.valueOf(str);
这样就可以正确的取到值了,Long.valueOf()内部实现调用了BigInteger中的方法,这样就能保证结果正确了。