本文讨论三个问题

  1. 计算机如何表示浮点数,不同平台,处理器,是否一致
  2. 32位精度浮点数float与64位精度浮点数double能表示的范围
  3. 浮点数是如何存储的 最后检验 printf(“%d”, 2.5)

IEEE754标准

4种浮点数值的表示 1. 单精度 2. 双精度 3. 延伸单精确度(43位元以上) 4. 延伸双精确度(79位元以上)

浮点数表示

$\displaystyle Value=sign\times exponent\times fraction$ 浮点数的实际值等于符号位(sign bit)乘以指数偏移值(exponent bias)再乘以分数值(fraction) 浮点数

32位元双精度

$1$ $8$ $23$
$S$ $Exp$ $Fraction$
$31$ $30$~$23$ 偏正值$(+127)$ $22$~$0$

单精度指数范围$-126$ ~ $+127$,指数大小从$1$~$254$($0$和$255$是特殊值)

单精度浮点数极值

单精度浮点数极值 在表示浮点数时+127便于比较指数大小

64位元双精度

$1$ $11$ $52$
$S$ $Exp$ $Fraction$
$63$ $62$~$52$ 偏正值$(+1023)$ $51$~$0$

双精度指数范围$-1022$ ~ $+1023$,指数大小从$1$~$2046$($0$和$2047$是特殊值)

浮点数的比较

浮点数的表示顺序很好的方便了数值比较 按照符号位、指数域、尾数域的顺序作字典比较。所有正数大于负数;正负号相同时,指数的二进位表示法更大的其浮点数值更大

浮点数的舍入

  • 舍入到最接近:修约到最接近,在一样接近的情况下偶数优先(Ties To Even,这是预设的修约方式):会将结果修约为最接近且可以表示的值,但是当存在两个数一样接近的时候,则取其中的偶数(在二进位中式以$0$结尾的)。
  • 朝$+\infty$方向舍入:会将结果朝正无限大的方向舍入。
  • 朝$-\infty$方向舍入:会将结果朝负无限大的方向舍入。
  • 朝$0$方向舍入:会将结果朝0的方向舍入。

精度

第一个有效数字必定是「1」,所以这个「1」并不会储存,称之为“隐藏位”。 单精度表示的有效位数$log2^{24}=7.22$(尾数域的第一个1) 双精度表示的有效位数$log2^{53}=15.95$ 由以上的计算,单精和双精浮点数可以保证7位和15位十进制有效数字。

例子

使用单双精度表示$(1001.0111010)_2 = +1.001011101×2^3$

  1. 单精度:符号位$0$, 偏移指数$(3+127=130(10000010))$,尾数$1.001011101$隐藏最高位($001011101$) 因此表示为$0\ 10000010\ 00101110100000000000000$
  2. 双精度:偏移指数$(3+1023=1026(10000000010))$, 尾数$0010111010000000000000000000000000000000000000000000$ 因此表示为$0\ 10000000010\ 0010111010000000000000000000000000000000000000000000$

双精度测试

1
2
3
4
#include <stdio.h>  
int main() {  
    printf("%d", 2.55);  
}  

x86实验:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
        _,met$$$$$gg.                                                           
     ,g$$$$$$$$$$$$$$$P.                                                        
   ,g$$P""       """Y$$.".                                                      
  ,$$P'              `$$$.                                                      
',$$P       ,ggs.     `$$b:                                                     
`d$$'     ,$P"'   .    $$$                               ,#.                    
 $$P      d$'     ,    $$P      ##:          :##        :###:                   
 $$:      $$.   -    ,d$$'      ##'          `##         `#'                    
 $$;      Y$b._   _,d$P'    __  ##     __     ##  __      _     __          _   
 Y$$.    `.`"Y$$$$P"'     ,####:##  ,######.  ##.#####. :### ,######. ###.####: 
 `$$b      "-.__         ,##' `###  ##:  :##  ###' `###  ##' #:   `## `###' `##:
  `Y$$b                  ##    `##  ##    ##  ##'   `##  ##    ___,##  ##:   `##
   `Y$$.                 ##     ##  #######:  ##     ##  ##  .#######  ##'    ##
     `$$b.               ##     ##  ##'       ##     ##  ##  ##'  `##  ##     ##
       `Y$$b.            ##.   ,##  ##        ##    ,##  ##  ##    ##  ##     ##
         `"Y$b._         :#:._,###  ##:__,##  ##:__,##' ,##. ##.__:##. ##     ##
             `""""       `:#### ###  ######'  `######'  #### `#####"## ##     ##
Linux 4.14.0 x86_64
gdb (Debian 7.12-6) 7.12.0
gcc version 6.3.0

启用gdb查看AT&T汇编

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(gdb) disassemble main
Dump of assembler code for function main():
   0x000055bf1bfb67c0 <+0>:	 push   %rbp
   0x000055bf1bfb67c1 <+1>:	 mov    %rsp,%rbp
   0x000055bf1bfb67c4 <+4>:	 sub    $0x10,%rsp
=> 0x000055bf1bfb67c8 <+8>:     mov    0xb1(%rip),%rax # 0x55bf1bfb6880
   0x000055bf1bfb67cf <+15>:	mov    %rax,-0x8(%rbp)
   0x000055bf1bfb67d3 <+19>:	movsd  -0x8(%rbp),%xmm0
   0x000055bf1bfb67d8 <+24>:	lea    0x99(%rip),%rdi # 0x55bf1bfb6878
   0x000055bf1bfb67df <+31>:	mov    $0x1,%eax
   0x000055bf1bfb67e4 <+36>:	callq  0x55bf1bfb6670 <printf@plt>
   0x000055bf1bfb67e9 <+41>:	mov    $0x0,%eax
   0x000055bf1bfb67ee <+46>:	leaveq 
   0x000055bf1bfb67ef <+47>:	retq   
End of assembler dump.

浮点数查看

1
2
(gdb) x/fg 0x55bf1bfb6880
0x55bf1bfb6880:	2.5499999999999998 

查看8个字节信息

1
2
(gdb) x/8tb 0x55bf1bfb6880
0x55bf1bfb6880:	01100110  01100110  01100110  01100110  01100110  01100110  00000100  01000000

在x86平台采用小端字节序, 调整顺序

01000000 00000100 01100110 01100110 01100110 01100110 01100110 01100110

采用IEEE754计算

0 10000000000 1.0100011001100110011001100110011001100110011001100110 指数纠偏:1024-1023 = 1; 尾数:1.275

结果$1.275*2^1=2.55$ 符合预期

单精度测试

1
2
3
int main() {
    float a = 2.55;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
(gdb) disassemble main
Dump of assembler code for function main():
   0x0000562c35596780 <+0>:	 push   %rbp
   0x0000562c35596781 <+1>:	 mov    %rsp,%rbp
=> 0x0000562c35596784 <+4>:	 movss  0x98(%rip),%xmm0 # 0x562c35596824
   0x0000562c3559678c <+12>:	movss  %xmm0,-0x4(%rbp)
   0x0000562c35596791 <+17>:	mov    $0x0,%eax
   0x0000562c35596796 <+22>:	pop    %rbp
   0x0000562c35596797 <+23>:	retq   
End of assembler dump.

(gdb) x/fw 0x562c35596824
0x562c35596824:	2.54999995

(gdb) x/4tb 0x562c35596824
0x562c35596824:	00110011  00110011  00100011  01000000

计算:00110011 00110011 00100011 01000000 =>01000000 00100011 00110011 00110011 0 10000000 1.01000110011001100110011 128-127=1; 1.274999976158142 1.274999976158142*2=2.54999995232 符合预期

ARM32测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$pi@raspberrypi:~/Documents $ linuxlogo
    .~~.   .~~.         ___                __                      ___  _ 
   '. \ ' ' / .'       / _ \___  ___ ___  / /  ___  ___ ___ _ __  / _ \(_)
    .~ .~~~..~.       / , _/ _ `(_-</ _ \/ _ \/ -_) __/ __/ // / / ___/ / 
   : .~.'~'.~. :     /_/|_|\_,_/___/ .__/_.__/\__/_/ /_/  \_, / /_/  /_/  
  ~ (   ) (   ) ~                 /_/                    /___/          
 ( : '~'.~.'~' : )    
  ~ .~ (   ) ~. ~     Linux Version 4.14.30-v7+
   (  : '~' :  )      Compiled #1102 SMP Mon Mar 26 16:45:49 BST 2018
    '~ .~~~. ~'       Four ARM  Processors, 927M RAM
        '~'           153.60 Bogomips Total
                      raspberrypi
					  
pi@raspberrypi:~/Documents $ neofetch 
  .',;:cc;,'.    .,;::c:,,.    pi@raspberrypi
 ,ooolcloooo:  'oooooccloo:    --------------
 .looooc;;:ol  :oc;;:ooooo'    OS: Raspbian GNU/Linux 9.4 (stretch) armv7l
   ;oooooo:      ,ooooooc.     Model: Raspberry Pi 3 Model B Rev 1.2
     .,:;'.       .;:;'.       Kernel: 4.14.30-v7+
     .... ..'''''. ....        Uptime: 2 hours, 4 minutes
   .''.   ..'''''.  ..''.      Packages: 1321
   ..  .....    .....  ..      Shell: bash 4.4.12
  .  .'''''''  .''''''.  .     CPU: ARMv7 rev 4 (v7l) (4) @ 1.2GHz
.'' .''''''''  .'''''''. ''.   Memory: 126MB / 927MB
'''  '''''''    .''''''  '''
.'    ........... ...    .'.
  ....    ''''''''.   .''.
  '''''.  ''''''''. .'''''
   '''''.  .'''''. .'''''.
    ..''.     .    .''..
          .''''''' 
           ......

测试代码

1
2
3
4
5
#include<stdio.h>
int main() {
    float f = 2.55;
    double d = 2.55;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$(gdb) disassemble main
Dump of assembler code for function main:
   0x00000000 <+0>:	 push	{r11}		; (str r11, [sp, #-4]!)
   0x00000004 <+4>:	 add	r11,sp, #0
   0x00000008 <+8>:	 sub	sp, sp, #20
   0x0000000c <+12>:	ldr	r3, [pc, #32]	; 0x34 <main+52>
   0x00000010 <+16>:	str	r3, [r11, #-8]
   0x00000014 <+20>:	ldr	r2, [pc, #28]	; 0x38 <main+56>
   0x00000018 <+24>:	ldr	r3, [pc, #28]	; 0x3c <main+60>
   0x0000001c <+28>:	strd   r2, [r11, #-20]	; 0xffffffec
   0x00000020 <+32>:	mov	r3, #0
   0x00000024 <+36>:	mov	r0, r3
   0x00000028 <+40>:	add	sp, r11, #0
   0x0000002c <+44>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00000030 <+48>:	bx	 lr
   0x00000034 <+52>:	eormi	  r3, r3, r3, lsr r3
   0x00000038 <+56>:	strbtvs	r6, [r6], -r6, ror #12
   0x0000003c <+60>:	andmi	  r6, r4, r6, ror #12
End of assembler dump.
$(gdb) x/fw 0x34
0x34 <main+52>:	2.54999995
$(gdb) x/4tb 0x34
0x34 <main+52>:	00110011  00110011  00100011  01000000
$(gdb) x/fg 0x38
0x38 <main+56>:	2.5499999999999998
$(gdb) x/8tb 0x38
0x38 <main+56>:	01100110  01100110  01100110  01100110	01100110  01100110  00000100 01000000

Arch64

1
2
ubuntu@ubuntu:~$ uname -a
Linux ubuntu 4.11.12-chainsx-edition-CX-v5 #1 SMP PREEMPT Thu Feb 22 12:09:39 CST 2018 aarch64 aarch64 aarch64 GNU/Linux

测试代码

1
2
3
4
5
#include<stdio.h>
int main() {
    float f = 2.55;
    double d = 2.55;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$(gdb) disassemble main
Dump of assembler code for function main:
   0x0000000000000000 <+0>:  sub	sp, sp, #0x10
   0x0000000000000004 <+4>:	 ldr	w0, 0x20
   0x0000000000000008 <+8>:	 str	w0, [sp,#4]
   0x000000000000000c <+12>:	ldr	x0, 0x28
   0x0000000000000010 <+16>:	str	x0, [sp,#8]
   0x0000000000000014 <+20>:	mov	w0, #0x0  // #0
   0x0000000000000018 <+24>:	add	sp, sp, #0x10
   0x000000000000001c <+28>:	ret
End of assembler dump.

$(gdb) x/fw 0x20
0x20:	2.54999995

$(gdb) x/4tb 0x20
0x20:  00110011  00110011  00100011  01000000

$(gdb) x/fg 0x28
0x28:    2.5499999999999998

$(gdb) x/8tb 0x28
0x28:  01100110  01100110  01100110  01100110  01100110 01100110  00000100  01000000