某一天,某人问我,Go里面可以用浮点数进行比较吗? 某同事答约,可以的。那到底可以不可以。"Talk is Cheap, Show Me the Code."
我找到了网络上比较有名的验证浮点数精度的例子,看看Go能不能通过
func testPrecision(){
a := 0.1
b := 0.2
c := 0.3
if a + b == c {
fmt.Printf("Isprecision:%v", true)
}else{
fmt.Printf("Isprecision:%v", false)
}
}
但是很不幸,答案是否定的,输出了false。但是为什么呢? 这是一个可以被编程语言解决的问题吗?
Isprecision:false
现在回想一下大学教授的计算机组成原理的知识。计算机上只对0,1进行保存。对于整数,我们非常简单地解决了这个问题。只需要在存储字节地最高位腾出一个位来表示该整数的正负即可。
但是在计算机中,浮点数的存储可十分复杂。在计算机发展的过程中,出现过许多不同的浮点数存储办法。其中包括以下三种:
浮点数表示方法 | 优点 | 缺点 |
---|---|---|
定点数表示方法 | 精度准确,不存在近似 | 存储空间浪费,不利于表示超高精度小数 |
有理数表达方式 | 精度准确,可能存在近似 | 存储空间浪费,不利于表示超高精度小数 |
浮点数表达方式 | 存在近似 | 节约存储空间,可以同时表示超高精度和超大数值 |
大部分计算机采用 IEEE 754 浮点数算术标准。在Go 语言中拥有float32和float64两种不同长度的浮点数变量类型。
单精度(float32)
单精度使用8位来表示其指数。用23位来表示其尾数。因此,单精度的数值表示范围大致为[-2128,2128],远远超出同空间存储的整数 232。但是其存储精度由尾数部分所决定,故而舍绝对精度换来换更大的数值表示空间。
双精度(float64)
熊掌与鱼不可兼得。世界上不存在兼顾性能和空间的方法。首先我搜索了一下看看Go官方有没有解决高精度问题的包。
我们看到官方提供的math包中有大数解决方法,那么它可以解决我们的问题吗?
import (
"fmt"
"math/big"
)
func bigData(){
fa := big.NewFloat(0)
fa.Add(big.NewFloat(0.1), big.NewFloat(0.2))
fc := big.NewFloat(0.3)
if fc.Cmp(fa) == 0{
fmt.Printf("Isprecision:%v\n", true)
}else{
fmt.Printf("Isprecision:%v\n", false)
}
}
执行输出如下:
Isprecision:false
很遗憾,仍然是false。那么这是为什么呢?他做了什么样的操作为什么还是没有成功呢?
经过源码阅读它的数据结构,我们发现它其实也没有实现真正的高精度。其整体思路仍然是浮点数表示的思路。只不过通过使用slice切片来解决超高精度下的尾数存储问题,通过一个32位无符号整数来表示指数。
// A nonzero finite Float represents a multi-precision floating point number
//
// sign × mantissa × 2**exponent
//
// with 0.5 <= mantissa < 1.0, and MinExp <= exponent <= MaxExp.
// A Float may also be zero (+0, -0) or infinite (+Inf, -Inf).
// All Floats are ordered, and the ordering of two Floats x and y
// is defined by x.Cmp(y).
//
// Each Float value also has a precision, rounding mode, and accuracy.
// The precision is the maximum number of mantissa bits available to
// represent the value. The rounding mode specifies how a result should
// be rounded to fit into the mantissa bits, and accuracy describes the
// rounding error with respect to the exact result.
type Float struct {
prec uint32
mode RoundingMode
acc Accuracy
form form
neg bool
mant nat
exp int32
}
其他数据结构的定义和解释。
// RoundingMode determines how a Float value is rounded to the
// desired precision. Rounding may change the Float value; the
// rounding error is described by the Float's Accuracy.
type RoundingMode byte
// Accuracy describes the rounding error produced by the most recent
// operation that generated a Float value, relative to the exact value.
type Accuracy int8
// A form value describes the internal representation.
type form byte
// An unsigned integer x of the form
//
// x = x[n-1]*_B^(n-1) + x[n-2]*_B^(n-2) + ... + x[1]*_B + x[0]
//
// with 0 <= x[i] < _B and 0 <= i < n is stored in a slice of length n,
// with the digits x[i] as the slice elements.
//
// A number is normalized if the slice contains no leading 0 digits.
// During arithmetic operations, denormalized values may occur but are
// always normalized before returning the final result. The normalized
// representation of 0 is the empty or nil slice (length = 0).
//
type nat []Word
世界上没有性能和空间都最棒的排序算法,只有互相妥协的解决方案。浮点数也同是如此。真正的高精度浮点数只有以字符串形式的初始化。因为一旦采取了浮点数初始化,那么它就有可能是不精确的。我们大概定义了定点数来表示浮点数的数据结构。
type RealFloat struct {
neg bool// 正负
mant nat // 小数存储
trunc nat // 整数存储
}
IEEE 754浮点数表示标准 https://www.cnblogs.com/german-iris/p/5759557.html
浮点数运算原理详解 https://blog.csdn.net/big_data1/article/details/82356206