关于Go浮点数精度的骗局

    xiaoxiao2025-07-11  8

    关于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)

    解决 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

    最新回复(0)