原以为浮点数是计算机编程的基础知识,后来发现个奇怪的现象:很多人都说浮点很坑,千万不要用,至于为什么却说不出个所以然。更有甚者,以专业民科的架势发明出一套处理浮点的办法,应该如何如何,不该如何如何。网上也看到不少人对这个现象很困惑,解释为湿猴理论。
通常你问别人浮点有什么坑,如果别人说浮点数不能用==,十有八九这个人会认为,浮点数可以用><的。不过你继续追问,他可能就会犹豫了。
看代码:
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。并没有统一用四舍五入。
这种问题在实际问题中出现也比较多,把一笔订单拆分成几笔子订单,把一笔成本摊销到一年。在做除法的时候都要根据具体场景来做取舍。其实都不算什么浮点问题。
扩展
- 能否直接用高精度 math/big.Float 来解决浮点数的诡异问题?https://play.golang.org/p/-8C7Gg7uHxd
- wiki https://zh.wikipedia.org/wiki/IEEE_754