Floating point integer
提到浮点数,首先要看下定点数是什么概念。在定点数表达法中,其小数点固定地位于实数所有数字中间的某个位置。比如有四位小数,那么所有数字都是有固定的四位小数的, 12345 12345 12345 则表示 1.2345 1.2345 1.2345。缺点很明显,精度和数据大小无法兼顾。
浮点数的表示涉及到不同的处理器的支持情况。对于需要高精度计算的数值分析来说,搞清楚浮点数背后的故事是相当有必要的,这可以减少或消除不必要的精度损失。
IEEE 754
我们先回顾一下科学计数 (Scientific notation), ± 123456.0 \pm123456.0 ±123456.0 可以表示成 ± 1.23456 E + 05 \pm1.23456E+05 ±1.23456E+05。由有效数字 (significand)、底数 (base)、指数 (exponent) 组成。有效数字又称为尾数 (mantissa)。
上面的数值可以表示为如下的公式:
±
(
1
×
1
0
0
+
2
×
1
0
−
1
+
3
×
1
0
−
2
+
4
×
1
0
−
4
+
5
×
1
0
−
5
)
×
1
0
5
\pm(1\times 10^0 + 2\times 10^{-1} + 3\times 10^{-2} + 4\times 10^{-4} + 5\times 10^{-5})\times 10^5
±(1×100+2×10−1+3×10−2+4×10−4+5×10−5)×105
计算机的浮点数也是基于类似的原理,只不过计数的进制由十进制换成了二进制。对于 9.625 9.625 9.625,其二进制表示为 1.001101 × 2 3 1.001101\times2^3 1.001101×23。
20世纪80年代,业界还没有一个统一的浮点数标准,直到 Intel 公司提出的 KCS 方案出现,这直接影响了后来IEEE 制定的 (IEEE Standard for Binary Floating Point Arithmetic) ANSI/IEEE Std 754-1985
标准,该标准限定底数为 2,目前,几乎所有的计算机都支持 IEEE 754 标准,它大大地改善了科学程序的可移植性。
IEEE 浮点数标准是从逻辑上用三元组 {S,E,M} 来表示一个数 V。它实质上是规定了构成浮点数的各个字段的含义、布局以供大家进行解释,可以看成是一种编码规则。
V = ( − 1 ) s × ( 1 + M ) × 2 E − B i a s V=(-1)^s\times (1+M)\times 2^{E-Bias} V=(−1)s×(1+M)×2E−Bias
- 符号位 s (Sign)决定数是正数(s=0)还是负数(s=1),而对于数值 0 的符号位解释则作为特殊情况处理。
- 有效数字位 M (Significand)是二进制小数位。因此又被称为小数位、尾数。
- 指数位 E (Exponent)是 2 的幂(可能是负数),它的作用是对浮点数加权。为了表示负数,实际有个偏移。单精度为 + 127 +127 +127,双精度为 + 1023 +1023 +1023。
单精度浮点 s 1bit,E 8bits,M 23bits。
双精度浮点 s 1bit,E 11bits,M 52bits。
指数全
0
0
0 和全
1
1
1 为保留值。
因此单精度浮点的最大最小值为:
Min: 指数为1,尾数为0。 ± 1.0 × 2 − 126 \pm1.0\times2^{-126} ±1.0×2−126 ≈ \approx ≈ ± 1.2 × 1 0 − 38 \pm1.2\times10^{-38} ±1.2×10−38
Max: 指数为254,尾数全1。 ± 2.0 × 2 + 127 \pm2.0\times2^{+127} ±2.0×2+127 ≈ \approx ≈ ± 3.4 × 1 0 + 38 \pm3.4\times10^{+38} ±3.4×10+38
因此双精度浮点的最大最小值为:
Min: 指数为1,尾数为0。 ± 1.0 × 2 − 1022 \pm1.0\times2^{-1022} ±1.0×2−1022 ≈ \approx ≈ ± 2.2 × 1 0 − 308 \pm2.2\times10^{-308} ±2.2×10−308
Max: 指数为2046,尾数全1。 ± 2.0 × 2 + 1023 \pm2.0\times2^{+1023} ±2.0×2+1023 ≈ \approx ≈ ± 1.8 × 1 0 + 308 \pm1.8\times10^{+308} ±1.8×10+308
因此上文提到的
1.001101
×
2
3
1.001101\times2^3
1.001101×23。
符号位为
0
0
0,指数段为
3
+
127
=
130
3+127=130
3+127=130,尾数部分则照抄,右侧补零。
可以表示成
0
,
10000010
,
001101
,
0
,
0000
,
0000
,
0000
,
0000
0,10000010,001101,0,0000,0000,0000,0000
0,10000010,001101,0,0000,0000,0000,0000
非规格化数
前面提到指数全
0
0
0 和全
1
1
1 为保留值。
指数段全0,隐含着隐含位也为0。
x
=
(
−
1
)
s
×
(
0
+
M
)
×
2
−
B
i
o
s
x=(-1)^s\times(0+M)\times2^{-Bios}
x=(−1)s×(0+M)×2−Bios。表示非规格化数。
指数和尾数都位0的值表示 x = ( − 1 ) s × ( 0 + 0 ) × 2 − B i o s = ± 0.0 x=(-1)^s\times(0+0)\times2^{-Bios} = \pm0.0 x=(−1)s×(0+0)×2−Bios=±0.0
指数段全1,尾数段非0的特殊值定为 NaN
(Not any Number)。
指数段全1,尾数全0的值表示 ± ∞ \pm\infty ±∞。
Rounding rules
Mode | Example value | ||||
---|---|---|---|---|---|
+11.5 | +12.5 | -11.5 | -12.5 | ||
to nearest, ties to even | +12.0 | +12.0 | -12.0 | -12.0 | |
to nearest, ties away from zero | +12.0 | +13.0 | -12.0 | -13.0 | |
tward 0 | +11.0 | +12.0 | +13.0 | -11.0 | |
tward + ∞ \infty ∞ | +12.0 | +13.0 | -11.0 | -12.0 | |
tward - ∞ \infty ∞ | +11.0 | +12.0 | -12.0 | -13.0 | |
libc support
Libc Floating-Point-Parameters
C 库里的 float.h
头文件描述了当前计算平台上所提供的浮点操作支持。
libc 定义的浮点异常
IEEE 754 还提出 5 种类型的浮点异常,即上溢、下溢、除零、无效运算和不精确。默认情况下浮点运算过程中出现的异常记录在浮点状态字中。用户程序可以查看状态字判断出现了何种异常。当然也可以使能 trap
这个异常,由操作系统接管,这个时候异常程序将收到 SIGFPE
信号,该信号的默认行为是异常进程被终止。
非法操作 (Invalid Operation)
- 加减 ∞ − ∞ \infty-\infty ∞−∞
- 乘法 0 × ∞ 0\times\infty 0×∞
- 除法 0 / 0 0/0 0/0 or ∞ / ∞ \infty/\infty ∞/∞
- 取余 X REM y。y 是 0 或者 x 为无穷
- 开方 − 1 \sqrt{-1} −1
- …
除零 (Division by Zero)
fanite nonzero value / 0
上溢出 (Overflow)
当运算过程中出现超出当前数据类型所能表示的最大浮点数时触发上溢出异常。
下溢出 (Underflow)
当运算过程中出现超出当前数据类型所能表示的最小浮点数时触发下溢出异常。
不精确 (Inexact)
当出现 Rounding 不精确,或 2 \sqrt2 2、 2.0 / 3.0 2.0/3.0 2.0/3.0 这种操作时触发该异常。
上面的异常中可能同时出现,其中不精确异常的优先级最低,即多种浮点异常同时出现时它会被
操作系统屏蔽。libc 提供的浮点接口可参看。
ARM SIMD / NEON / VFP
- SIMD (Single Instruction Multiple Data). Introduced in the ARMv6 architecture.
- NEON is Advanced SIMD extension. The Neon hardware shares the same floating-point registers as used in VFP.
- VFP (Vector Floating Point) is a classic floating point hardware accelerator. It is not a parallel architecture like NEON.
VFP 是专为浮点运算加速而设计的运算单元,NEON 则是高级的 SIMD 处理单元,专为向量计算提供加速,在一些架构设计中 VFP 和 NEON 共享一套寄存器,如此设计方便了上下文切换。需要注意的是某些设备如 armv7 上的 NEON 不完全兼容 IEEE-754 标准,极小的浮点数值会被刷成 0,导致精度丢失。因此 GCC 默认是不适用 NEON 指令的,除非显式指定 -mfpu=neon -funsafe-math-optimizations
。ARMv8 中貌似解决了这个问题,具体需要查阅手册中相关章节的描述。
Neon intrinsic and assembly code
这部分之后再开篇。
Floating-point exception
这一节介绍一下 arm 处理器中的浮点异常相关的东西。
Execution of a floating-point instruction, or an Advanced SIMD instruction that performs floating-point operations, can generate an exceptional condition, called a floating-point exception.
AArch64
The -mfloat-abi
option is not valid with ARMv8 AArch64 targets.
AArch64 targets use hardware floating-point instructions and hardware floating-point
linkage. However, you can prevent the use of floating-point instructions or
floating-point registers for AArch64 targets with the -mcpu=+nofp+nosimdname
option.
Subsequent use of floating-point data types in this mode is unsupported.
ARM
gcc
-mcpu=cortex-a9 \
-mfpu=neon -funsafe-math-optimizations or -mfpu=vfpv3 \
-mfloat-abi=value
value can be:
- soft
Software library functions for floating-point operations and software floating-point linkage.
- softfp
Hardware floating-point instructions and software floating-point linkage.
- hard
Hardware floating-point instructions and hardware floating-point linkage.
-msoft-float is equivalent to -mfloat-abi=soft.
-mhard-float is equivalent to -mfloat-abi=hard.
The -mfpu= option is ignored when -mfloat-abi=soft is specified,
or when compiling for AArch64 targets.
Specifies whether to use hardware instructions or software library functions
for floating-point operations, and which registers are used to pass floating-point
parameters and return values.
Compiler ISSUE
armeb-linux-gnueabi-gcc -mcpu=cortex-a9 -march=armv7-a -marm -mfloat-abi=softfp --stat lazy.S fpu.c -o a.out
lazy.S:11: Error: selected processor does not support `vstmia r0!,{d0-d15}' in ARM mode
armeb-linux-gnueabi-gcc -mcpu=cortex-a9 -march=armv7-a -marm -mfpu=vfp --stat lazy.S fpu.c -o a.out
/tmp/ccglMAVV.s:53: Error: selected processor does not support `vadd.f32 s0,s1,s2' in ARM mode