为何Go的big.Float类型精度反而低于float64?附代码示例
为什么Go语言中使用big.Float反而精度不如float64?
同样的Nilakantha级数计算π的逻辑,用float64得到的结果符合预期,但用更高精度的big.Float却得到了严重偏离的结果,这是为什么?明明代码逻辑看起来完全一致,按道理big.Float应该给出更精确的结果才对。
// 以下代码行为不符合预期,原因是什么? // π = 3 + 4/(2*3*4) - 4/(4*5*6) + 4/(6*7*8) - 4/(8*9*10) + 4/(10*11*12) ... package main import ( "fmt" "math/big" ) func main(){ with_float64_types() with_big_Float_types() } // 使用float64类型的实现 func with_float64_types() { // float64常量: var four64 float64 four64 = 4 var three64 float64 three64 = 3 // float64变量: var digitone64 float64 digitone64 = 2 var digittwo64 float64 digittwo64 = 3 var digitthree64 float64 digitthree64 = 4 var sum64 float64 sum64 = three64 + (four64 / (digitone64*digittwo64*digitthree64)) var nextterm64 float64 // float64计算过程: iters_fl64 := 1 for iters_fl64 < 100000 { // 循环100,000次 iters_fl64++ digitone64 = digitone64 + 2 digittwo64 = digittwo64 + 2 digitthree64 = digitthree64 + 2 nextterm64 = four64/(digitone64*digittwo64*digitthree64) if iters_fl64 % 2 == 0 { // %是取模运算符 sum64 = sum64 - nextterm64 } else { sum64 = sum64 + nextterm64 } } fmt.Printf("\n用float64计算的Nilakantha圆周率:%0.22f \n", sum64) // 输出 3.1415926535897864546598,结果符合预期 } // 使用big.Float类型的实现 func with_big_Float_types() { // big.Float常量: twoBig := big.NewFloat(2) threeBig := big.NewFloat(3) fourBig := big.NewFloat(4) // big.Float变量: digitoneBig := new(big.Float) digitoneBig = twoBig digittwoBig := new(big.Float) digittwoBig = threeBig digitthreeBig := new(big.Float) digitthreeBig = fourBig sumBig := new(big.Float) nexttermBig := new(big.Float) precision := 512 sumBig.SetPrec(uint(precision)) twoBig.SetPrec(uint(precision)) threeBig.SetPrec(uint(precision)) fourBig.SetPrec(uint(precision)) digitoneBig.SetPrec(uint(precision)) digittwoBig.SetPrec(uint(precision)) digitthreeBig.SetPrec(uint(precision)) nexttermBig.SetPrec(uint(precision)) sumBig.Add(threeBig, (new(big.Float).Quo(fourBig, new(big.Float).Mul(digitoneBig, new(big.Float).Mul(digittwoBig, digitthreeBig))))) // big.Float计算过程: iterBig := 1 for iterBig < 100000 { // 循环100,000次 iterBig++ digitoneBig.Add(digitoneBig, twoBig) digittwoBig.Add(digittwoBig, twoBig) digitthreeBig.Add(digitthreeBig, twoBig) nexttermBig.Quo(fourBig, new(big.Float).Mul(digitoneBig, new(big.Float).Mul(digittwoBig, digitthreeBig))) if iterBig % 2 == 0 { // %是取模运算符 sumBig.Sub(sumBig, nexttermBig) } else { sumBig.Add(sumBig, nexttermBig) } } fmt.Printf("\n用big.Float计算的Nilakantha圆周率:%0.22f \n", sumBig) // 输出 3.1376671059697782695252,结果严重偏离预期,但为什么?逻辑不是完全一致吗? }
问题根源
问题出在big.Float的变量赋值方式上:Go语言中big.Float是引用类型,直接用=赋值只是复制指针,让新变量指向同一个底层对象。
看这段错误的初始化代码:
digitoneBig := new(big.Float) digitoneBig = twoBig
这里digitoneBig并没有创建独立的big.Float实例,而是直接指向了twoBig的内存地址。后续循环中执行digitoneBig.Add(digitoneBig, twoBig)时,实际上是修改了twoBig本身的值!
第一次循环:digitoneBig(也就是twoBig)从2变成2+2=4;第二次循环时,twoBig已经是4了,所以digitoneBig.Add会变成4+4=8,以此类推——每次加的不再是固定的2,而是越来越大的数,导致整个Nilakantha级数的项完全错误,结果自然严重偏离π的真实值。
而float64是值类型,赋值时会拷贝值,所以不会出现这种引用共享的问题。
修正方案
要正确初始化big.Float变量,需要使用Set方法复制值,确保每个变量拥有独立的实例:
// 修正后的big.Float变量初始化部分 digitoneBig := new(big.Float).Set(twoBig) digittwoBig := new(big.Float).Set(threeBig) digitthreeBig := new(big.Float).Set(fourBig)
这样后续的Add操作只会修改各自的变量,不会影响到twoBig等常量,循环中每次加的都是固定的2,就能得到正确的高精度结果。
内容的提问来源于stack exchange,提问作者user2421775
相关产品推荐
相关产品推荐

