当我们在说浮点数精度不准的时候,到底在说什么


原以为浮点数是计算机编程的基础知识,后来发现个奇怪的现象:很多人都说浮点很坑,千万不要用,至于为什么却说不出个所以然。更有甚者,以专业民科的架势发明出一套处理浮点的办法,应该如何如何,不该如何如何。网上也看到不少人对这个现象很困惑,解释为湿猴理论。

通常你问别人浮点有什么坑,如果别人说浮点数不能用==,十有八九这个人会认为,浮点数可以用><的。不过你继续追问,他可能就会犹豫了。

看代码:

a := 0.1
b := 0.2
c := 0.3

fmt.Printf("%10s => %v\n", "a+b", a+b)
fmt.Printf("%10s => %v\n", "a+b == c", a+b == c)
fmt.Printf("%10s => %v\n", "a+b < c", a+b < c)
fmt.Printf("%10s => %v\n", "a+b > c", a+b > c)

https://play.golang.org/p/QdoleMRyJr3

why?

  • 按照浮点标准,0.1 转化成二进制是个无限小数

  • 浮点数有效长度有限,必有取舍

总结,二进制无法准确表达0.1,多多少少会有点失真。遂不能直接比较大小。

更详细的论述:https://www.zhihu.com/question/28551135

浮点数如何比较?

通常做法,如果两个数的差值在可接受范围内,就认为是相等的。其实很多非精确的比较都是类似方法,比如

1、两个人年龄、五官等等主要特征都很像,你就敢猜这俩人是双胞胎了。 2、你的服务10:03开始大量报错,隔壁服务10:02上了个线,你猜大概就是他的锅。

var floatPrecision = 1e-6

func floatEqual(a float64, b float64) bool {
	return math.Abs(a-b) < floatPrecision
}

func floatLess(a float64, b float64) bool {
	return b-a > floatPrecision
}

func floatGreater(a float64, b float64) bool {
	return a-b > floatPrecision
}

func main(){
	fmt.Println(floatEqual(a+b, c))	// true
}

浮点数怎么转换

跟整型转换比较简单

// 去尾
func floatFloor(a float64) int {
	return int(a)
}

// 四舍五入
func floatToInt(a float64) int {
	return int(math.Round(a))
	//return int(a + 0.5)
}

跟浮点转换就有意思了

// 打印浮点数
func testFloatToString(){	
  f := 16.99
	fmt.Println(f * 10)
	fmt.Println(f * 100)
	fmt.Println(f * 1000)
	fmt.Println(f * 10000)
	fmt.Printf("%.2f\n", f*10000)
	fmt.Printf("%.0f\n", f*10000)
}

169.89999999999998
1698.9999999999998
16990
169899.99999999997
169900.00
169900

strconv

浮点数要不要用字符串传输?

有人问,如果别人想传1,结果传了个0.99999,不就失真了吗?

基本上这样问的都是因为用的时候直接用了比较操作符。

原来的 double 有 15 位有效数字,一般转成 string 后只刻意保留了几位,如果是直接截取,其实是主动丢弃了准确度。

举例:本来要传 1,截取之后传了 0.999 ,别人并不不知道这个 0.999 是准确值还是你截出来的。

个人推荐,传输的时候保留原数据,只有在渲染的时候再做格式化处理。

问题核心:怎么存不重要,怎么用才是关键。想着存的时候干净一点,用的时候就随意了。

财务场景下的特殊处理

仔细想了想,好像财务上也没什么特别要处理的,怀疑自己知识面不够,就去网上翻了翻,虽然没看到什么新问题,却又不少其他的收获。比如

系统性误差有系统性解决办法

我对这句话的理解,不要把计算机看得太重了,计算机只是解决领域问题的一个工具,准确的说只是特定时期的计算工具,专业的领域在手工计算的时代就有专业的算法了。

这里只提一个最开始想到的跟普通场景的区别:不能简单的四舍五入

先抛开小数不谈,现实生活中,3个人平分10块钱,理论上一人3.33,实际上大家会按照3-3-4来分就够了。放到微信群收款里,也是3.33*2+3.34。并没有统一用四舍五入。

这种问题在实际问题中出现也比较多,把一笔订单拆分成几笔子订单,把一笔成本摊销到一年。在做除法的时候都要根据具体场景来做取舍。其实都不算什么浮点问题。

扩展

Avatar
huiren
Code Artisan

问渠那得清如许,为有源头活水来

下一页
上一页
comments powered by Disqus