仁扬 - 浮点数
type: channel
link: https://imlht.com/tag/浮点数/
items: 深入理解浮点数

深入理解浮点数
type: item
date: 2023-12-17T17:49:00+08:00
description: 注：本文包含AI创作。第一部分：浮点数的基础一、定义与原理浮点数是计算机科学中用于近似表示实数的一种方法。它们对于处理广泛的值特别有用，特别是在数值的大小超出整数所能表示的范围时。表示实数：在现实世界中，许多量（如温度、距离或速度）可以是任意精度的实数。浮点数允许计算机以有限的数字位来近似这些数值。构成：一个浮点数由三个部分组成：符号位（表明正负）、指数位（决定数值的规模或大小）和尾数位（提供实际数值的精确度）。动态范围：通过分离数值的大小（指数）和精确度（尾数），浮点数能够覆盖非常大的范围，从非常小的数到非常大的数。二、浮点数的内部表示（IEEE 754标准）IEEE 754标准是一种广泛采用的标准，用于在计算机中表示浮点数。标准格式：单精度（32位）：这种格式使用1位符号位、8位指数位和23位尾数位。双精度（64位）：在这种格式中，1位用于符号，11位用于指数，52位用于尾数。格式：| sign | exponent | fraction |符号位：第一位是符号位，0表示正数，1表示负数。指数位：指数位用于表示数值的大小。它是一个偏移的二进制表示，这意味着指数的实际值是指数位的值减去一个偏移量。为什么指数位的值需要偏移量？比如单精度浮点数，8位指数位可以表达256个数，但只能是非负数。如果使用+127偏移量（也就是指数3用130表示），负数就可以直接表达，并且可以直接比较大小。尾数位：尾数位（或称为有效位）提供了数值的精度。有两种情况：规约形式：在规约形式中，浮点数被表示为一个非零的数字，这个数字的小数点前有一个非零的数字（通常是1）。这样的表示确保了浮点数的精度和有效位数最大化。非规约形式：用来表达非常接近0的数字。在这种形式下，浮点数的表示允许尾数的最高位是0，这意味着可以表示比标准规约形式更小的数。这个地方可能看着很奇怪，它的作用在于对接近 0 的数字提供更高的精度。比如单精度浮点数 “0.000 ... 00102”，可以表示为 1.02 10^a，也可以表示为 0.000...102 10^b。如果 a 溢出了，存不了更小的小表达负数（只能存 0，指数位有偏移，没有更小的数了），这个数就表达不了！但是用 b 表达就可以装进 8 位指数位，也就可以保证精度了。特殊值的表示：IEEE 754还定义了一些特殊值的表示方式，如正无穷大（+Infinity）、负无穷大（-Infinity）和非数值（NaN，Not a Number）。第二部分：使用浮点数时的挑战和注意事项精度问题浮点数由于精度的限制，有时候对计算会有一定的影响。比如，二进制浮点数不能表达0.1和0.01，0.1的平方既不是准确的0.01，也不是最接近0.01的可表达的数。浮点数也不能表达圆周率 pi，所以 tan(pi/2) 不等于正无穷，也不会溢出。比较与运算我一般是缩放一定的比例后转换成 int 再比较，GPT4 给了一个新的思路：package main import ( "fmt" "math" ) // FloatEquals 比较两个浮点数是否足够接近，以epsilon作为容差 func FloatEquals(a, b, epsilon float64) bool { return math.Abs(a - b) < epsilon } func main() { a := 0.1 b := 0.2 c := 0.3 // 直接比较 - 往往不可靠 fmt.Println("Direct comparison:", b+c == a) // false // 使用FloatEquals函数比较 epsilon := 1e-9 // 定义一个足够小的epsilon值 fmt.Println("Epsilon comparison:", FloatEquals(b+c, a, epsilon)) // true }特殊值的处理（+Inf、-Inf 和 NaN）Go 语言如果你使用浮点数作为 map 的 key，你会发现 map[NaN] != map[NaN]！因为 NaN != NaN，所以哈希后也是不一致的，底层会加上随机数保证这一特性。最好不要直接用浮点数当作 key，除非你明确知道数据的形式。数值稳定性问题在连续的运算中，尤其是涉及大量或小量级数值的运算时，精度损失可能会累积。参考资料：维基百科-浮点数维基百科-IEEE 754