js css htmlAndroid开发

【数据与安全】- 浮点数精度问题详解

2022-05-10  本文已影响0人  拔萝卜占坑

简介

由于计算机存储的规则所致,有些时候浮点数存入和读取出来的值并不相等,同样的数据用单精度(float)和用双精度(double)存储,获取出来的值也会有差异。所以,当我们开发对精度要求比较高的业务场景下,如果不了解,很可能出现直接的经济损失。针对这些问题,接下来用一篇文章来详细讲解这些问题和解决方案。

例子

public static void main(String[] args) {
    double d1 = 0.1;
    double d2 = 0.1f;
    float d3 = 0.1f;
    System.out.println("d1=" + d1);
    System.out.println("d2=" + d2);
    System.out.println("d3=" + d3);
}

输出:

d1=0.1
d2=0.10000000149011612
d3=0.1

如果想要解释上面的问题,那么就需先了解浮点数在计算机中的存储方式(遵循IEEE 754(IEEE Standard for Floating-Point Arithmetic)标准)。

十进制数转换为二进制数

浮点数存储格式

IEEE 754对有效数字M和指数E的一些特别规定。
至于指数E,情况就比较复杂。

浮点数舍入规则

而 IEEE 754 就是采用向最近偶数舍入(round to nearest even)的规则。

比如0.1的二进制是1.10011001100110011001100110011001100110011001100110011...
根据单精度有效位M长度时23:
1.10011001100110011001100110011001100110011001100110011...,显然向上舍入丢失的精度更小,所以0.1单精度存储为:10011001100110011001101而不是10011001100110011001100

举例

这里以小数0.1以单精度和双精度存储位例进行讲解。首先把0.1转换成二进制的形式是(转换网站

000110011001100110011001100110011001100110011001100110011...

二进制小数转十进制

以上面0.1单精度存储二进制1. 10011001100110011001101进行转换:

2^0 + 2^-1 + 2^-4 + 2^-5 + 2^-8 + 2^-9 + 2^-12 + 2^-13 + 2^-16 + 2^-17 + 2^-20 + 2^-21 + 2^-23
上面加起来的值在乘以指数2^-4就得到0.1存储在计算机的值了。
经过计算上面最终的值是:0.10000000149011612...

浮点数的有效位数

解释上面的问题

public static void main(String[] args) {
    System.out.println("aa= " + new BigDecimal(0.1));
}

输出

aa= 0.1000000000000000055511151231257827021181583404541015625

精度丢失

public static void main(String[] args) {
    float d4 = 111111.01111111f;
    System.out.println("d4=" + d4);
}

输出

d4=111111.01

大家可以按照上面的思路转换一下。可以见得,使用浮点数时,如果整数部分越大,小数精度丢失越严重。

精度丢失解决办法

BigDecimal

Java的在使用除法(divide方法)时,应该手动指定精度和舍入的方式。如果不指定精度和舍入方式,在除不尽的时候会报异常。

public static void main(String[] args) {
  System.out.println("aa= " + new BigDecimal("1.0").divide(new   BigDecimal("3.0"),1170,BigDecimal.ROUND_HALF_UP));
}
Half

半精度,使用优势:

半精度,缺点:

BigInteger(不可变的任意精度有符号整数)

这个应该就是表示任意大小的整数类,里面用了一个数组来存储。

上一篇下一篇

猜你喜欢

热点阅读