当前位置:首页 > 文章列表 > Golang > Go问答 > Go Decimal 从 float64 构造小数为什么不能恢复原始十进制输入

Go Decimal 从 float64 构造小数为什么不能恢复原始十进制输入

来源:17golang原创 2026-10-29 14:23:50 0浏览 收藏

Go Decimal 从 float64 构造小数,无法保证恢复原始十进制输入,因为原始文本转换成 float64 时,可能已经舍入成另一数值,且文本格式也被丢弃。decimal.NewFromFloat 接收到的只有这个浮点值,没有原始字符串。换一种数值容器,不能反推出已经丢失的信息。

官方地址:https://github.com/shopspring/decimal

这里讨论的是 shopspring/decimal。排查时要把两件事分开:新构造出的 Decimal 是否合理表示当前 float64,以及它是否等于用户最初输入的十进制数。前者成立,不代表后者必然成立。

先定位原始信息在哪一层丢失

一个 float64 只有有限的可表示数值。任意长度的十进制输入却没有这样的数量限制,因此不同输入可能落到同一个 float64 上。到了这一步,后续构造函数没有足够信息判断它原来是哪一个字符串。

另一类丢失与数值精度无关:1、1.0 和 1.00 在业务上可能表达不同录入精度,但转换成浮点数后不能靠数值本身区分。即使数值恰好能精确表示,也不代表原始书写形式被保留下来。

原始十进制信息和浮点表示的结构边界
原始文本与数值表示的静态边界说明图。

不要把简短显示当作恢复原始输入

NewFromFloat 的设计是选择可以转换回同一个 float64 的简短十进制表示。它并不承诺输出浮点数所储存二进制值的完整十进制展开,更不承诺找回之前的原始输入。因此 NewFromFloat(0.1) 得到简短的 0.1 是合理行为,不能据此证明每个 float64 都保存了它的原始十进制来源。

观察到的情况能说明什么不能说明什么
转换结果显示很短该表示可用于当前浮点值的往返转换原始输入未发生舍入
补足固定小数位采用了指定显示格式补出的位就是原始输入
Decimal 与另一 Decimal 相等当前两个十进制值相等两者经历过完全相同的数据来源
原始文本仍然可用可以从文本重新构造十进制数可以忽略文本解析错误和业务范围

用相邻长小数区分两条构造路径

下面用两个靠近 1 的长小数作为说明样本。它们在十进制中不同,但差异小于这一位置附近的 float64 分辨能力,会舍入到同一个浮点值。直接用 NewFromString 则没有经过这个中间入口。

package main

import (
    "fmt"
    "log"
    "strconv"

    "github.com/shopspring/decimal"
)

func main() {
    texts := []string{
        "1.00000000000000001",
        "1.00000000000000002",
    }
    for _, text := range texts {
        // 直接解析十进制文本,避免先经过 float64。
        exact, err := decimal.NewFromString(text)
        if err != nil {
            log.Fatal(err)
        }
        // 这条路径只用于对照已经引入的浮点舍入。
        f, err := strconv.ParseFloat(text, 64)
        if err != nil {
            log.Fatal(err)
        }
        fromFloat := decimal.NewFromFloat(f)
        fmt.Printf("input=%s direct=%s viaFloat=%s same=%t\n",
            text, exact.String(), fromFloat.String(), exact.Equal(fromFloat))
    }

    // 固定两位小数的整数系数,必须连同指数一起约定。
    quantity := decimal.New(7385, -2)
    fmt.Println("quantity:", quantity.String())
}

按照数值表示规则,这两个样本经 float64 路径后都不能保留尾部差异;直接十进制路径仍表达各自的数值。代码供读者自行运行,文中没有提供本次实测输出,也没有把显示文本当作运行证据。选择这种长输入,是为了明确展示“不同输入合并成一个浮点值”,而不是仅展示一个看起来有很多小数位的结果。

示例的两条浮点解析路径都检查了错误。实际入口还要处理 NaN 和无穷值,因为 NewFromFloat 不接受这些特殊值,会触发 panic;不能因为函数没有 error 返回值,就把任意外部浮点输入直接送进去。

把修复放在输入边界

如果业务契约本来就是十进制文本,优先在入口保留字符串,再交给 NewFromString。若数据来自采用固定缩放单位的整数,则把整数系数和指数一同约定,例如示例中的 decimal.New(7385, -2) 表示 73.85。别让接口只给出 7385,却不说明其单位和缩放含义。

十进制字符串和整数系数两种入口
十进制文本与整数系数入口的静态结构说明图。

需要逐字回显或保留录入精度时,可把原始文本作为独立字段保存。Decimal 负责数值运算,原始字段负责来源与格式,这两个职责不应混在一起。即便直接解析字符串,也不要假定默认 String 方法会逐字还原前导零、尾零、正号和科学计数法等所有形式。

如果上游只提供 float64,当前系统只能接受这份已经确定的输入值,或请求上游调整协议。此时使用 Decimal 仍可让后续运算遵守十进制运算方式,但不能把它描述成恢复了上游原始精度。

舍入策略不能充当历史输入的恢复算法

业务规定保留两位小数,是一项新的取舍规则。Round 或指定指数的构造可以表达这种策略,但无法知道已经被舍掉的尾部究竟是多少。先格式化成字符串再调用 NewFromString,也只是从“格式化后的文本”构造,原始信息不会重新出现。

同样,已经在 float64 中完成的加减乘除,其结果不能通过最后包一层 Decimal 撤销。应当从需要十进制语义的那一段运算开始就使用 Decimal,同时独立规定除法精度和最终舍入位置;这里不把后续十进制运算泛称为永不舍入。

  • 回到原始输入,确认是字符串、整数系数还是浮点值。
  • 查看首次进入 float64 的位置,不只查看最后的 Decimal 构造。
  • 区分数值精度和文本格式,分别选择保存方式。
  • 将显示位数、业务舍入和来源恢复视为不同要求。
  • 原文仍存在时重新从原文解析;原文不存在时明确上游输入限制。

NewFromFloat 本身就不能用吗?可以用。它适合把已有浮点值转换到 Decimal;问题在于是否错误地要求它恢复先前未知的输入。

先用 FormatFloat 再解析能恢复吗?不能。格式化函数收到的仍然是同一个 float64,无法知道已经丢失的尾数和文本形式。

增加 Decimal 的显示位数能补回精度吗?不能。显示策略只能呈现当前值,不会增加关于原始输入的信息。

参考:Decimal 构造函数的官方说明与实现、Go 数值类型规范。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go Flock 删除锁文件为什么可能破坏进程间互斥Go Flock 删除锁文件为什么可能破坏进程间互斥
上一篇
Go Flock 删除锁文件为什么可能破坏进程间互斥
Go Flock 同一个 Flock 实例加锁状态为什么不能代表其他进程
下一篇
Go Flock 同一个 Flock 实例加锁状态为什么不能代表其他进程
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    775次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    770次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    856次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    821次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    652次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码