讲师中心 微信公众号
AI工具推荐 视频效率加速

Go语言中mgo与big.Rat高精度有理数的持久化存储实践

夜静大大_7178

夜静大大_7178

发布时间:2025-11-18 15:20:02

|

316人浏览过

|

来源于php中文网

原创

Go语言中mgo与big.Rat高精度有理数的持久化存储实践

本教程详细阐述了如何在go语言应用中,利用`mgo`驱动将`math/big.rat`类型的高精度有理数存储到mongodb。针对`big.rat`无法直接持久化的挑战,文章提出了一种解决方案:通过自定义结构体分别存储其分子和分母(使用`int64`类型),实现数据的精确保存与恢复,确保金融或科学计算场景下的数据完整性。

在Go语言的开发中,尤其是在涉及金融计算、科学数据处理等对精度要求极高的场景下,标准浮点数(float32, float64)可能因其固有的精度限制而无法满足需求。此时,math/big包中的big.Rat类型提供了一种理想的解决方案,它以有理数(分数)的形式表示数字,从而避免了浮点运算带来的误差。然而,当我们需要将这些高精度数据持久化到MongoDB等数据库时,mgo驱动(或任何BSON序列化器)并不直接支持big.Rat类型。本文将详细介绍如何优雅地解决这一问题。

理解 math/big.Rat 的内部结构

big.Rat 类型在内部由两个 *big.Int 值组成,分别代表有理数的分子(Numerator)和分母(Denominator)。这两个值可以通过 (*big.Rat).Num() 和 (*big.Rat).Denom() 方法获取。例如,big.NewRat(5, 10) 会创建一个表示 0.5 的有理数。

由于 big.Rat 是一个复杂的结构体,且其内部字段未导出(unexported),mgo 无法直接将其序列化为BSON格式并存储到MongoDB。同样,其内部的 *big.Int 类型也无法直接映射到BSON的简单数值类型。

解决方案:自定义结构体存储分子和分母

为了实现 big.Rat 的持久化,我们可以采用一种间接但有效的方法:创建一个自定义的Go结构体,用于存储 big.Rat 的分子和分母。在大多数实际应用中,尤其是在处理货币或常见分数时,int64 类型通常足以表示这些分子和分母。

立即学习“go语言免费学习笔记(深入)”;

以下是实现这一策略的步骤和示例代码:

Remix V2 Meta Sessions Review
Remix V2 Meta Sessions Review

审查 Remix v2 代码中的 v1 形态 meta 导出(v2 中为破坏性变更),cookie 安全漏洞(httpOnly、secure、密钥轮换),认证门位于错误层级,以及其他

下载

1. 定义用于MongoDB存储的结构体

首先,我们需要定义一个Go结构体,它包含两个 int64 类型的字段来分别存储分子和分母。为了与BSON字段名对应,我们通常会添加 bson 标签。

package main

import (
    "fmt"
    "log"
    "math/big"
    "time"

    "gopkg.in/mgo.v2" // 使用 gopkg.in/mgo.v2 以兼容现代Go模块
    "gopkg.in/mgo.v2/bson"
)

// RationalValue 是用于在MongoDB中存储 big.Rat 的自定义结构体
type RationalValue struct {
    Numerator   int64 `bson:"numerator"`
    Denominator int64 `bson:"denominator"`
}

// DocumentWithBudget 是一个包含 RationalValue 字段的示例文档结构
type DocumentWithBudget struct {
    ID     bson.ObjectId `bson:"_id,omitempty"`
    Name   string        `bson:"name"`
    Budget RationalValue `bson:"budget"`
    UpdateAt time.Time   `bson:"update_at"`
}

var mgoSession *mgo.Session

// initMongoDB 初始化MongoDB连接
func initMongoDB() {
    var err error
    // 确保MongoDB服务在 localhost:27017 运行
    mgoSession, err = mgo.Dial("mongodb://localhost:27017")
    if err != nil {
        log.Fatalf("Failed to connect to MongoDB: %v", err)
    }
    // 设置模式为 Monotonic,确保读取一致性
    mgoSession.SetMode(mgo.Monotonic, true)
    log.Println("Connected to MongoDB successfully.")
}

// closeMongoDB 关闭MongoDB连接
func closeMongoDB() {
    if mgoSession != nil {
        mgoSession.Close()
        log.Println("MongoDB session closed.")
    }
}

2. 实现 big.Rat 与 RationalValue 之间的转换

为了方便数据的存取,我们还需要编写辅助函数,用于在 big.Rat 和 RationalValue 之间进行转换。

// NewRationalValueFromRat 将 *big.Rat 转换为 RationalValue
func NewRationalValueFromRat(r *big.Rat) RationalValue {
    // 注意:这里假设 big.Int 的值不会超出 int64 的范围。
    // 在生产环境中,如果 big.Int 可能非常大,需要进行溢出检查,
    // 或考虑将 big.Int 转换为字符串存储。
    return RationalValue{
        Numerator:   r.Num().Int64(),
        Denominator: r.Denom().Int64(),
    }
}

// ToRat 将 RationalValue 转换回 *big.Rat
func (rv RationalValue) ToRat() *big.Rat {
    return big.NewRat(rv.Numerator, rv.Denominator)
}

3. 将数据存入MongoDB

现在,我们可以使用这些转换函数将 big.Rat 值存储到MongoDB。

func main() {
    initMongoDB()
    defer closeMongoDB()

    // 获取一个会话副本,用于当前操作
    session := mgoSession.Copy()
    defer session.Close()

    // 选择数据库和集合
    c := session.DB("db_log").C("precise_budgets")

    // --- 插入示例 ---
    initialBudget := big.NewRat(5, 10) // 0.5
    docToInsert := DocumentWithBudget{
        ID:       bson.NewObjectId(),
        Name:     "Project Alpha Budget",
        Budget:   NewRationalValueFromRat(initialBudget),
        UpdateAt: time.Now(),
    }

    err := c.Insert(&docToInsert)
    if err != nil {
        log.Fatalf("Failed to insert document: %v", err)
    }
    fmt.Printf("Inserted document ID: %s, Initial Budget: %s\n", docToInsert.ID.Hex(), initialBudget.FloatString(10))

    // --- 更新示例 (模拟业务逻辑中的计算) ---
    // 假设我们进行了一系列高精度计算,并需要更新预算
    deduction := big.NewRat(1, 100000) // 0.00001
    currentBudget := initialBudget     // 从初始值开始计算

    fmt.Println("\nPerforming budget calculations and updates:")
    for i := 0; i < 3; i++ { // 循环3次进行扣减
        currentBudget.Sub(currentBudget, deduction) // 扣减预算
        fmt.Printf("  Iteration %d: Current Budget after deduction: %s\n", i+1, currentBudget.FloatString(10))

        // 更新MongoDB中的文档
        updateDoc := bson.M{
            "$set": bson.M{
                "budget":    NewRationalValueFromRat(currentBudget),
                "update_at": time.Now(),
            },
        }
        err = c.UpdateId(docToInsert.ID, updateDoc)
        if err != nil {
            log.Fatalf("Failed to update document: %v", err)
        }
    }

    // --- 检索示例 ---
    var retrievedDoc DocumentWithBudget
    err = c.FindId(docToInsert.ID).One(&retrievedDoc)
    if err != nil {
        log.Fatalf("Failed to retrieve document: %v", err)
    }

    retrievedBudget := retrievedDoc.Budget.ToRat()
    fmt.Printf("\nRetrieved document ID: %s\n", retrievedDoc.ID.Hex())
    fmt.Printf("Retrieved Name: %s\n", retrievedDoc.Name)
    fmt.Printf("Retrieved Budget from MongoDB: %s\n", retrievedBudget.FloatString(10))
    fmt.Printf("Final calculated Budget (in application): %s\n", currentBudget.FloatString(10))
    fmt.Printf("Retrieved Update Time: %s\n", retrievedDoc.UpdateAt.Format(time.RFC3339))

    // 验证检索到的值是否与计算后的值一致
    if retrievedBudget.Cmp(currentBudget) == 0 {
        fmt.Println("Verification successful: Retrieved budget matches calculated budget.")
    } else {
        fmt.Println("Verification failed: Retrieved budget does NOT match calculated budget.")
    }

    // --- 清理 (可选) ---
    // err = c.RemoveId(docToInsert.ID)
    // if err != nil {
    //  log.Printf("Failed to remove document: %v", err)
    // } else {
    //  fmt.Println("\nDocument removed from MongoDB.")
    // }
}

运行前准备:

  1. 确保你已经安装了 Go 语言环境。
  2. 安装 mgo 驱动:go get gopkg.in/mgo.v2
  3. 确保本地运行着 MongoDB 服务,默认端口 27017。

运行上述代码,你将看到数据被成功插入、更新和检索,并且 big.Rat 的高精度特性在整个持久化过程中得到了完整保留。

注意事项与最佳实践

  1. int64 的局限性: 尽管 int64 对于大多数货币和常见分数场景足够,但 big.Rat 内部的 big.Int 理论上可以表示任意大小的整数。如果你的分子或分母可能超出 int64 的最大/最小值(约 ±9 quintillion),则需要更复杂的处理。
    • 替代方案1(字符串存储): 将 big.Int 转换为字符串存储,并在存取时进行字符串与 big.Int 之间的转换。这会增加序列化/反序列化的开销,但能支持任意精度。
    • 替代方案2(自定义BSON marshaler): 为 big.Int 或 big.Rat 实现 bson.Setter 和 bson.Getter 接口,以便 mgo 知道如何直接处理它们。这需要更深入地理解 mgo 的内部机制。
  2. 错误处理: 在实际应用中,从 big.Int 转换为 int64 时,应检查 Int64() 方法是否会丢失精度(即 big.Int 值是否超出 int64 范围)。big.Int 提供 IsInt64() 方法进行检查。
  3. 分母为零: big.NewRat 在分母为零时会 panic。在从数据库读取 RationalValue 并转换为 big.Rat 时,应确保 Denominator 字段不为零,或者在 ToRat 方法中添加相应的检查。
  4. 数据完整性: 确保在应用程序层进行所有 big.Rat 的数学运算,只有在需要持久化时才转换为 RationalValue,并在读取后立即转换回 big.Rat 进行后续操作。

总结

通过为 big.Rat 创建一个自定义的 RationalValue 结构体,并利用其分子和分母的 int64 表示形式,我们可以有效地将高精度有理数存储到MongoDB。这种方法简单、直接,并且在大多数常见应用场景中表现良好。在极端精度需求下,可以考虑将分子和分母作为字符串存储,或实现自定义的BSON序列化逻辑,以确保数据在任何规模下都能保持其完整性。

热门AI工具

更多
讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

创客贴AI

创客贴AI是一款AI图像与设计工具,AI辅助的智能在线设计工具。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1838

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2964

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6244

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

6169

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

7754

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

11734

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

6059

2025.07.29

c++字符串相关教程
c++字符串相关教程

本专题整合了c++字符串相关教程,阅读专题下面的文章了解更多详细内容。

6707

2025.08.07

Tailwind CSS 如何制作导航栏和卡片
Tailwind CSS 如何制作导航栏和卡片

用 Tailwind CSS 制作导航栏和卡片,可先搭建语义化结构,再组合 Flex、Grid、间距、圆角与阴影类。这里整理横向导航、移动端菜单、卡片网格及悬停效果的相关文章,帮助你把常见页面组件做得清晰且适配不同屏幕。

0

2026.11.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn