1. 问题描述
最近在给前端返回Long类型的数据时,发现返回的数据到前端的时候,精度发生了变化:
后台返回:221629798350123011
前端接收:221629798350123000
也就是说,最终前端接收的时候,这个值不是一个精确的值。大致了解了下,原因大致如下:
2. 原因
JavaScript中表示数值的只有一个类型,就是Number,Number类型采用IEEE754标准来表示数字,不区分整数和浮点数,统一使用64bit 浮点数来进行表示。而在一段范围之内,所有的整数都有唯一的浮点数表示,这些整数叫做安全整数,而在JS里,安全整数的范围是:
Number.MAX_SAFE_INTEGER = 2^53 - 1 => 9007199254740991
Number.MIN_SAFE_INTEGER = - (2^53 - 1) => -9007199254740991
在安全整数范围之内的整形都能够正常显示,而超出安全整数范围的整数则都是不可靠的。
根据国际标准IEEE 754,JS浮点数的64个二进制位,从最左边开始,依次是:
第1位: 符号位,0正数,1表示负数
第2位到第12位: 储存指数部分
第13位到第64位: 储存小数部分(即有效数字)
其中,IEEE 754规定,有效数字第一位默认总是1,不保存在64位浮点数之中。也就是说,有效数字总是1.xx...xx的形式,因此,JavaScript提供的有效数字最长为53个二进制位,意味着绝对值小于2的53次方的整数,即-(253-1) 到 253-1 都可以精确表示。所以换算成十进制的话,JS数字的最高精度是16位,而上面我们传递的那个数值,很明显超过了16位,所以这个结果是不精确的。
3. 解决方式
解决方式也很简单了,就是返回的时候不用数值类型,使用字符串的形式进行返回。而在后台的处理中,有多种方式方式将Long类型转为字符串类型,这里我们了解两种常用的方式。
- Spring MVC中默认使用的json解析工具是Jackson,使用的默认Message转换器是Jackson的
MappingJackson2HttpMessageConverter
,实际实现是通过该对象的ObjectMapper来实现的,所以我们可以修改一下ObjectMapper的一些设置:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(jackson2HttpMessageConverter);
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
}
- 上面这种方式是针对全局实现的,会将全局内所有的Long对象转换为字符串类型, 有的时候我们可能只想针对特定类中的某个字段,那么这时候我们可以在实体中,通过配置Jackson的
JsonInclude
和JsonSerialize
来实现:
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
public class User implements Serializable{
private String name;
private String id;
private Double aDouble;
@JsonSerialize(using = ToStringSerializer.class)
private Long sum;
// set,get省略
}
- 现在我们工程中经常会用到fastjson,如果我们想通过使用fastjson来实现该类型转换的话,可以通过如下配置:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
SerializeConfig serializeConfig = SerializeConfig.globalInstance;
serializeConfig.put(Long.class, ToStringSerializer.instance);
fastJsonConfig.setSerializeConfig(serializeConfig);
fastConverter.setFastJsonConfig(fastJsonConfig);
converters.add(fastConverter);
}
这里fastjson版本:1.2.47
,版本不同,代码可能会稍有不同。
- 同样,如果我们不想针对全局做转换,如果要针对单个实现类,可以通过fastjson的
JSONField
来实现(记得先将Jackson转为Fastjson):
public class User implements Serializable{
private String name;
private String id;
private Double aDouble;
@JSONField(serializeUsing= ToStringSerializer.class)
private Long sum;
}
Fastjson在com.alibaba.fastjson.serializer
包下面提供了多种数据类型转换的注解,我们可以有选择的使用,如果有需要,也可以实现ObjectSerializer
来自定义我们的实现。
网友评论