您的位置:首页 > Web前端 > JavaScript

Flaot 转 Json 精度丢失

2016-06-30 10:14 429 查看
在coding碰到一个比较奇葩的问题,从服务端接口获取的数据上个带有较长小数位数的String,在DO中封装了一个对应的Float字段来存取这份数据并保留小数点两位,利用DecimalFormat想想也没有什么困难的地方,输出的数据也正常无误:

<span style="white-space:pre">	</span>DecimalFormat df = new DecimalFormat("#.00");
df.setMaximumFractionDigits(2);
df.setGroupingSize(0);
df.setRoundingMode(RoundingMode.FLOOR); //无需四舍五入
//DecimalFormat formater = new DecimalFormat("#0.##"); // 另外一种表达方式
return df.format(Float.valueOf(obj + "") );


此时出现了比较诡异的事件,在DO的数据进行数据中转从后端通过JSON数据渲染格式返回到前端时,总是把两位小数的浮点数转化成为一长串的浮点型小数,感觉是比较奇葩的,笔者仅仅将数据put到JSON Obj中而已,中间过程没有掺杂任何数据处理,而且保证前晚没有通宵写代码出现双影的情况,不带这么玩人的呀,静下心来仔细分析下:

个人认为有两种情况可以考虑:其一float浮点数中小数位数在底层硬件编码中(本科学的计算机组成原理基本全额还给老师了)数据位数比较低导致数据丢失,其二是不是JSON自身捣的蛋,在JSONObject.put过程中分析数据类型,将Float类型的数据在存入Map之前进行数据转换,将单精度浮点型数据转化为双精度浮点型数据,即将四字节存储空间转为八字节的。

此时能够判定的是float在转double的过程会有数据丢失,但是float的小数在什么情况也会出现数据丢失的情况?
<span style="white-space:pre">	</span>float tt = 9.81f;
float mm = 32.1599132123f;
System.out.print("tt:"+(double)tt);   =======> tt:9.8100004196167
System.out.print("mm:"+mm); =========> mm:32.159912


check下单双精度浮点型数据在计算机硬件上的编码方式,可以发现浮点数是用科学计数法来表述,存在三个部分分别是符号位、指数位(其中底数为2)和尾数数为来表示,一个浮点数可以表示为尾数乘以2的指数次方再加上符号位,其中float占4字节32bit存储空间,第一位用于存符号位,2-9为表示阶码用于存取指数,10-32位为尾数位;double占64bit,一个符号位,11个指数位,52个尾数位。因此第一种情况导致数据丢失可以进一步定位到是由于指数位超出了数据表示范围,或者是尾数位的表示超出了范围!

重新拿32.1599132123来开刀,可以分别转化成单精度和双精度浮点的二进制表示:

<span style="white-space:pre">	</span>double m = 32.1599132123;

long l = Double.doubleToLongBits(m);
System.out.println(Long.toBinaryString(l));      1 00000001000  0000001010001111000000 01001010000001000001101101110
float f = 32.1599132123f;
int i = Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(i));   1  00001000  0000001010001111000000


分析输出结果,符号位相同,指数位双精度浮点型除了在高位补0之外和单精度浮点型数据保持一致,因此造成32.1599132123这个数精度丢失的唯一原因就是尾数部

分了,仔细对比下发现尾数部分前23位都相同,float类型数据相比较double类型数据由于精度的限制只能表示到23位(经测试精确到23位会进行四舍五入),相对于double数

据而言的后面29位数据被遗弃从而造成数据丢失。

另外一个问题是为什么好好的保留两位小数的浮点数在put进JSONObject中后会输出带有一长串小数的JSON

private Object _processValue( Object value, JsonConfig jsonConfig ) {
...

if( JSONUtils.isString( value ) ){
String str = String.valueOf( value );
if( JSONUtils.mayBeJSON( str ) ){
try{
return JSONSerializer.toJSON( str, jsonConfig );
}catch( JSONException jsone ){
return JSONUtils.stripQuotes( str );
}
}else{
if( value == null ){
return "";
}else{
String tmp = JSONUtils.stripQuotes( str );
return JSONUtils.mayBeJSON( tmp ) ? tmp : str;
}
}
}else if( JSONUtils.isNumber( value ) ){
JSONUtils.testValidity( value );
return JSONUtils.transformNumber( (Number) value );
}else if( JSONUtils.isBoolean( value ) ){
return value;
}else if( value != null && Enum.class.isAssignableFrom( value.getClass() ) ){
return ((Enum) value).name();
}else{
return fromObject( value, jsonConfig );
}
}

public static Number transformNumber( Number input ) {
if( input instanceof Float ){
return new Double( input.doubleValue() );
}else if( input instanceof Short ){
return new Integer( input.intValue() );
}else if( input instanceof Byte ){
return new Integer( input.intValue() );
}else if( input instanceof Long ){
Long max = new Long( Integer.MAX_VALUE );
if( input.longValue() <= max.longValue() && input.longValue() >= Integer.MIN_VALUE ){
return new Integer( input.intValue() );
}
}

return input;
}

那么如何规避这种情况的发生,或者换种方式说如何解决这个比较头疼的问题,我所采用的方式是用字符串代替浮点型数据,直接利用df.format(Float.valueOf(obj
+ "") );

所返回的String类型字符串,而不再转换成Float类型的数据,前端只需显示该字符串而无需再进行相应的数据加工,网上查看了下相关文章,还有一种方式解决此问题,即采用

BigDecimal来解决该问题,闲暇再来探究,待续....

转载自http://zhuyuge0.blog.163.com/blog/static/13230361420141128102152277/#

写在最后:博主在项目中 float 类型转成json数组时,发现小数点后突然多出来4、5位数,开始以为时数据库精度问题,后来发现时json转化时出现精度丢失

看了一位大神的测试(上面转载过来的),确实是这个问题,但没有采用以上的方法(并没有看懂),而是用了Double,转json不要用Float就对了~。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Json Float