Go 语言类型 浮点型
基本概念
浮点型数据(Floating-point-number)用于表示包含小数部分的数值,也称为实数(Real-number)。
浮点类型
Go 语言提供两种浮点类型 float32
和 float64
。这两种类型遵循 IEEE-754 标准,其中 float32
是单精度浮点数,float64
是双精度浮点数:
类型 | 字节长度 | 精确位数 |
---|---|---|
float32 |
4 | 小数点后 7 位 |
float64 |
8 | 小数点后 15 位 |
单精度浮点数在运算时会迅速积累误差,应优先使用双精度浮点数。
表示方法
浮点型数据在 Go 语言中有两种表示方式:
- 十进制小数:由数字和小数点组成,例如:
0.210
。 - 指数形式:由数字和字母
e
组成,例如表示1234.5
指数形式为12.345e2
或1.2345e3
。
浮点结构
浮点数在计算机中表示基于科学记数法(),在内存中按指数形式储存,分为三部分:符号位(Sign)、指数(Exponent)和尾数(Mantissa):
- 符号位:用于表示数值正负的 1 个单独位,
0
表示正数,1
表示负数。 - 指数:用于表示基数的幂。不是直接存储实际指数值,而是存储一个偏移指数,有利于表达正负。例如,在
float32
类型中,指数部分占用 8 位,偏移量为127
,实际指数值是存储的指数值 - 偏移量。如果存储的指数值是120
,那么实际指数值是120-127=-7
。 - 尾数:用于表示有效数字或分数部分。尾数部分用二进制表示为
1.xxxx
形式,其中1
默认隐藏(对于规格化的数),不占用存储空间。实际存储值是xxxx
,也就是小数部分。在float32
中,尾数部分占用 23 位,加上隐含的1
,总共可以提供 24 位精度。
在十进制中,科学记数法这样表示:±d.d...d * 10^n
,其中 d.d...d
是尾数部分,基数为 10
,而 n
是指数。在二进制中,基数变成了 2
。假设有一个 32 位的 IEEE 754 浮点数:
- 符号位:
0
,代表是正数。 - 指数:
10000011
等于十进制131
,表示实际指数4
。 - 尾数:
1010000...
(省略后面许多0
,注意隐含前导1
)。
这个数值是 1.101 * 2^4
,转换成十进制大约是 1.625 * 16 = 26
。
反过来,对一个单精度浮点数 6.75
转为科学计数法:
- 符号位:
0
。 - 指数:实际指数为
2
,加上偏移量127
等于129
,二进制表示为10000001
。 - 尾数:将
6.75
转为二进制浮点数等于110.11
,规范化表示为1.1011 * 2^2
,取小数点后部分1011
,储存时补齐到 23 位就是10110000...
。
下面是用代码演示推导过程:
package main
import (
"fmt"
"math"
)
func main() {
// 定义一个单精度浮点数
var f float32 = 6.75
// 将浮点数转换为其二进制表示的 uint32 类型
bits := math.Float32bits(f)
// 提取符号位、指数部分和尾数部分
sign := bits >> 31
exponent := (bits >> 23) & 0xFF
mantissa := bits & 0x7FFFFF
fmt.Printf("符号位: %d\n", sign) // 输出:0
fmt.Printf("指数部分: %d (%08b)\n", exponent, exponent) // 输出:129 (10000001)
fmt.Printf("尾数部分: %d (%023b)\n", mantissa, mantissa) // 输出:5767168 (10110000000000000000000)
}
这种存储方式使得浮点数能表示非常宽泛的数值,但由于指数和尾数部分储存位数有限,有时候会导致精度问题。例如,0.1
用二进制表示开始为 0.000110011...
,后面的 0011
会无限循环,直到达到精度限制。大部分十进制小数在二进制中是无限循环小数,因此在计算机中不能被精确表示。
声明和初始化
浮点数字面量自动类型推断为 float64
,有下面 4 种声明和初始化方式:
package main
import "fmt"
func main() {
// 声明但不赋值,初始化零值为 0.0
var a float64
// 显式声明并赋值
var b float32 = 4
// 短变量声明,自动推导类型为 float64,值为 5.10
c := 5.09999999999999999999999
// 表达式赋值,强调类型
d := float32(3)
// 打印变量类型和值
fmt.Printf("类型为:%T\t%T\t%T\t%T\n", a, b, c, d)
fmt.Printf("浮点值:%0.1f\t%.1f\t%.2f\t%0.2f\n", a, b, c, d)
}
浮点运算
可以对浮点数进行除了 %
(要使用 math.Mod
) 以外任意数学和比较运算,但计算结果有误差:
package main
import "fmt"
func main() {
var a, b float32 = 5.01, 3.1
fmt.Println("32 位浮点数加法:", a+b) // 输出:8.110001
fmt.Println("32 位浮点数减法:", a-b) // 输出:1.9100003
fmt.Println("32 位浮点数乘法:", a*b) // 输出:15.531
fmt.Println("32 位浮点数除法:", a/b) // 输出:1.6161292
var c, d float64 = 5.01, 3.1
fmt.Println("\n64 位浮点数加法:", c+d) // 输出:8.11
fmt.Println("64 位浮点数减法:", c-d) // 输出:1.9099999999999997
fmt.Println("64 位浮点数乘法:", c*d) // 输出:15.531
fmt.Println("64 位浮点数除法:", c/d) // 输出:1.6161290322580644
}
从上面结果可以看出,精度越高误差越小。
舍入误差
当 float64
类型变量值小数位数超过 15 位时,超出部分会被舍去。同理,float32
类型在小数位数超过 7 位时会发生截断,因此不要直接比较两个浮点数相等性:
package main
import "fmt"
func main() {
// 定义两个 float64 类型变量,尝试赋予 b 超出 float64 精度的值
a, b := 1.0, 1.0000000000000001
// 检查两个浮点数是否相等。由于精度限制,b 被视为与 a 相等
if a == b {
fmt.Printf("a 等于 b\na 的值为:%v\nb 的值为:%v\n", a, b)
}
}
导致浮点数错误的极限值称为机器精度(machine epsilon)。高精度科学计算中,应当使用 math
标准库中相关功能来避免舍入误差。
特殊值
除法运算中,除数为浮点数 0,结果会得到特殊值正无穷大(+Inf
)、负无穷大(-Inf
)和非数(NaN
):
package main
import (
"fmt"
"math"
)
func main() {
b := float64(0)
// 正数除以浮点数 0,结果为正无穷大
fmt.Println("正无穷大:", 1.1/b, math.Inf(1)) // 输出:+Inf
// 负数除以浮点数 0,结果为负无穷大
fmt.Println("负无穷大:", -1.1/b, math.Inf(-1)) // 输出:-Inf
// 被除数和为 0,结果为 Not a Number
fmt.Println("非数:", 0/b, math.NaN()) // 输出:NaN
}