本文讨论三个问题

  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$

双精度测试

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

x86实验:

        _,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汇编

(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.

浮点数查看

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

查看8个字节信息

(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$ 符合预期

##单精度测试

int main() {
    float a = 2.55;
}
(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测试

$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
'''  '''''''    .''''''  '''
.'    ........... ...    .'.
  ....    ''''''''.   .''.
  '''''.  ''''''''. .'''''
   '''''.  .'''''. .'''''.
    ..''.     .    .''..
          .''''''' 
           ......

测试代码

#include<stdio.h>
int main() {
    float f = 2.55;
    double d = 2.55;
}
$(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

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

测试代码

#include<stdio.h>
int main() {
    float f = 2.55;
    double d = 2.55;
}
$(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
分类: 汇编

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.