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

Go big.Rat 怎样保存精确小数计算

来源:17golang原创

时间:2026-10-14 19:32:03 105浏览 收藏

Go big.Rat 保存精确小数计算的做法是:从十进制字符串调用 SetString,运算保持在 Rat 中,存档使用 RatString,读回再用 SetString。FloatString(2) 适合显示两位小数,却可能舍入,不能普遍代替精确存档。

输入小数先保留原始字符串

假设一个换算功能收到字符串“10.25”和“0.15”,需要保留乘法结果,而页面只显示两位。这里的技术目标是让保存值保持 1.5375,不提前变成 1.54;示例不代表真实结算规则。

字符串是数值来源的一部分。若先 ParseFloat,再 SetFloat64,Rat 保存的是那个浮点数本身的精确值,而不是自动恢复原始十进制。例如通常的 float64 0.1 不是数学意义上的 1/10,换成有理数不会抹掉前面的表示误差。

官方接口文档:https://pkg.go.dev/math/big#Rat。下面围绕字符串与 Rat 的边界组织实现,不展开数据库、货币单位或结算制度。

把解析失败挡在运算之前

SetString 接受的格式比普通小数输入宽,包含分数和带指数的数字。库能解析不代表业务应该接受。演示中的 parseDecimal 先限制为带可选正负号的普通十进制,最多64字节,不接受空白、分数、指数和千位分隔符;具体项目还应按需求规定数值范围和小数位数。

必须检查返回的 ok。解析失败时返回 nil,接收者的内容不能作为有效值使用。不要用旧对象反复尝试解析后,失败仍继续计算,也不要默默把非法输入替换成零。分数存档与普通小数输入是两种格式,恢复入口不能错误地套用这个仅允许十进制的正则。

字符串输入约束与精确有理数模型的静态关联
图1:原创静态结构说明图。输入预算和十进制格式约束属于输入层;SetString 与 Rat 属于数值层,Rat 的值由分子和分母表达,不保留原始排版。

用 Rat 保存精确值而不是显示位数

Rat 是有理数,不是自动附带“保留两位”的十进制字段。它表达分子与分母的比值:有限小数能精确表达,1/3 这样的循环小数也能精确保存在有理数模型里。它不保留原输入中的尾随零;“1.20”和“1.2”具有相同数值,若需要保存原排版,应另存原始字符串或业务尺度。

math/big 运算写入接收者。使用 new(big.Rat).Mul(amount, factor) 得到独立结果,输入保持不变;若把 amount 自己当接收者,它的值就会被覆盖。精确计算不是无限资源,输入长度、运算规模和显示位数都需要预算。

保存分数字符串并恢复计算

下面完整 main.go 展示输入解析、乘法、保存和恢复;不涉及文件或数据库写入,saved 就是待交给存储层的字符串。

package main

import (
    "fmt"
    "log"
    "math/big"
    "regexp"
)

// 示例仅收普通十进制,拒绝分数、指数、空白和额外字符。
var decimal = regexp.MustCompile(`^[+-]?[0-9]+(\.[0-9]+)?$`)

func parseDecimal(s string) (*big.Rat, error) {
    // 长度64是演示输入预算,不是通用金额业务规则。
    if len(s) == 0 || len(s) > 64 || !decimal.MatchString(s) {
        return nil, fmt.Errorf("小数字符串格式或长度不符合要求")
    }
    r, ok := new(big.Rat).SetString(s)
    if !ok {
        // 失败接收者的内容不可继续使用。
        return nil, fmt.Errorf("小数字符串无法解析")
    }
    return r, nil
}

func main() {
    amount, err := parseDecimal("10.25")
    if err != nil { log.Fatal(err) }
    factor, err := parseDecimal("0.15")
    if err != nil { log.Fatal(err) }

    // 独立接收者保留输入对象;示例只是十进制乘法。
    result := new(big.Rat).Mul(amount, factor)
    saved := result.RatString()
    restored, ok := new(big.Rat).SetString(saved)
    if !ok {
        log.Fatal("保存字符串恢复失败")
    }
    // 字符串显示可舍入,但保存和恢复仍保留精确数值。
    fmt.Println("保存值:", saved)
    fmt.Println("显示值:", result.FloatString(2))
    fmt.Println("恢复相等:", restored.Cmp(result) == 0)

    // 循环小数也能以有理数形式无损保存。
    third := big.NewRat(1, 3)
    fmt.Println("三分之一保存:", third.RatString())
    fmt.Println("三分之一显示:", third.FloatString(2))
}

按数学计算推导,保存值为 123/80,显示值为 1.54,恢复比较为 true;三分之一保存为 1/3,显示为 0.33。这些是示例预期,不是本机运行记录。真正存档时字段类型应能保留整个字符串,不能让接口再把它转成 float64。

RatString 在分母为1时可以只输出整数;String 则总是 a/b 形式。选定一种作为接口约定,保存时注明是有理数字符串,避免调用方把“123/80”当作十进制数解析。读回外部存档也要限制长度并检查 SetString 成功,不能因来源叫“存档”而完全信任。

Rat 精确存档与 FloatString 显示的边界
图2:原创静态依赖说明图。RatString 关联精确存档,SetString 关联恢复对象,Cmp 比较数值;FloatString 属于显示边界,不是无损格式。连线不表示执行时序。

别把显示舍入当作精确结果

用途采用方式保留什么
继续精确计算Rat 运算有理数值
无损存档RatString 后 SetString完整数值,不保留原排版
固定小数显示FloatString(位数)舍入后的显示文本
原输入格式另存输入字符串尾随零等文字信息

FloatString 按小数位数生成文本,末位按最近值舍入,恰好一半时远离零,不是默认银行家舍入。给 1/3 输出两位只能得到近似显示;把“0.33”再解析回 Rat,得到的是 33/100,不再是 1/3。Rat 本身不会因为调用 FloatString 而变成舍入后的值。

因此原场景的取舍很明确:存档用精确字符串,显示独立处理。若业务明确要求某一环节量化到固定尺度,需要另外定义舍入时点和规则,不能把显示函数直接当成所有场景的处理规范。

结束计算时保持对象所有权清楚

Rat 对象不支持结构体浅拷贝。需要独立副本时用 new(big.Rat).Set(original),不要写 copied := *original 再分别修改。多个 goroutine 共享可变 Rat 时仍需自己的同步和所有权规则;精确算术不等于并发修改安全。

这些对象没有文件句柄可关闭。局部计算结束后,不再持有临时对象引用即可让垃圾回收管理内存;不要在 long-lived 容器里无界保存每次计算的临时对象。这里的生命周期管理是输入、结果、存档文本各自负责,不是增加一个清理 API。

能用 FloatString 保存全部精度吗?

对任意 Rat 不能保证。循环小数没有有限位数的无损十进制表示,优先保存分数字符串。

为什么 1.200 的三个小数位不见了?

Rat 保存数值,不保存原字符串尺度。需要原样显示时另存输入文本。

解析失败后可以继续使用接收者吗?

不能依赖其值,必须停止这一条输入的计算并返回错误。除法还应另行检查除数为零,本文没有实现通用除法接口。

字符串转换实现可对照 https://go.dev/src/math/big/ratconv.go。完整方案的关键是让字符串保持十进制来源,让 Rat 保持精确计算,让存档和显示承担不同职责。

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