float double 类型数据 精度问题

简介

https://msdn.microsoft.com/zh-cn/library/hd7199ke.aspx

浮点数使用 IEEE(电气和电子工程师协会)格式。 浮点类型的单精度值具有 4 个字节,包括一个符号位、一个 8 位 excess-127 二进制指数和一个 23 位尾数。 尾数表示一个介于 1.0 和 2.0 之间的数。 由于尾数的高顺序位始终为 1,因此它不是以数字形式存储的。 此表示形式为 float 类型提供了一个大约在 3.4E–38 和 3.4E+38 之间的范围。

您可根据应用程序的需求将变量声明为 float 或 double。 这两种类型之间的主要差异在于它们可表示的基数、它们需要的存储以及它们的范围。 下表显示了基数与存储需求之间的关系。

浮点类型

类型有效位字节数
float6 – 74
double15 – 168

浮点变量由尾数(包含数字的值)和指数(包含数字的数量级)表示。
下表显示了分配给每个浮点类型的尾数和指数的位数。 任何 float 或 double 的最高有效位始终是符号位。 如果符号位为 1,则将数字视为负数;否则,将数字视为正数。

指数和尾数的长度

类型指数长度尾数长度
float8 位23 位
double11 位52 位

由于指数是以无符号形式存储的,因此指数的偏差为其可能值的一半。 对于 float 类型,偏差为 127;对于 double 类型,偏差为 1023。 您可以通过将指数值减去偏差值来计算实际指数值。
存储为二进制分数的尾数大于或等于 1 且小于 2。 对于 float 和 double 类型,最高有效位位置的尾数中有一个隐含的前导 1,这样,尾数实际上分别为 24 和 53 位长,即使最高有效位从未存储在内存中也是如此。
浮点包可以将二进制浮点数存储为非标准化数,而不使用刚刚介绍的存储方法。“非标准化数”是带有保留指数值的非零浮点数,其中尾数的最高有效位为 0。 通过使用非标准化格式,浮点数的范围可以扩展,但会失去精度。 您无法控制浮点数以标准化形式还是非标准化形式表示;浮点包决定了表示形式。 浮点包从不使用非标准化形式,除非指数变为小于可以标准化形式表示的最小值。
下表显示了可在每种浮点类型的变量中存储的最小值和最大值。 此表中所列的值仅适用于标准化浮点数;非标准化浮点数的最小值更小。 请注意,在 80x87 寄存器中保留的数字始终以 80 位标准化形式表示;数字存储在 32 位或 64 位浮点变量(float 类型和 long 类型的变量)中时只能以非标准化形式表示。

浮点类型的范围

类型最小值最大值
float1.175494351 E – 383.402823466 E + 38
double2.2250738585072014 E – 3081.7976931348623158 E + 308
  • 如果存储比精度更重要,请考虑对浮点变量使用 float 类型。 相反,如果精度是最重要的条件,则使用 double 类型。

对float类型在内存中的存储例子

float i = 4.5 作为例子 :

通过上面的格式,我们下面举例看下4.5在计算机中存储的具体数据:
Address+0 Address+1 Address+2 Address+3
Contents 0x40 0x90 0x00 0x00
接下来我们验证下上面的数据表示的到底是不是4.5,从而也看下它的转换过程。
由于浮点数不是以直接格式存储,他有几部分组成,所以要转换浮点数,首先要把各部分的值分离出来。

          Address+0      Address+1     Address+2      Address+3
格式       SEEEEEEE       EMMMMMMM      MMMMMMMM       MMMMMMMM
二进制     01000000       10010000      00000000       00000000
16进制     40             90            00              00

可见:

  • S: 为0,是个正数。
  • E:为 10000001 转为10进制为129,129-127=2,即实际指数部分为2。
  • M:为 00100000000000000000000。 这里,在底数左边省略存储了一个1,使用 实际底数表示为 1.00100000000000000000000


    到此,我们吧三个部分的值都拎出来了,现在,我们通过指数部分E的值来调整底数部分M的值。调整方法为:如果指数E为负数,底数的小数点向左移,如果指数E为正数,底数的小数点向右移。小数点移动的位数由指数E的绝对值决定。
    这里,E为正2,使用向右移2为即得:
    100.100000000000000000000
    至次,这个结果就是4.5的二进制浮点数,将他换算成10进制数就看到4.5了,如何转换,看下面:
    小数点左边的100 表示为 (1 × 22) + (0 × 21) + (0 × 20), 其结果为 4。
    小数点右边的 .100… 表示为 (1 × 2-1) + (0 × 2-2) + (0 × 2-3) + … ,其结果为.5 。
    以上二值的和为4.5, 由于S 为0,使用为正数,即4.5 。
    所以,16进制 0x40900000 是浮点数 4.5 。

参见优质博客 :

http://www.cnblogs.com/onedime/archive/2012/11/19/2778130.html

精度问题的例子

int main()
{

    float test ;

    test = (float)0.064522351;
    printf("(float)0.064522351 is : %f \n", test);

    test = (float)0.0645 * 10000000 ;
    printf("(float)0.0645 * 10000000  is : %.8f \n" , test);
    int i;
    test = (float)0.0645;
    for ( i = 0 ; i < 7 ; i++ ) 
    {
        test  *= 10;
        printf("0.0645 x %i 个 10 : %f \n", i+1, test);
    }

    return 0;
}
  • 输出

(float)0.064522351 is : 0.064522
(float)0.0645 * 10000000 is : 644999.93750000
0.0645 x 1 个 10 : 0.645000
0.0645 x 2 个 10 : 6.450000
0.0645 x 3 个 10 : 64.500000
0.0645 x 4 个 10 : 645.000000
0.0645 x 5 个 10 : 6450.000000
0.0645 x 6 个 10 : 64500.000000
0.0645 x 7 个 10 : 645000.000000

可以看到:
* 0.064522351 保留了6位小数的精度.
* 0.0645 本身不能被 2^-n 有限表示 , 故而它本身就是一个近似值, 一次性乘以一个大数的时候放大了这个误差, 故而出现了上面的不精确 .

验证上面的猜想 , 将代码改为

int main()
{
    float test ;
    test = (float)0.064522351;
    printf("(float)0.064522351 is : %f \n", test);
    test = (float)0.0645 * 10000000 ;
    printf("(float)0.0645 * 10000000  is : %.8f \n" , test);
    int i;
    test = (float)0.0645;
    float f = (float)64.5 * 1000000;
    printf("(float)64.5 * 1000000  is : %.8f \n" , f);
    int multi_i = 1;
    for ( i = 0 ; i < 7 ; i++ ) 
    {
        multi_i*=10;
        printf("0.0645 x %i 个 10 : %f \n", i+1, test * multi_i);
    }
    return 0;
}

输出

    (float)0.064522351 is : 0.064522 
    (float)0.0645 * 10000000  is : 644999.93750000 
    (float)64.5 * 1000000  is : 64500000.00000000 
    0.0645 x 1 个 10 : 0.645000 
    0.0645 x 2 个 10 : 6.450000 
    0.0645 x 3 个 10 : 64.500000 
    0.0645 x 4 个 10 : 644.999939 
    0.0645 x 5 个 10 : 6449.999512 
    0.0645 x 6 个 10 : 64499.996094 
    0.0645 x 7 个 10 : 644999.937500 

可以看到 0.0645 * 10000 的时候就出现不精确了.
而64.5 * 1000000 都是精确的. 因为64.5是可以被精确表示的.

分析 float 类型的加减运算是精度损失

优质博客

http://www.blogjava.net/jelver/articles/340038.html

浮点型的减法运算

浮点加减运算过程比定点运算过程复杂。完成浮点加减运算的操作过程大体分为四步:
(1) 0操作数的检查;

   如果判断两个需要加减的浮点数有一个为0,即可得知运算结果而没有必要再进行有序的一些列操作。 

(2) 比较阶码(指数位)大小并完成对阶;

    两浮点数进行加减,首先要看两数的 指数位 是否相同,即小数点位置是否对齐。若两数指数位相同,
表示小数点是对齐的,就可以进行尾数的加减运算。反之,若两数阶码不同,表示小数点位置没有对齐,
此时必须使两数的阶码相同,这个过程叫做对阶 。

    如何对 阶(假设两浮点数的指数位为 Ex 和 Ey ):
    通过尾数的移位以改变 Ex 或 Ey ,使之相等。 由于浮点表示的数多是规格化的,
尾数左移会引起最高有位的丢失,造成很大误差;而尾数右移虽引起最低有效位的丢失,但造成的误差较小,
因此,对阶操作规定使 尾数右移,尾数右移后使阶码作相应增加,其数值保持不变。很显然,一个增加后的
移位 ( 相当于小数点左移 ) ,每右移一位,其阶码加 1 ,直到两数的阶码相等为止,右移的位数等于阶
差 △ E 。 

(3) 尾数(有效数位)进行加或减运算;

 对阶完毕后就可 有效数位求和。不论是加法运算还是减法运算,都按加法进行操作,其方法与定点加减
 运算完全一样。 

(4) 结果规格化并进行舍入处理。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值