Go cmp.Compare 遇到 NaN 时结果怎么理解
直接结论:Go 的 cmp.Compare 为 NaN 规定了一套可用于排序的一致规则:NaN 小于任何非 NaN 浮点数;NaN 与 NaN 比较时返回 0;非 NaN 与 NaN 比较时返回 +1。它还把负零和正零视为相等。
官方文档:https://pkg.go.dev/cmp
因此,若直接把 cmp.Compare 交给升序排序,所有 NaN 会排在普通数值之前。这不是在说 NaN 具有通常数学意义上的大小,而是标准库为了让比较函数能够稳定工作而定义的排序语义。

先记住返回值代表什么
cmp.Compare(x, y) 只返回三种结果:x 小于 y 时为 -1,x 与 y 相等时为 0,x 大于 y 时为 +1。对整数和字符串,这与直觉一致;浮点数出现 NaN 时,则应用标准库的特殊规则。
| 比较表达式 | 返回值 | 含义 |
|---|---|---|
| cmp.Compare(NaN, 3.14) | -1 | NaN 被放在非 NaN 之前 |
| cmp.Compare(3.14, NaN) | +1 | 非 NaN 被放在 NaN 之后 |
| cmp.Compare(NaN, NaN) | 0 | 两个 NaN 在该比较规则下相等 |
| cmp.Compare(-0.0, +0.0) | 0 | 负零与正零被视为相等 |
用最小示例核对三条 NaN 规则
下面把三个关键组合一次打印出来。输出中的 -1、0、+1 是比较结果,不是参与比较的浮点值。
package main
import (
"cmp"
"fmt"
"math"
)
func main() {
nan := math.NaN()
value := 3.14
// 分别核对 NaN 在左侧、右侧和两侧时的返回值
fmt.Println(cmp.Compare(nan, value))
fmt.Println(cmp.Compare(value, nan))
fmt.Println(cmp.Compare(nan, nan))
}
-1
1
0
第一行说明 NaN 在升序中靠前;第二行是相反方向;第三行保证两个 NaN 能进入同一个“相等分组”。这使比较函数可以满足排序算法需要的一致性。
为什么和原生等号、小于号看起来矛盾
原生浮点运算遵循 NaN 的特殊性质:NaN 与任何值做等号比较都不会得到 true,包括它自己;用小于号比较也不会得到普通的小于结论。cmp.Compare 的任务不同,它需要返回一个明确的三态结果,所以专门补上了稳定顺序。
package main
import (
"cmp"
"fmt"
"math"
)
func main() {
nan := math.NaN()
// 原生相等判断与 cmp.Compare 的排序相等不是同一语义
fmt.Println(nan == nan)
fmt.Println(nan
false
false
true

所以,不要把返回 0 解释为“两个 NaN 的二进制内容完全一致”,也不要据此替换业务中的 NaN 检测。要判断一个值是不是 NaN,仍应使用 math.IsNaN。
负零和正零为什么也返回 0
浮点数可以保存符号不同的零。cmp.Compare 明确把 -0.0 与 +0.0 视为相等,因此返回 0。若业务必须保留零的符号,例如某些数值分析或序列化场景,应另外检查符号位,而不能只看比较结果。
package main
import (
"cmp"
"fmt"
"math"
)
func main() {
negativeZero := math.Copysign(0, -1)
positiveZero := 0.0
// Compare 视两种零为相等,Signbit 仍能识别符号
fmt.Println(cmp.Compare(negativeZero, positiveZero))
fmt.Println(math.Signbit(negativeZero), math.Signbit(positiveZero))
}
0
true false
排序时 NaN 默认靠前
把 cmp.Compare 直接传给切片排序,升序结果会先出现 NaN,再出现从小到大的普通数值。这个默认规则适合希望异常值集中在开头、便于优先检查的数据清洗流程。
package main
import (
"cmp"
"fmt"
"math"
"slices"
)
func main() {
values := []float64{8, math.NaN(), -2, 3}
// cmp.Compare 的升序规则会把 NaN 排在普通数值之前
slices.SortFunc(values, cmp.Compare[float64])
fmt.Println(values)
}
[NaN -2 3 8]
业务要求 NaN 靠后时要显式改规则
报表、排行榜或图表经常希望有效数字优先展示,把缺失值放到末尾。这时不要误以为交换升降序就能解决,因为那也会反转普通数字的顺序。更清楚的做法是先处理 NaN,再把普通数交给 cmp.Compare。
package main
import (
"cmp"
"fmt"
"math"
"slices"
)
func compareNaNLast(a, b float64) int {
// 两边都是 NaN 时归为同一组
if math.IsNaN(a) && math.IsNaN(b) {
return 0
}
// 只有左边是 NaN 时,让左边排到后面
if math.IsNaN(a) {
return 1
}
// 只有右边是 NaN 时,让左边普通数排在前面
if math.IsNaN(b) {
return -1
}
return cmp.Compare(a, b)
}
func main() {
values := []float64{8, math.NaN(), -2, 3}
slices.SortFunc(values, compareNaNLast)
fmt.Println(values)
}
[-2 3 8 NaN]
判断清单
- 看到 -1:左值排在右值之前;NaN 对非 NaN 就是这一结果。
- 看到 0:在 cmp.Compare 的排序语义下等价,不代表 NaN 原生等号为 true。
- 看到 +1:左值排在右值之后;非 NaN 对 NaN 就是这一结果。
- 直接升序排序:NaN 靠前,普通数按正常大小排列。
- 业务要 NaN 靠后:先用 math.IsNaN 分组,再比较普通数。
- 业务关心零的符号:另外使用 math.Signbit,不依赖 Compare 区分。
总结:把 cmp.Compare 看成“为排序建立确定关系”的工具,就不会和原生浮点运算混淆。它把 NaN 放在普通数之前、把两个 NaN 视为相等,并把正负零视为相等;若业务排序不同,就在比较函数里显式调整。
相关问题
cmp.Less 对 NaN 的规则一样吗?
方向一致:NaN 小于任何非 NaN,而非 NaN 不小于 NaN。两个 NaN 之间不会报告小于。
可以用 cmp.Compare 判断一个值是不是 NaN 吗?
不建议。判断单个值是否为 NaN 应使用 math.IsNaN,语义更直接。
多个不同 NaN 会在排序中放在一起吗?
会。cmp.Compare 将 NaN 与 NaN 的比较结果定义为 0,因此排序比较器会把它们视为同一比较组。
农业植保无人机作业记录通常要保存哪些信息
- 上一篇
- 农业植保无人机作业记录通常要保存哪些信息
- 下一篇
- 诗歌本应用ID是什么?Google Play开发者信息与同名产品核对方法
-
- Golang · Go问答 | 24分钟前 | Go DEFLATE flate.NewReaderDict 预设字典
- Go flate.NewReaderDict 字典不匹配会发生什么
- 465浏览 收藏
-
- Golang · Go问答 | 1小时前 | go · Go 缓冲区 bytes.Buffer next
- Go bytes.Buffer.Next 参数超过长度会返回什么
- 456浏览 收藏
-
- Golang · Go问答 | 1小时前 | go · bufio · Go bufio.Reader UnreadByte
- Go bufio.Reader.UnreadByte 为什么只能回退一次
- 341浏览 收藏
-
- Golang · Go问答 | 2小时前 | go · 问题排查 · Go bufio.Scanner SplitFunc 空token
- Go bufio.Scanner 连续空 token 为什么会停止
- 296浏览 收藏
-
- Golang · Go问答 | 2小时前 | 标准库 · Go问答 · archive/tar Go tar.Header.Format FormatUnknown tar格式识别
- Go tar.Header.Format 为什么会变成未知格式
- 397浏览 收藏
-
- Golang · Go问答 | 3小时前 | 性能优化 · Go SIMD GOEXPERIMENT archsimd
- Go SIMD 实验 API 启用条件与架构回退策略
- 280浏览 收藏
-
- Golang · Go问答 | 4小时前 |
- Go 泛型方法在接口满足关系中的兼容边界
- 460浏览 收藏
-
- Golang · Go问答 | 5小时前 | 标准库 · JSON · go · 兼容性 · Go encoding/json jsontext encoding/json/v2 JSON兼容
- Go encoding/json/v2 试用时的行为差异梳理
- 433浏览 收藏
-
- Golang · Go问答 | 5小时前 |
- Go 1.27 泛型方法迁移旧接口的兼容清单
- 130浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 323次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 378次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 375次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 338次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 164次使用
-
- go语言标准库fmt包的一键入门
- 2023-01-24 335浏览
-
- 深入解析golang中的标准库flag
- 2023-01-21 372浏览
-
- 一文完全掌握 Go math/rand(源码解析)
- 2022-12-23 370浏览
-
- Go maps.DeleteFunc 怎么安全删掉不合格键:遍历语义、判定副作用与并发边界
- 2026-08-26 141浏览
-
- Go runtime/metrics.Read 如何批量读取运行时指标:样本缓冲、描述符与类型校验
- 2026-08-28 417浏览

