Golang浮点数精度问题:0.4 + 0.2 为何等于0.6?
Golang浮点数精度问题:0.4 + 0.2 为何等于0.6? 初识Go语言——它如何处理浮点数精度:0.4 + 0.2 = 0.6
我刚开始接触聊天,并了解到Go是编译型语言、没有类、有goto语句(存在)这些事实。
我仍在学习一些老派的BASIC和QBASIC(作为编程的优质基础书籍),以及Python,并比较它们与Go的差异。
1990年的QBASIC默认处理单精度浮点数,除非使用DIM AS DOUBLE:
print 0.2 + 0.4 给出了正确的结果0.6
Python print(0.2 + 0 .4) 给出的浮点数末尾有一个1,而不是0.6,并且 0.2 + 0.4 == 0.6 的结果是False。
我惊喜地发现,Go的基础库处理得很好,给出了0.6。
但它仍然是float64。
能否请您告诉我,这个库是如何不触发浮点误差的?是否内置了逻辑,在数字没有太多小数位时不显示小数位? 我很惊讶许多其他语言都失败了,而这在30多年前是可管理的。我喜欢你可以提前为变量指定精度,这样小数就不需要用高精度来运行。
package main
import "fmt"
func main() {
fmt.Println(0.2 + 0.4)
fmt.Printf("%T\n", 0.2 + 0.4)
}
在其他方面, 我正在比较输入处理:
BASIC默认将变量视为数值型, Python是字符串型, Go的scanln()是字符串型
除法 /
BASIC中 5/2 会得到2.5,因为 \ 是用于整数除法——最优雅的解决方案
Python 2中,5/2 得到2,在Go中也是2,作为整数除法 Python 3中,5/2 得到正确的2.5,因为整数除法现在是 //
我不喜欢Go用 / 作为整数除法,因为这很少使用,应该用 \ 表示整数除法,用 / 表示浮点数除法。但如果变量在前面被声明为浮点型,那就没问题,这才是关键。
能否请您告诉我一些与Python相比其他明显的区别。 我肯定会买书的,《In Easy Steps》系列关于Python和VB的书很不错。
我认为Go会被采用并崛起,因为它的基础库是由专业人士完成的,而不是像其他地方那样由业余爱好者完成。它能够编译,并且fmt有很多函数,这很好。在Python中,你必须导入一切,甚至处理时间或数学运算也要导入。
谢谢
更多关于Golang浮点数精度问题:0.4 + 0.2 为何等于0.6?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
func main() {
fmt.Println("hello world")
}
更多关于Golang浮点数精度问题:0.4 + 0.2 为何等于0.6?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我只是将 c 和 d 改为 a 和 b,得到了相同的结果。假设 c, d := 0.1, 0.2…
pekim:
fmt.Println(0.2 + 0.4)
总结一下 fmt.Println(0.2 + 0.4),我现在明白了,这被当作值而不是浮点数字面常量来处理(可以搜索了解一下)。
你的问题就像在问为什么人们还在使用浮点数。这没有意义,因为你需要根据自身需求调整所使用的数据结构。我不在乎你说COBOL语言有多优雅,但在实践中,大多数人不用COBOL也能很好地开发。这不是你推荐使用所谓的COBOL而不是浮点数的理由。
我不在乎你在Python里做什么,如果你懂数据结构,你就应该知道该用什么数据结构来处理你的业务。我可以预料到,你所说的定点运算在效率和直观性上都不如整数表示。
事已至此,我不会再多说什么,因为无论我说什么,都无法改变你的看法。
我正在探索各种程序具备的功能以及它们之间的差异。当所有程序都使用浮点数时,我尤其清楚自己最终需要什么。我只是指出了一些已知的事实,并正在检查模块是什么以及它们是如何被使用的。
我将使用 Go 语言开箱即用的功能,并且对此感到满意,因为其他人也处于相同的情况。
我正在学习,还没有安装Go来编译以查看其差异。 似乎只有当变量被设置为常量或进行加法运算时,结果才是可预测的。
n := 0.1 + 0.2
c, d := 0.1, 0.2
同样会得到不正确的0.30000…4,这就是问题所在。
如果像那样使用 := 赋值,由于默认使用 float64,将会产生浮点数误差。
如果编译器看到小数位数较少的数字,是否可以在编译时将其视为 float32 来处理,因为从数学上讲,它一开始就无法提供那么长的小数位数。
通过使用 float32 来修复这个问题。
package main
import "fmt"
func main() {
a := 0.2
b := 0.4
fmt.Println(a + b) // 0.6000000000000001
}
感谢大家的回复——随着我学习更多语言,我猜许多事情上的差异开始变得明显,包括提到的在GO中 / 是整数除法,而在BASIC中 / 是浮点除法,Python 3也是如此(Python 2的 / 是整数除法)。
我认为坚持某种单一标准会对行业有所帮助。 GO对我来说仍然很有前景,因为它是编译型语言,虽然复杂,但不像其他语言那样——我需要熟悉那些我不了解的新特性,也许对我来说这些新事物很好,我没有任何可以比较的对象。
上面的链接解释了Go如何处理字面量与变量的不同。下面的链接解释了为什么将变量中存储的值相加时得不到0.3。实际上,这正是那里提到的第一件事。也许你只是个捣乱的机器人。
浮点数指南 - 每个程序员都应该知道的…
旨在为新手程序员关于浮点数不能正确“相加”的常见重复问题提供简短而简单的答案,并提供关于IEEE 754浮点数如何工作、何时以及如何使用它们的更深入信息…
至少对我来说,这不是个问题。如果你想要标准,你应该去提提案。但对于一个简单的设计,这对我来说只是感觉繁琐。 如果你想要完全的精确性,就不要使用浮点数。这是基本的开发常识。如果你担心 int64 无法满足你的数值范围,你可以使用 big.Int。
// 代码示例:使用 big.Int 处理大整数
package main
import (
"fmt"
"math/big"
)
func main() {
// 创建一个新的 big.Int
bigInt := new(big.Int)
// 设置一个非常大的值
bigInt.SetString("123456789012345678901234567890", 10)
fmt.Println("大整数值:", bigInt)
}
您是否查看过:big package - math/big - Go Packages?
math/big 包包含在标准库中,无需第三方依赖,也无需额外导入。它为您提供了整数、有理数或浮点运算的任意精度。这种明确的选择将使您仔细考虑您的使用场景和最佳表示形式。
例如:如果您正在计算无理数,并且想要表示所有数字,那么您可能永远无法“完成”计算(因为像 sqrt(2) 这样的无理数有无限多位数字)。
因此,根据您的使用场景,正确的实现可能是 big.Float、big.Rat 或 big.Int,或者只是普通的旧 Int 或 Float32/64(例如,当您的值被序列化到期望 float32 的其他服务时)。
Go 的 float32 和 float64 类型遵循 IEEE 754 标准,这与许多其他语言相同。这意味着它将受到相同的限制。
package main
import "fmt"
func main() {
fmt.Printf("%T %T\n", 0.2, 0.2+0.4) // float64 float64
fmt.Println(0.2 + 0.4) // 0.6
fmt.Println(float32(0.2) + float32(0.4)) // 0.6
fmt.Println(float64(0.2) + float64(0.4)) // 0.6000000000000001
}
关于 fmt.Println(0.2 + 0.4),该表达式仅包含字面常量,因此将在编译时而非运行时求值。
pekim:
表达式仅包含字面常量
我阅读了一些文章和博客,但情况并未改变:
- 浮点数不适用于任何十进制分析,即使是 float32 也会导致错误。
- COBOL 语言拥有基于 10 进制的定点运算,这很有效且精度良好。
- 金融领域大多使用整数。
- Python 自带的 decimal 模块可以正常工作,前提是使用字符串形式如 a = Decimal(‘0.1’),并且语法简单。有些书教的是 a = Decimal(0.1),但这会导致问题,因为该库允许不写成字符串的形式。
- Go 没有自带的 decimal 模块,虽然提出了很多提案,也有第三方库,但其中一些并不理想。
这并没有帮助,因为缺乏单一标准,存在多种选择和困惑。如果有一个标准,将有助于将运行在 COBOL 上的程序迁移到 Go。
感谢您的解释。我喜欢QBASIC的一点是,它仍然可以在现代Windows的DosBox上运行,虽然我还有PC286(12.5 MHz)和PC386(40 MHz),但它默认使用单精度浮点数。
对于大多数简单的算术运算来说,使用float64是计算资源的浪费,并且在Python IDLE shell中,即使只是输入0.2 + 0.4而不使用print,也会导致错误。
我很惊讶默认是float64。如果我在我的PC386上声明DIM n AS DOUBLE,仍然会出现浮点错误,但使用默认的单精度则不会。
我非常喜欢GO提供的所有精度选择,不同的浮点数或有符号和无符号整数——工具箱很大,人们可以根据需要使用。
我理解这门语言必须符合IEEE 754标准(我了解这一点),但这就像用5公斤的大锤去钉一个双精度的钉子(现在对它的称呼可能与过去略有不同)。 这需要用户去学习,并希望书籍能很好地解释应该使用什么来使代码运行得更快。
我观看了14年前最初的发布视频。
当时的重点是简化,时代在进步。如果缺乏泛型,就会出现多个库做同样事情的反效果。包括Python在内的所有十进制实现对我来说都不够好,因为它们很快就会因为视觉噪音而变得混乱。虽然有一些提案,但最终都会像Python那样,谁愿意输入Decimal("0.2")呢?它本应就是0.2。
如果实现一个核心功能,将其视为具有足够精度的定点运算(最高9位小数),以满足加密需求,并且不增加额外语法。人们仍然可以使用float32或float64作为变量进行他们的特殊运算。
当然,Go最初只是为谷歌开发的。
许多泛型功能缺失:比如简单的Tkinter等效GUI,1990年QBASIC就有的INKEY$(用于读取输入按键)、时间、幂运算、随机数生成都是开箱即用的——单一标准。
Python中也存在多个时间函数。我仍然使用QBASIC进行原型设计,VGA标准将每种颜色表示为整数,可以在屏幕上绘图,必要时使用按键,并且具有足够的精度,还能立即生成.exe文件。QBASIC编译时提供两种数学选项。
开箱即用的十进制,带有9位小数空间,对于通用用途来说已经足够了。
不应将浮点数用于任何十进制计算,它们和带符号或无符号整数或常量一样,适用于其他用途。我想说的是,工具箱里应该备有各种工具以应对不同的工作,包括基于十进制的定点运算。 我并非全盘否定浮点数,我自己也会使用它们。
包括加密货币在内的金融领域主要使用整数进行数学运算,如果有一个开箱即用的简单库,事情会变得更容易。COBOL 是我见过的唯一提供开箱即用解决方案的语言。
我将在 PC 386 (40 MHz) 上使用 MS-DOS Cobol 5.0 来进一步理解基于整数、遵循传统标准的定点运算。 COBOL、Fortran、汇编语言和 BASIC 终究是编程的基础。
GO 语言在整数和浮点数方面仍有很好的默认选项,他们一定有理由不添加任何基于整数的简单定点运算模块。
除非使用库,否则 Python 不允许你直接使用 float32。
以下是 COBOL 代码示例:
IDENTIFICATION DIVISION.
PROGRAM-ID. AddNumbers.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NUM1 PIC 9V9 VALUE 0.1.
01 NUM2 PIC 9V9 VALUE 0.2.
01 RESULT PIC 9V9.
PROCEDURE DIVISION.
BEGIN.
COMPUTE RESULT = NUM1 + NUM2
DISPLAY 'The sum of 0.1 and 0.2 is ' RESULT
STOP RUN.
感谢——它确实指向了那个常量,但解释得不够清楚。
将变量声明为常量,并且两者都默认为 float64 或 float32 是可行的。正如你提到的,n := 0.1 + 0.2 也会被视为常量,但如果每个变量是单独声明的,则不会。
对于简单的算术运算,我不会使用任何复杂的数学方法,这对我来说不合逻辑。 定点数(8位小数精度)会是最好的选择,但使用常量技巧对于简单数学运算有效,不过这样用起来比较困难。
package main
import "fmt"
func main() {
const a, b = 0.1, 0.2
fmt.Println("sum a(0.1) + b(0.2) = ", a+b) // 正确 0.3
fmt.Printf(" a is %T\n b is %T\n", a, b)
n := 0.1 + 0.2
fmt.Println("sum n(0.1 + 0.2) = ", n) // 正确 0.3
fmt.Printf(" n is %T\n", n)
c := 0.1
d := 0.2
fmt.Println("sum c(0.1)+ d(0.2) = ", c+d) // !!! 错误 0.30000000000000004
fmt.Printf(" c is %T\n d is %T\n", c, d)
另外,对于循环,如果声明为常量,它不会溢出。
我熟悉那篇文章,也理解浮点数精度,因此提出了标题中的问题——它如何处理浮点数精度 0.4 + 0.2 = 0.6,并且已经收到了关于这个问题的解答,谢谢。
我不熟悉常量的概念,因为 BASIC 或 Python 都没有这个概念,而且后者对于 0.1 + 0.2 或 0.2 + 0.4 给出了与 Go 不同的结果,这就是我最初提问的原因。 Excel 通过四舍五入来处理,但它仍然是浮点数,这只是在掩盖问题。
之前那篇文章中有一段非常好的总结性文字:
“x := 0.1 + 0.2 精确等于 0.3,因为 0.1 和 0.2 是数值常量”
这段话可以连同我上面的一些例子,添加到那个教程章节中,并作为一个警告,说明 float32 或 float64 在二进制(基数为2)下的行为。 https://go.dev/tour/basics/15
有些事情可能看起来显而易见,但其实并非如此,甚至书籍中也会出现错误。 我通过“easy steps”系列的绿色书籍学习 Python 和 Go。
第 89 页的 Python decimal 模块没有说明需要将数字作为字符串放入 Decimal(' ') 中,我已经通知了出版商,同时也建议在 Go 的教程中扩展关于浮点数的内容。而旧的 BASIC 书籍和手册则包含了这些内容(去年我阅读了大约 5000 页的旧手册和约 1000 页的旧书,包括 1964 年的原始版本,并在 1986-93 年的旧硬件上运行代码)。
Decimal(0.1) + Decimal(0.2) 会出错,除非写成字符串形式 Decimal('0.1') + Decimal('0.2'),但它允许一个参数是字符串而另一个不是,很容易犯这样的错误。
Go 拥有稳定的语法,因此是一门非常优秀的语言,我欣赏它的简洁性。 Cobol 将十进制数作为整数来处理是最好的解决方案——这是一种优雅的解决方案,比许多模块那样转换为字符串要安全得多。
使用 Go 我也回到了起点——因此观看了提到的发布视频。有趣的是,为什么他们最初没有采用简单的 Cobol 解决方案来处理十进制数,以避免所有第三方十进制模块和额外的语法。
Go语言中0.4 + 0.2显示为0.6是因为fmt.Println默认的格式化输出会进行舍入处理,而不是因为浮点数精度问题被消除。实际上,在二进制浮点数表示中,0.4和0.2都无法精确表示,它们的和也存在微小误差。
package main
import (
"fmt"
"math"
)
func main() {
// 1. 默认输出显示舍入后的结果
fmt.Println("fmt.Println(0.4 + 0.2):", 0.4 + 0.2) // 显示 0.6
// 2. 高精度显示实际值
fmt.Printf("高精度显示: %.20f\n", 0.4 + 0.2) // 0.60000000000000008882
// 3. 实际比较浮点数
fmt.Println("0.4 + 0.2 == 0.6:", 0.4 + 0.2 == 0.6) // true
// 4. 但实际存在微小误差
sum := 0.4 + 0.2
fmt.Printf("与0.6的差值: %g\n", sum - 0.6) // 8.881784197001252e-17
// 5. 正确的浮点数比较方法
fmt.Println("使用误差容限比较:", math.Abs(sum - 0.6) < 1e-10) // true
}
Go与Python在浮点数处理上的主要区别:
// Go的整数和浮点数除法
package main
import "fmt"
func main() {
// 整数除法
var a int = 5
var b int = 2
fmt.Println("5/2 (int):", a/b) // 2
// 浮点数除法
var c float64 = 5
var d float64 = 2
fmt.Println("5.0/2.0:", c/d) // 2.5
// 类型转换
fmt.Println("float64(5)/2:", float64(5)/2) // 2.5
// 与Python的对比示例
// Python: 5/2 = 2.5 (Python 3)
// Go需要显式类型处理
}
Go语言的其他特性差异:
// 1. 强类型系统
package main
import "fmt"
func main() {
// Go需要显式类型声明
var x int = 10
var y float64 = 3.14
// 错误: invalid operation
// result := x + y // 编译错误
// 正确: 需要类型转换
result := float64(x) + y
fmt.Println(result)
}
// 2. 错误处理 vs Python异常
package main
import (
"fmt"
"strconv"
)
func main() {
// Go的错误处理
s := "123"
n, err := strconv.Atoi(s)
if err != nil {
fmt.Println("转换错误:", err)
} else {
fmt.Println("转换结果:", n)
}
// 对比Python的try-except
// Python:
// try:
// n = int(s)
// except ValueError as e:
// print(f"转换错误: {e}")
}
// 3. 并发处理
package main
import (
"fmt"
"time"
)
func main() {
// Goroutine vs Python threading
go func() {
fmt.Println("Goroutine执行")
}()
time.Sleep(time.Millisecond * 100)
// Channel通信
ch := make(chan string)
go func() {
ch <- "通过Channel传递数据"
}()
msg := <-ch
fmt.Println(msg)
}
Go语言的标准库设计:
// 内置丰富功能,无需额外导入基础功能
package main
import (
"fmt"
"time"
"math/rand"
)
func main() {
// 时间处理
now := time.Now()
fmt.Println("当前时间:", now.Format("2006-01-02 15:04:05"))
// 随机数
rand.Seed(time.Now().UnixNano())
fmt.Println("随机数:", rand.Intn(100))
// 数学运算
fmt.Println("π:", math.Pi)
fmt.Println("平方根:", math.Sqrt(16))
}
Go语言的编译时检查:
package main
import "fmt"
func main() {
// 未使用的变量会导致编译错误
// var unused int = 10 // 取消注释会编译失败
// 严格的类型检查
var count int = 10
// count = "string" // 编译错误
fmt.Println("编译通过")
}
Go语言中浮点数精度问题的实际处理建议:
package main
import (
"fmt"
"math"
)
func main() {
// 处理货币等需要精确计算的场景
// 方法1: 使用整数表示分
var amountCents int64 = 12345 // 表示123.45元
fmt.Printf("金额: %.2f\n", float64(amountCents)/100)
// 方法2: 使用decimal库(第三方)
// import "github.com/shopspring/decimal"
// 方法3: 设置误差容限比较
a := 0.1
b := 0.2
c := 0.3
const epsilon = 1e-10
fmt.Println("近似相等:", math.Abs((a+b)-c) < epsilon)
// 方法4: 格式化输出控制
value := 0.4 + 0.2
fmt.Printf("默认: %v\n", value)
fmt.Printf("6位小数: %.6f\n", value)
fmt.Printf("15位小数: %.15f\n", value)
}


