登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go Decimal 怎样从字符串构造精确小数

来源:17golang原创

时间:2026-10-29 11:59:18 252浏览 收藏

用 decimal.NewFromString("12.3405") 可以直接从十进制文本构造 shopspring/decimal 的 Decimal,并通过返回的 error 处理非法输入。需要保留原始十进制数值时,应让文本直接进入该函数,避免先经过 float64 再转换。

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

面对 CSV 导入或接口中的小数文本,我会先确认输入来源:既然源头已经提供字符串,就没有必要先换成二进制浮点。这是选型判断,下面用测量值累加说明;不把示例包装成一次真实事故或性能测试。

先按输入来源选择构造方式

输入本来是什么推荐入口取舍
十进制字符串NewFromString直接解析文本,保留数值;调用方处理错误
已有 float64NewFromFloat转换已有浮点值,不能还原之前丢失的原文信息
整数系数和明确单位New 或 NewFromInt整数来源清楚时简洁,但尺度必须约定

需要注意,NewFromFloat(0.1) 不是一定输出一大串尾数。当前文档将它描述为能转换回同一个 float64 的最短十进制表示。真正的问题是:如果上游已经把两个不同原始文本舍入成相同浮点值,下游再用 Decimal 就无法猜回区别。

例如整数文本 9007199254740993 超出了 float64 连续精确表示整数的范围。若业务希望保持该文本的精确数值,就应该直接解析字符串,而不是寄希望于后续转换修复。接口定义见 Decimal 构造方法文档。

用 NewFromString 接住原始文本

下面是一个完整的测量值累加示例。输入长度上限与去空白策略是应用主动选择的规则,库不替应用决定这些限制。项目需要在 go.mod 中声明并固定 github.com/shopspring/decimal 的版本;本文未执行代码,也没有附实际运行输出。

package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/shopspring/decimal"
)

func parseReading(raw string) (decimal.Decimal, error) {
    // 先限制原始输入长度,避免无界文本进入后续解析。
    if len(raw) > 128 {
        return decimal.Zero, fmt.Errorf("测量文本过长")
    }
    // 本例明确接受首尾空白;严格协议可以改为直接拒绝。
    text := strings.TrimSpace(raw)
    if text == "" {
        return decimal.Zero, fmt.Errorf("测量值不能为空")
    }
    value, err := decimal.NewFromString(text)
    if err != nil {
        return decimal.Zero, fmt.Errorf("测量值格式错误: %w", err)
    }
    return value, nil
}

func sumReadings(raws []string) (decimal.Decimal, error) {
    total := decimal.Zero
    for i, raw := range raws {
        value, err := parseReading(raw)
        if err != nil {
            // 附上位置,调用方可定位需要修正的导入记录。
            return decimal.Zero, fmt.Errorf("第%d项: %w", i+1, err)
        }
        // Add 返回新值,必须接住返回结果。
        total = total.Add(value)
    }
    return total, nil
}

func main() {
    total, err := sumReadings([]string{"0.125", "2.375", "-0.5"})
    if err != nil {
        log.Print(err)
        return
    }
    // 显式固定三位用于展示;不把结果再转换成 float64。
    fmt.Println("合计:", total.StringFixed(3))
}

按十进制算术,上述数值合计为 2,固定三位的展示应为 2.000。这是例子的预期语义,不是运行回执。示例允许负值,因为测量修正量可能带符号;业务若只接受非负数,应在解析成功后单独限制。

结构图使用 原始文本、TrimSpace、NewFromString、Decimal、error、输入规则 六个正文节点:输入规则约束原始文本,TrimSpace 对应本例的清理选择,NewFromString 同时有成功值和错误结果两类出口。

十进制文本解析成功值与错误结果的静态结构图
结构图:文本清理、应用规则和解析结果各有职责;无向连线表示关联,不是操作流程或实测证据。

把解析失败和业务约束分别处理

我会把“解析器认识这个数字”和“业务允许这个数字”分成两件事。指数写法、符号、小数位数都可能合法表达一个数,但业务协议可能只允许固定格式。先确定协议,再选择保留或拒绝,不能因为库能解析就默认允许所有形式。

代码返回 decimal.Zero 时同时返回了 error,调用方必须先处理错误。忽略 error 会把不合法文本误当作真实的零。若需要区分空值与数值零,就在输入层额外保留有效性状态,不要仅靠 Decimal 的零值判断。

长度上限只是示例的第一层限制,并不能代替数值范围和指数范围限制。接收不可信输入时,还应按协议限制指数形式、有效数字和运算规模;特别大的指数可能在展开成普通十进制文本时带来额外资源消耗。不要随意给 parser 接受的全部数值都调用超大位数格式化。

解析函数如何分解十进制文本,可参考 项目 decimal.go;外部输入的业务规则应由应用明确维护。

不要让格式化重新丢失精度

构造精确数值后,我更愿意继续在 Decimal 之间计算,边界输出用十进制字符串。若中途又转换为 float64,可能重新引入二进制浮点的表示限制。

图中 Decimal值、Add、累加结果 归入数值计算;StringFixed、展示文本、原始文本 归入文本表达。Add 使用 Decimal 值并产生累加结果,StringFixed 与结果、展示文本相关;原始文本作为单独保留的信息,与数值对象有来源关系。

Decimal数值计算与文本显示职责的静态关系图
结构图:精确数值、固定显示和原始文本是不同层面的数据,不要用展示字符串反推输入格式。

StringFixed(3) 控制固定三位小数的展示,位数不足会补齐,过多会按该方法的舍入规则输出。它不会把原数值永久限制成三位,也不代表原文恰好有三位。若原文是 +001.2300,Decimal 负责数值,原有加号、前导零等排版信息应另存。

解析的精确性也不意味着所有后续运算都无限精确。例如不能整除的除法仍需精度与舍入策略。本文聚焦构造、加法和显示,没有给所有运算承诺统一的精度行为。

哪些场景不必引入 Decimal

如果数据本来就是计数器,只需 int64 就能表达,Decimal 会增加不必要的复杂度。如果是容许近似误差的科学计算或传感器算法,float64 可能更符合库生态和计算需求。若协议固定以最小单位传整数,整数系数加明确尺度也能很好地工作。

我的选择是有条件的:原始输入为十进制文本,而且数值必须按十进制语义保留时,优先直接 NewFromString;已经只有 float64 时,接受它代表的现有信息,必要时回溯上游输入协议,而不是把“转成 Decimal”当成信息恢复工具。

是不是必须先 ParseFloat,再 NewFromFloat?

不需要。输入已经是数字字符串时,这样绕行反而引入额外的表示转换。直接 NewFromString,并检查 error。

字符串里带空格能不能直接解析?

本文明确在应用层调用 TrimSpace。这样是否符合协议由调用方决定,不能把本例的去空白行为误当作库对所有空白形式的保证。

需要保留输入中的尾随零怎么办?

数值尺度、默认字符串展示与原始字符逐字保留是不同要求。需要固定显示位数可选 StringFixed;需要保留用户原文则另外存储原始文本,不要依赖默认 String 格式完成原文存档。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>