数据库中有一个bigint类型数据,对应java后台类型为Long型,在某个查询页面中碰到了问题:页面上显示的数据和数据库中的数据不一致。例如数据库中存储的是:1475797674679549851,显示出来却成了1475797674679550000,后面几位全变成了0,精度丢失了。


1. 原因

这是因为Javascript中数字的精度是有限的,bigint类型的的数字超出了Javascript的处理范围。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。其结构如图:

java中ScriptEngine计算丢失精度 java long丢失精度_数据库

各位的含义如下:

  • 1位(s) 用来表示符号位
  • 11位(e) 用来表示指数
  • 52位(f) 表示尾数

尾数位最大是 52 位,因此 JS 中能精准表示的最大整数是 Math.pow(2, 53),十进制即 9007199254740992。而Bigint类型的有效位数是63位(扣除一位符号位),其最大值为:Math.pow(2,63)。任何大于 9007199254740992 的就可能会丢失精度:

9007199254740992     >> 10000000000000...000 // 共计 53 个 09007199254740992 + 1 >> 10000000000000...001 // 中间 52 个 09007199254740992 + 2 >> 10000000000000...010 // 中间 51 个 0

实际上值却是:


9007199254740992 + 1 // 丢失 9007199254740992 + 2 // 未丢失 9007199254740992 + 3 // 丢失 9007199254740992 + 4 // 未丢失


2.解决方法

解决办法就是让Javascript把数字当成字符串进行处理。对Javascript来说,不进行运算,数字和字符串处理起来没有什么区别。当然如果需要进行运算,只能采用其他方法,例如使用JavaScript的一些开源库bignumber之类的处理了。Java进行JSON处理的时候是能够正确处理long型的,只需要将数字转化成字符串就可以了。

 

方案一:

另一个方式是使用注解JsonSerialize

@JsonSerialize(using=ToStringSerializer.class)private Long bankcardHash;

指定了ToStringSerializer进行序列化,将数字编码成字符串格式。这种方式的优点是颗粒度可以很精细;缺点同样是太精细,如果需要调整的字段比较多会比较麻烦。

 

方案二:

Jackson有个配置参数WRITE_NUMBERS_AS_STRINGS,可以强制将所有数字全部转成字符串输出。其功能介绍为:Feature that forces all Java numbers to be written as JSON strings.。使用方法很简单,只需要配置参数即可:

spring:  jackson:    generator:      write_numbers_as_strings: true

这种方式的优点是使用方便,不需要调整代码;缺点是颗粒度太大,所有的数字都被转成字符串输出了,包括按照timestamp格式输出的时间也是如此。

 

方案三

@Configuration
 public class WebMvcConfig implements WebMvcConfigurer {    @Bean
     public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
         MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
         ObjectMapper mapper = new ObjectMapper();        //日期格式转换
         mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
         mapper.setDateFormat(new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN));
         mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));        //Long类型转String类型
         SimpleModule simpleModule = new SimpleModule();
         simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
         simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
         mapper.registerModule(simpleModule);        converter.setObjectMapper(mapper);
         return converter;
     }}

 

推荐方案三,虽然我用的是方案2吧