深入理解浮点数类型float和double

浮点数的表示

浮点数格式

首先看一下64位机器中的浮点数float和double格式:

表示

浮点数位表示有三个字段,分别对这些值进行编码:符号位s,阶码字段exp和小数字段frac。单精度float浮点格式中,s、exp和frac分别为1,8和23位,得到一个32位的表示;双精度double浮点格式中,s、exp和frac分别为1,11和52位,得到一个64位的表示。

编码

知道了浮点数的位表示,那如何对数值进行编码呢?以float为例,分三种情况:

分类

IEEE浮点标准表示:V = (-1)^s * M * 2^E

s:对应符号位s,s决定这个数是负数(s=1)还是正数(s=0),对于数值0作为特殊情况处理,+0.0和-0.0在某些方面是不同的,有些方面是相同的。

M:M是一个二进制小数,对于规格化的值:M=1+frac;而对于非规格化的值:M=frac。

E:E的作用是对浮点数加权,这个权重是2的E次幂。对于规格化的值:E=exp-Bias (Bias是一个偏置值,Bias=2^(k-1)-1,单精度是2^7-1=127,双精度是2^10-1=1023);对于非规格化的值:E=1-Bias。

对于特殊值:阶码全为1,小数域全为0时,表示无穷大;阶码全为1,小数域不全为0时,为NaN(Not a Number)。

值得一提的是,将非规格化的阶码值设置成1-Bias能够使得从非规划化值平滑转换到规格化的值。以正数float为例:

次最大非规格化值:  0 00000000 111…110     V = (1-128) *2 ^ (1 - 2 ^ (-22)) 
最大非规格化值: 0 00000000 111…111 V = (1-128) *2 ^ (1 - 2 ^ (-23))
最小规格化值: 0 00000001 000…000 V = (1- 128) *2 ^ (1 + 0)
次最小规格化值: 0 00000001 000…001 V = (1-128) *2 ^ (1+ 2 ^ (-23))

从这四个相邻的值可以看出,非规格化值到规格化值形成平滑转换。

范围和精度

1、范围

float:(-2^128 , 2^128) ,即 (-3.40E+38, 3.40E+38)

最大值:   0 11111110 111…111         ( 2 - 2^(-23) ) * 2 ^ 127 
最小值: 1 11111110 111…111 -( 2 - 2^(-23) ) * 2 ^ 127

double:(-2^1024 , +2^1024),即 (-1.79E+308 , 1.79E+308)

最大值:   0 11111111110 111…111      ( 2 - 2^(-52) ) * 2 ^ 1024 
最小值: 1 11111111110 111…111 -( 2 - 2^(-52) ) * 2 ^ 1024

2、精度

因为浮点数是通过二进制科学记数法表示,所以float和double的精度由尾数决定,即小数的精度决定。要将一个小数在内存中表示出来,就要转换成二进制,即在32(64)个位上填0或者1,决定精度的是最后23(52)位:

对于float:精度为6位。
float的尾数是23位,前22位填好之后,最后一位能表示的小数是2 ^ (-23) = 1.1920928955078125e-7,所以能保证表示到小数点后7位。如果此时第23位填1还不能精确表示,计算机就无能为力了,因为它不会给你24位了。到此计算机表示的值与数学中的真实值误差就出在了第8位往后,但是第7位也不一定是精确的,因为第8位之后通过舍入的方式(IEEE定义了四种舍入方式)保证精度,例如0.0000001192…,从第8位的1往后就要舍掉,就有可能造成向第7位进位的情况,所以第7位并不能保证一定精确。

对于double:精度为15位。
和float一个道理,最后一位能表示的小数是2 ^ (-52) = 2.220446049250313e-16,所以能绝对保证的精度为16位。

判断两个浮点数相等

对于整数很好处理 x==y这样的一个语句就可以解决全部的问题,但是对于浮点数是不同的。因为,浮点数在计算机当中的二进制表示方式就决定了大多数浮点数都是无法精确的表达。

看下面的程序:

#include <iostream>
using namespace std;

int main()
{
    float a = 0.25;
    float b = 0.35;
    float c = a + b;
    if (c == 0.6)
        cout << "0.25 + 0.35 ==0.6" << endl;
    else
        cout << "0.25 + 0.35 !=0.6" << endl;

    cout << "becasue:" << endl;

    printf(" c = %.8f", c);
    cout << endl;
    printf("0.6= %.8f", 0.6);
    cout << endl;   

    return 0;
}

输出:

0.25+0.35!=0.6
becasue:
 c = 0.60000002
0.6= 0.60000000
请按任意键继续. . .

原因在于0.35不能够精确表示。所以,对于浮点数的相等判断,千万不能用==符号!一般我们认为两个浮点数的查的绝对值足够小,例如小于0.00000001,就可以认为它们相等。

我们可以定义一个equal函数:

#define EPSILON 0.00000001
bool equal(double left, double right)
{
    if((left - right > -EPSILON) &&
      (left - right < EPSILON))  
        return true;
    else
        return false;
}

浮点数的理解也可以参考:http://blog.csdn.net/justjavac/article/details/8178044

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值