浮点数美丽的表象(为什么要慎用浮点数)

简介: 这其实是float累加过程中精度丢失导致的,要理解这点我们首先要理解什么是浮点数。首先我们了解数在计算机中是如何表示的,因为计算机只能理解0和1两个数,所以一切信息都是用二进制表示的。如何保存更多的信息就是计算机设计者面临的挑战。

现在假设你负责一个广告公司的结算系统,你需要统计下月度点击收入,生成一个月度报告。假设有2000w个点击,每个点击平均1元,我们用小学数学计算就知道总收入是2000w。但是我们用计算机累加就会出问题了。如果我们用float存储数据,float可以表示的数据范围− 2 128 -2^{128}−2

128

到 2 128 2^{128}2

128

,看起来绝对够啊!那让我们写个程序测试下。


    public static void main(String[] args) {
        int N = 20000000;
        float sum = 0.0;
        float price = 1.0f;
        for (int i = 0; i < N; i++) {
            sum += price;
        }
        System.out.println("sum is " + sum);
    }
1.6777216E7


What?输出应该是2000w的,但为什么少了3222784.0,如果在生成环境中,这意味着我们的钱凭空消失了3222784.0!why?


这其实是float累加过程中精度丢失导致的,要理解这点我们首先要理解什么是浮点数。首先我们了解数在计算机中是如何表示的,因为计算机只能理解0和1两个数,所以一切信息都是用二进制表示的。如何保存更多的信息就是计算机设计者面临的挑战。


在各个编程语言中的int都有4个字节32位,32位二进制最多有2 32 2^{32}2

32

种状态,也就是意味着最多能表示2 32 2^{32}2

32

个数,但这里还需要流出1位做为符号位来标识正负。所以能标识的最大数是2147483647(01111111111111111111111111111111),最小数是-2147483647(11111111111111111111111111111111),这个时候其实0有两个,一个+0,一个-0,两个表示的意义其实是一样的,我们干脆用-0代表最小数-2147483648。所以最终int的表示范围就是-2147483648~2147483647了,这就是定点数,它只能用来表示整数。

 

如何表示小数?小数的特点是小数点前后的位数是不固定的,这个小数点是浮动的,这就是浮点数这个名词的由来。为了表示浮点数,我们可以把一个数拆分成两个部分,数值部分和指数部分,比如11.16可以表示为1116乘以1 0 − 2 10^{-2}10

−2

 ,0.1表示为1乘以1 0 − 1 10^{-1}10

−1

。没错,在计算机中也是这么做的,只不过用的是2进制而已。

 

float使用了4个字节来存储数据,总共32位,最高位作为符号位,最高2-9位作为指数位,最后的23位才作为有效数位。注意,23位之前有个1被省略掉了,所以他的有效位其实是24位,float所能表示的有效数值只有2 24 2^{24}2

24

,大概8位数,因此它不能标识超过8位的有效数字,否则会丢失精度,这就是浮点数美丽的表象。https://www.h-schmidt.net/FloatConverter/IEEE754.html 这里可以可视化float数值的二进制表示,方便你理解float。

 

让我们继续来看为什么上面的代码会少数据。这就得先理解浮点数的加法是怎么做的。当两个float数相加时,计算机首先会对齐两个数的指数位,向指数位比较大的一个靠拢,这时候比较小的float数的有效数位就要右移。如果其中一个的数的指数位太大,就有可能让指数位比较小的数的有效数一直右移,甚至变成0。

 

上面的代码一直+1,当sum大于16777216之后,1.0为了和sum的指数位对其,它的有效数会右移动24次,上面说到float的有效数只有24位,所以它会完全变成0。所以上面sum大于16777216之后它再加1也相当于+0。

b437fc7338c9f6843307c070f489349b_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly94aW5kb28uYmxvZy5jc2RuLm5ldA==,size_16,color_FFFFFF,t_70.png

如果我们换做double类型呢?这次没有出现精度损失。但并不意外这就不会出现精度损失,double比float有更多的精确位,但也不是无限的,在数据非常大时也会丢失精度。在金融行业,即便是非常非常小的精度损失,也会让客户对你失去信任,所以要保证100%的精度。


如何保证? 有个叫Kahan Summation算法,可以保证不会出现精度损失,代码如下,测试确实不会损失精度。


public class KahanSummation {
  public static void main(String[] args) {
    int N = 20000000;
    float sum = 0.0f;
    float c = 0.0f;
    for (int i = 0; i < N; i++) {
      float x = 1.0f;
      float y = x - c;
      float t = sum + y;
      c = (t-sum)-y;
      sum = t;      
    }
    System.out.println("sum is " + sum);   
  } 
}

这个算法虽然好用,但看起来很复杂,所以在实际使用中很少使用。因为有更简单的方法。当然,从古至今解决问题最好最彻底的方式就是避免问题的发生。我们直接不使用浮点型,而是转而用long。用最小的货币单位分来计量,long的取值范围是-9223372036854775808~9223372036854775807,绝对够用了。

目录
相关文章
|
3月前
|
C# 图形学
小功能⭐️C#控制小数点后位数的方法
小功能⭐️C#控制小数点后位数的方法
|
6月前
|
C语言
【C语言】如何只打印小数的有效数字位数且不补0
【C语言】如何只打印小数的有效数字位数且不补0
94 0
【C语言】如何只打印小数的有效数字位数且不补0
|
6月前
|
算法 测试技术 C#
【位运算】【脑筋急转弯】2749. 得到整数零需要执行的最少操作数
【位运算】【脑筋急转弯】2749. 得到整数零需要执行的最少操作数
|
6月前
|
存储 编译器 C语言
魔性的float浮点数精度问题
魔性的float浮点数精度问题
60 0
|
存储 算法
算法小白的心得笔记:比较小数点后五位,而不会受到浮点数精度问题的影响。
std::cerr << "\n __" << inum << "__ 计算错误 " << ratio << " 应该是 " << beta3[inum - 1] << std::endl; return 1;
43 0
解决 c++ 字符转转浮点型数据且保留所有小数
解决 c++ 字符转转浮点型数据且保留所有小数
|
编译器 C++
C++ 字符串转浮点数,包括整数、小数和科学记数法
C++ 字符串转浮点数,包括整数、小数和科学记数法
601 0
|
C语言
C语言之浮点数(小数)
C语言之浮点数(小数)
252 0
|
存储 算法 API
一个对于小数四舍五入的算法C语言版
一个对于小数四舍五入的算法C语言版
197 0
定点数与浮点数简单解释
定点数 定点数:小数的位置是固定不变的。定点数又包括定点整数和定点小数。 定点小数:小数点隐含固定在最高数据位的左边,整数位则用于表示符号位,用于表示纯小数。
224 0
定点数与浮点数简单解释