当前位置:首页 > 文章列表 > Golang > Go问答 > Go cmp.Compare 遇到 NaN 时结果怎么理解

Go cmp.Compare 遇到 NaN 时结果怎么理解

来源:17golang原创 2026-10-04 04:34:11 0浏览 收藏

直接结论:Go 的 cmp.Compare 为 NaN 规定了一套可用于排序的一致规则:NaN 小于任何非 NaN 浮点数;NaN 与 NaN 比较时返回 0;非 NaN 与 NaN 比较时返回 +1。它还把负零和正零视为相等。

官方文档:https://pkg.go.dev/cmp

因此,若直接把 cmp.Compare 交给升序排序,所有 NaN 会排在普通数值之前。这不是在说 NaN 具有通常数学意义上的大小,而是标准库为了让比较函数能够稳定工作而定义的排序语义。

cmp.Compare 对 NaN 与普通数返回负一零正一的静态排序结构图
图1:静态说明图,cmp.Compare 把 NaN 放在所有非 NaN 数之前,并把两个 NaN 视为相等。

先记住返回值代表什么

cmp.Compare(x, y) 只返回三种结果:x 小于 y 时为 -1,x 与 y 相等时为 0,x 大于 y 时为 +1。对整数和字符串,这与直觉一致;浮点数出现 NaN 时,则应用标准库的特殊规则。

比较表达式返回值含义
cmp.Compare(NaN, 3.14)-1NaN 被放在非 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
原生浮点运算与 cmp.Compare 处理 NaN 和正负零差异的静态关系图
图2:静态关系图,原生运算保留 IEEE 浮点语义,cmp.Compare 则提供排序需要的一致顺序。

所以,不要把返回 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,因此排序比较器会把它们视为同一比较组。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
农业植保无人机作业记录通常要保存哪些信息农业植保无人机作业记录通常要保存哪些信息
上一篇
农业植保无人机作业记录通常要保存哪些信息
诗歌本应用ID是什么?Google Play开发者信息与同名产品核对方法
下一篇
诗歌本应用ID是什么?Google Play开发者信息与同名产品核对方法
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    323次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    378次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    375次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    338次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    164次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码