社区导航

 
楼主: lcofjp

[原创] 浅析浮点数

[复制链接]

5415

TA的帖子

185

TA的资源

版主

Rank: 6Rank: 6

发表于 2017-12-10 18:50:11 | 显示全部楼层 |阅读模式
本帖最后由 lcofjp 于 2017-12-10 19:02 编辑

在各种编程语言中,数值类型是最为常用的数据类型,数值类型包含两大类:整数和浮点数,整数类型比较通俗易懂,结构、取值范围以及运算过程都非常的直观,而浮点数就相对复杂了一些,本文的主要内容就是聊一聊浮点数。
在编程语言中,浮点数通常都是按着IEEE-754标准来实现的,本文以C语言为例,来探一探浮点数的坑。C语言中,浮点数类型包含单精度浮点数(float)和双精度浮点数(double),float为32位,double为64位。为了便于后面的测试,先来了解一下浮点数输入输出,在控制台程序中,通常用scanf和printf来实现输入和输出,浮点数的修饰符可以是以下之一:
f/F
十进制小数
e/E
科学计数法
g/G
根据数值大小自动选择f或者e格式
a/A
十六进制小数
在修饰符前面,还可以有一个数据类型长度修饰符,用来指明更具体的数据类型,长度修饰符有l和L,以f格式为例:
在scanf中,f对应float*,lf对应double*,Lf对应long double*,
而在printf中,f对应double,Lf对应long double,没有float,也就是说,用printf输出float都会被先转换为double之后再传递给printf并输出。这是因为在可变参数函数中,匹配到...的参数需要进行类型提升,整数类型遵循整数类型提升规则,浮点数float会被提升为double。更多类型的格式修饰符可以参考:http://www.cplusplus.com/reference/cstdio/printf/  和 http://www.cplusplus.com/reference/cstdio/scanf/

浮点数由三个部分组成:[1]s=符号位/sign,[2]q=指数/exponent,[3]c=尾数/significand/coefficient,表示形式为:
(−1)s × c × 2^q

float和double的各部分所占位数:
符号位(s)
指数(q)
尾数(c)
float
1
8
23
double
1
11
52
以float为例,float类型在内存中的存储结构看起来是这样的:
float-out.png
下面来了解一下浮点数各部分的求值过程:
举个例子,比如浮点数22.453125:
符号位只有一位,正数为0,负数为1,此数为正符号位为0。
整数部分和小数部分分别用二进制表示,为:
10110.011101
下一步是标准化,经过移动小数点,使小数点的左边只有一个1,也就是
1.0110011101 * 2^4
因为小数向左移动了4位,要乘以2^4保持不变。
上面的小数部分,去掉小数点左边的1,就是浮点数的尾数部分,22.453125的尾数部分就是:
0110011101,后面不足23位补0
指数部分就是4 + 127 = 131,用二进制表示就是10000011,其中另外加的127叫做指数偏差,double类型的指数偏差是1023。
那么现在22.453125的32位浮点数二进制表示已经出来了:
0 10000011 01100111010000000000000

类似于这种可以化成小数点左边只有一个1,指数部分的范围在[1,254]区间内的浮点数称为标准化浮点数(针对float类型),还有一些另类的数则是非标准化的,比如趋近于0的数以及0(下溢),趋向于正负无穷大的数(上溢):
特殊值的表示.png
0是一个特殊的值,无法标准化,所以用一个特殊的二进制全0来表示,当然还有一个-0,在某些语言中,+0等于-0,而在某些语言中则不相等。通常在C中是无法写出-0的字面量的。有些趋近于0的数值,在标准化后指数会下溢,也就是无法表示,此时可以使指数为0,而尾数部分不使用标准化的形式,这种数称为非标准化的。
QNaN和SNaN统称为NaN,NaN一般用于在计算过程中产生了无法表示的数值,比如0/0, 0×±∞, 任何数与NaN运算。QNaN在参与运算时通常不会产生异常,而SNaN在运算时会产生异常,所以SNaN可以用来初始化一些浮点数的内存,当这些浮点数未赋值为有效的浮点数并参与运算时,就会产生异常以指出程序的问题。NaN的指数为全1,尾数部分可以根据实现用于不同的用途,例如指示导致NaN的原因。至于是否支持QNaN和SNaN需要看编译器是否实现。
正负无穷大用于表示运算的上溢,也就是运算结果太大以至于无法表示。
为了方便测试,我写了一个浮点数构造函数,可以用来生成特定的浮点数,以及一个解析浮点数的函数,用来打印浮点数的各部分:
  1. void partitionFloat(float f) {
  2.     uint32_t x = *(uint32_t*)&f;
  3.     uint32_t e = (x >> 23) & 0xff;
  4.     for(int i=31; i>=0; i--) {
  5.         if(x & (1UL<<i)){
  6.             putchar('1');
  7.         }
  8.         else {
  9.             putchar('0');
  10.         }
  11.         if (i == 31 || i == 23) {
  12.             putchar(' ');
  13.         }
  14.     }
  15.     if ( e != 0 && e != 0xff) {
  16.         printf("\nexponent = %d\n", e - 127);
  17.     }
  18.     putchar('\n');
  19. }
  20. float constructFloat(uint32_t sign, uint32_t exp, uint32_t sig /*significand*/) {
  21.     float f = 0;
  22.     uint32_t *p = (uint32_t*)&f;
  23.     *p = (sign<<31) + ((exp&0xff) << 23) + (sig & (1<<23)-1);
  24.     return f;
  25. }
复制代码

测试:
  1.    float a = +0, b = -0;
  2.     partitionFloat(a);
  3.     partitionFloat(b);
  4.     partitionFloat(22.453125);
  5.     float sNaN = constructFloat(0, 255, 1);
  6.     float qNaN = constructFloat(0, 255, 1<<22);
  7.     float pInf = constructFloat(0, 255, 0);
  8.     float nInf = constructFloat(1, 255, 0);
  9.     float pZero = constructFloat(0, 0, 0);
  10.     float nZero = constructFloat(1, 0, 0);
  11.     float min = constructFloat(0, 1, 1);
  12.     float max = constructFloat(0, 254, (1<<23)-1);
  13.     printf("sNaN=%g, qNaN=%g, pInf=%g, nInf=%g, pZero=%g, nZero=%g, min=%g, max=%g\n", sNaN, qNaN, pInf, nInf, pZero, nZero, min, max);
复制代码
结果:
0 00000000 00000000000000000000000
0 00000000 00000000000000000000000
0 10000011 01100111010000000000000
exponent = 4

sNaN=nan, qNaN=nan, pInf=inf, nInf=-inf, pZero=0, nZero=-0, min=1.17549e-38, max=3.40282e+38

对于这些特殊数值的判断,并不能直接使用相等运算符去判断,因为并没有对应的可比较的字面量常量去表示nan,inf等,而是应该使用标准库提供的函数,比如在math.h中,可能包含这些函数或者宏定义的声明:
fpclassify 判断浮点数的类型,返回结果可能是:FP_INFINITE,FP_NAN,FP_NORMAL,FP_SUBNORMAL,FP_ZERO
isfinite 是否有限大小
isinf 是否是无穷大
isnan 是否是NaN
isnormal 是否标准化
signbit 符号位
详细内容可以参考:http://www.cplusplus.com/reference/cmath/ 或其他网络资源。
实例:
  1.     printf("2.2 is finite: %d\n", isfinite(2.2));
  2.     printf("signbit of -2.2 is: %d, and 2.2's is: %d\n", signbit(-2.2), signbit(2.2));
  3.     printf("%d,%d,%d,%d,%d\n", fpclassify(sNaN), fpclassify(qNaN), fpclassify(pInf), fpclassify(0.0), fpclassify(2.2));
复制代码
结果:
2.2 is finite: 1
signbit of -2.2 is: 1, and 2.2's is: 0
1,1,2,3,4

在float.h中,编译器提供了一些浮点数的极值,以及各组成部分相关的一些常量。

使用浮点数的注意事项:
1. 绝大多数实数无法用浮点数精确表示,在转换为浮点数的过程中要进行舍入或者截断,至于是舍入还是截断要参考具体实现。

2. 不要直接用等于运算符去判断浮点数运算结果是否相等,最经典的例子就是0.1+0.2不等于0.3,而是等于0.30000000000000004,而网址http://0.30000000000000004.com/ 说明了一切。 那么该如何判断两个浮点数是否近乎相等呢?这里要引入一个叫做相对误差的概念:
相对误差=|xc - x|/|x|, 如果xc与x的相对误差小于某个非常小的值,则可以认为这两个数近乎相等。
if( abs((a-b)/b) < 0.00001 ) 直接这样用是不行的,一个相对完美的实现还是比较复杂的,具体可以参考http://www.floating-point-gui.de/errors/comparison/ ,这里面有一个java版本的实现。

3. 尽量不要让两个相近的浮点数去做减法运算,这样会大大降低浮点数的精度,而应该想办法转换为其他运算。
比如说,表达式(1-cosx)/sin2x,在x接近与0时,结果误差就会非常大,而转换成1/(1+cosx)去计算,误差就会非常小。

4. 浮点运算会带来误差,a*b*c并不总是等于c*b*a,浮点运算带来误差这个问题确实很棘手,我以前写过一个浮点数转字符串的程序(参见:http://bbs.eeworld.com.cn/thread-527776-1-1.html),结果并不尽人意,原因就是在转换过程中进行了运算,导致结果出现偏差,所以说浮点数转字符串并不是一个简简单单的问题,而是非常的复杂,到底有多复杂,我现在还没弄明白,主要也是没时间和精力去详细了解,有兴趣的同学可以参考一些开源项目,比如谷歌js的v8引擎的浮点数转换部分的参考:https://github.com/google/double-conversion 。这也是为什么如果printf使能了浮点输出的话,体积大增的原因。


此内容由EEWORLD论坛网友lcofjp原创,如需转载或用于商业用途需征得作者同意并注明出处


此帖出自编程基础论坛

评分

1

查看全部评分

EEWORLD开发板置换群:309018200,——电工们免费装β的天堂,虽然在群里买不到板子,但是可以学会开车;虽然学不到技术,但是可以学会开车;商家勿入!加群暗号:喵


回复

使用道具 举报

5415

TA的帖子

185

TA的资源

版主

Rank: 6Rank: 6

 楼主| 发表于 2017-12-10 19:05:51 | 显示全部楼层
有些数的指数部分在编辑中搞乱了,我相信大家都能区分的出来。
EEWORLD开发板置换群:309018200,——电工们免费装β的天堂,虽然在群里买不到板子,但是可以学会开车;虽然学不到技术,但是可以学会开车;商家勿入!加群暗号:喵


回复

使用道具 举报

3269

TA的帖子

0

TA的资源

纯净的硅(高级)

Rank: 6Rank: 6

发表于 2017-12-12 00:00:04 | 显示全部楼层
好文
以前第一次处理浮点数的时候
也是搞得一头雾水
So what......


回复

使用道具 举报

544

TA的帖子

235

TA的资源

版主

Rank: 6Rank: 6

发表于 2017-12-14 12:44:49 | 显示全部楼层
好文,学习啦


回复

使用道具 举报

5

TA的帖子

0

TA的资源

一粒金砂(初级)

Rank: 1

发表于 2018-2-9 15:51:59 | 显示全部楼层
谢谢了


回复

使用道具 举报

68

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2018-5-4 19:17:34 | 显示全部楼层
写得不错,其实说起来,IEEE754标准中的浮点数还算是表示比较简单的,比国内数据结构课本里的那些奇特的浮点数容易理解多了…………


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

  • 论坛活动 E手掌握

    扫码关注
    EEWORLD 官方微信

  • EE福利  唾手可得

    扫码关注
    EE福利 唾手可得

小黑屋|手机版|Archiver|电子工程世界 ( 京ICP证 060456 )

GMT+8, 2018-10-18 02:15 , Processed in 0.195898 second(s), 17 queries , Gzip On, MemCache On.

快速回复 返回顶部 返回列表