关于Go浮点数精度的骗局

关于Go的浮点数骗局

  • 骗局前言
  • 代码验证浮点数精度
  • 浮点数的计算机存储
  • 解决 0.1 + 0.2 != 0.3 的难题
    • Go的"math/big"
      • "math/big"的小秘密
    • 真正的高精度
  • 参考文献

骗局前言

某一天,某人问我,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进行保存。对于整数,我们非常简单地解决了这个问题。只需要在存储字节地最高位腾出一个位来表示该整数的正负即可。

但是在计算机中,浮点数的存储可十分复杂。在计算机发展的过程中,出现过许多不同的浮点数存储办法。其中包括以下三种:

  • 定点数表示方法,即固定整数和浮点数的字节大小。在这种表达方式中,小数点固定的位于实数所有数字中间的某个位置。货币的表达就可以使用这种方式,比如 99.00 或者 00.99 可以用于表达具有四位精度(Precision),小数点后有两位的货币值。由于小数点位置固定,所以可以直接用四位数值来表达相应的数值。SQL 中的 NUMBER 数据类型就是利用定点数来定义的。
  • 有理数表达方式,即用两个整数的比值来表达实数。定点数表达法的缺点在于其形式过于僵硬,固定的小数点位置决定了固定位数的整数部分和小数部分,不利于同时表达特别大的数或者特别小的数。最终,绝大多数现代的计算机系统采纳了所谓的浮点数表达方式。
  • 浮点数表达方式, 即科学计数法来表达实数。其有一个尾数(Mantissa ),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号。比如 123.45 用十进制科学计数法可以表达为 1.2345 × 102 ,其中 1.2345 为尾数,10 为基数,2 为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。提示: 尾数有时也称为有效数字(Significand)。尾数实际上是有效数字的非正式说法。
浮点数表示方法 优点 缺点
定点数表示方法 精度准确,不存在近似 存储空间浪费,不利于表示超高精度小数
有理数表达方式 精度准确,可能存在近似 存储空间浪费,不利于表示超高精度小数
浮点数表达方式 存在近似 节约存储空间,可以同时表示超高精度和超大数值

大部分计算机采用 IEEE 754 浮点数算术标准。在Go 语言中拥有float32和float64两种不同长度的浮点数变量类型。

单精度(float32)
单精度使用8位来表示其指数。用23位来表示其尾数。因此,单精度的数值表示范围大致为[-2128,2128],远远超出同空间存储的整数 232。但是其存储精度由尾数部分所决定,故而舍绝对精度换来换更大的数值表示空间。
在这里插入图片描述
双精度(float64)关于Go浮点数精度的骗局_第1张图片

解决 0.1 + 0.2 != 0.3 的难题

熊掌与鱼不可兼得。世界上不存在兼顾性能和空间的方法。首先我搜索了一下看看Go官方有没有解决高精度问题的包。

Go的"math/big"

我们看到官方提供的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。那么这是为什么呢?他做了什么样的操作为什么还是没有成功呢?

"math/big"的小秘密

经过源码阅读它的数据结构,我们发现它其实也没有实现真正的高精度。其整体思路仍然是浮点数表示的思路。只不过通过使用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

你可能感兴趣的:(Go)