新闻中心
Go语言mgo持久化高精度小数:big.Rat的存储方案

本文探讨了在go语言应用中,如何利用`math/big.rat`实现高精度小数计算,并解决其无法直接通过`mgo`库存储到mongodb的问题。核心策略是将`big.rat`对象拆分为分子和分母,以`int64`类型存储在自定义结构体中,从而实现数据的持久化,并在需要时重构为`big.rat`进行精确运算,确保金融等敏感数据的准确性。
在Go语言中进行金融计算或任何需要极高精度小数处理的场景时,标准浮点数类型(如float64)由于其固有的精度限制,往往无法满足要求。math/big包中的big.Rat类型提供了一种基于有理数(分数)的精确小数表示,能够避免浮点运算带来的误差。然而,当需要将包含big.Rat类型的数据持久化到MongoDB时,使用mgo这样的ORM库会遇到一些挑战。
1. math/big.Rat与精确小数计算
big.Rat类型通过存储一个分子(Numerator)和一个分母(Denominator)来表示一个有理数。例如,0.5 可以表示为 1/2。这种表示方式确保了在进行加、减、乘、除等运算时,结果始终是精确的,不会有浮点误差的累积。这对于货币、账务等对精度要求极高的应用至关重要。
以下是一个使用big.Rat进行计算的简单示例:
package main
import (
"fmt"
"math/big"
)
func main() {
// 创建一个大有理数 5/10
rat1 := big.NewRat(5, 10)
fmt.Printf("Initial Rat: %s (Float: %s)\n", rat1.String(), rat1.FloatString(10)) // Output: 1/2 (Float: 0.5000000000)
// 创建一个小的有理数 1/100000
rat2 := big.NewRat(1, 100000)
fmt.Printf("Subtracting Rat: %s (Float: %s)\n", rat2.String(), rat2.FloatString(10)) // Output: 1/100000 (Float: 0.0000100000)
// 进行减法运算
rat1.Sub(rat1, rat2)
fmt.Printf("After Subtraction: %s (Float: %s)\n", rat1.String(), rat1.FloatString(10)) // Output: 49999/100000 (Float: 0.4999900000)
}2. mgo与big.Rat直接存储的挑战
mgo库在将Go结构体映射到MongoDB文档时,通常依赖于结构体字段的可见性(导出字段)和支持的BSON类型。big.Rat的内部结构包含两个未导出的*big.Int字段来表示分子和分母。由于这些字段是未导出的,mgo无法直接访问它们以进行序列化。即使它们是导出的,big.Int本身也不是MongoDB原生支持的BSON类型,直接存储也会遇到困难。
因此,尝试直接将包含big.Rat字段的结构体存储到MongoDB,通常会导致序列化失败或数据丢失。
3. 解决方案:拆分存储分子与分母
解决此问题的有效方法是,不直接存储big.Rat对象,而是将其拆解为分子和分母,并存储这两个基本数值。在需要时,再从存储的分子和分母重构回big.Rat对象。
3.1 定义自定义结构体
为了存储big.Rat的组件,我们可以定义一个简单的自定义结构体,包含两个int64类型的字段来分别存储分子和分母。选择int64是因为它能够覆盖绝大多数实际应用中的数值范围,并且是MongoDB原生支持的BSON整数类型。
// CurrencyValue 用于在MongoDB中存储高精度货币值
type CurrencyValue struct {
Num int64 `bson:"num"` // 分子
Denom int64 `bson:"denom"` // 分母
}3.2 转换与存储
在将数据存入MongoDB之前,我们需要将big.Rat对象转换为CurrencyValue结构体。big.Rat提供了Num()和Denom()方法来获取其分子和分母。
// RatToCurrencyValue 将 big.Rat 转换为 CurrencyValue
func RatToCurrencyValue(r *big.Rat) CurrencyValue {
// 注意:big.Rat 的 Num() 和 Denom() 返回的是 *big.Int
// 这里我们假设其值在 int64 范围内,并进行转换
return CurrencyValue{
Num: r.Num().Int64(),
Denom: r.Denom().Int64(),
}
}3.3 检索与重构
从MongoDB中检索数据后,我们得到的是CurrencyValue结构体。此时,我们需要将其转换回big.Rat对象,以便进行后续的精确计算。big.NewRat函数可以从两个int64值创建一个新的big.Rat。
Whimsical
Whimsical推出的AI思维导图工具
182
查看详情
// CurrencyValueToRat 将 CurrencyValue 转换为 *big.Rat
func CurrencyValueToRat(cv CurrencyValue) *big.Rat {
return big.NewRat(cv.Num, cv.Denom)
}4. 实践示例
下面是一个完整的Go语言示例,演示如何使用mgo将big.Rat类型的数据存储到MongoDB,并进行检索和重构。
package main
import (
"fmt"
"log"
"math/big"
"time"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// CurrencyValue 用于在MongoDB中存储高精度货币值
type CurrencyValue struct {
Num int64 `bson:"num"` // 分子
Denom int64 `bson:"denom"` // 分母
}
// Product 代表一个包含精确价格的产品
type Product struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Name string `bson:"name"`
Price CurrencyValue `bson:"price"`
Qty int `bson:"qty"`
}
// RatToCurrencyValue 将 big.Rat 转换为 CurrencyValue
func RatToCurrencyValue(r *big.Rat) CurrencyValue {
// 检查分母是否为零,避免潜在的运行时错误
if r.Denom().Cmp(big.NewInt(0)) == 0 {
log.Fatalf("Error: big.Rat has zero denominator. Cannot convert to CurrencyValue.")
}
return CurrencyValue{
Num: r.Num().Int64(),
Denom: r.Denom().Int64(),
}
}
// CurrencyValueToRat 将 CurrencyValue 转换为 *big.Rat
func CurrencyValueToRat(cv CurrencyValue) *big.Rat {
// 检查分母是否为零,避免潜在的运行时错误
if cv.Denom == 0 {
log.Fatalf("Error: CurrencyValue has zero denominator. Cannot convert to big.Rat.")
}
return big.NewRat(cv.Num, cv.Denom)
}
func main() {
// 1. 连接MongoDB
session, err := mgo.Dial("mongodb://localhost:27017")
if err != nil {
log.Fatalf("Failed to connect to MongoDB: %v", err)
}
defer session.Close()
// 可选:设置会话模式
session.SetMode(mgo.Monotonic, true)
db := session.DB("test_db")
collection := db.C("products")
// 清理旧数据 (仅为示例方便)
if _, err := collection.RemoveAll(nil); err != nil {
log.Printf("Failed to clear collection: %v", err)
}
// 2. 创建一个包含 big.Rat 价格的产品
productPriceRat := big.NewRat(19999, 100) // 199.99
product := Product{
ID: bson.NewObjectId(),
Name: "Example Product A",
Price: RatToCurrencyValue(productPriceRat), // 转换为 CurrencyValue
Qty: 10,
}
// 3. 插入产品到MongoDB
err = collection.Insert(&product)
if err != nil {
log.Fatalf("Failed to insert product: %v", err)
}
fmt.Printf("Inserted product: %s with price %s (stored as %v)\n",
product.Name, productPriceRat.FloatString(2), product.Price)
// 4. 从MongoDB检索产品
var retrievedProduct Product
err = collection.Find(bson.M{"_id": product.ID}).One(&retrievedProduct)
if err != nil {
log.Fatalf("Failed to retrieve product: %v", err)
}
// 5. 将检索到的 CurrencyValue 价格重构回 big.Rat
retrievedPriceRat := CurrencyValueToRat(retrievedProduct.Price)
fmt.Printf("Retrieved product: %s with price %s (reconstructed from %v)\n",
retrievedProduct.Name, retrievedPriceRat.FloatString(2), retrievedProduct.Price)
// 6. 进行计算验证
anotherProductPriceRat := big.NewRat(99, 100) // 0.99
sumPrice := new(big.Rat).Add(retrievedPriceRat, anotherProductPriceRat)
fmt.Printf("Calculated total price: %s\n", sumPrice.FloatString(2))
// 7. 存储一个更新后的产品
retrievedProduct.Qty = 5
updatedPriceRat := big.NewRat(150, 3) // 50.00
retrievedProduct.Price = RatToCurrencyValue(updatedPriceRat)
err = collection.UpdateId(retrievedProduct.ID, retrievedProduct)
if err != nil {
log.Fatalf("Failed to update product: %v", err)
}
fmt.Printf("Updated product: %s with new price %s\n",
retrievedProduct.Name, updatedPriceRat.FloatString(2))
// 再次检索验证更新
var updatedRetrievedProduct Product
err = collection.Find(bson.M{"_id": retrievedProduct.ID}).One(&updatedRetrievedProduct)
if err != nil {
log.Fatalf("Failed to retrieve updated product: %v", err)
}
updatedRetrievedPriceRat := CurrencyValueToRat(updatedRetrievedProduct.Price)
fmt.Printf("Verified updated product price: %s\n", updatedRetrievedPriceRat.FloatString(2))
}5. 注意事项与最佳实践
-
数据类型选择 (int64 vs big.Int): big.Rat内部使用*big.Int来存储分子和分母,这意味着它们理论上可以任意大。而我们选择int64作为存储类型,虽然能满足绝大多数常见需求(如货币金额),但如果分子或分母的值可能超出int64的最大范围(约9 quintillion),则需要考虑其他存储方案。
- 替代方案: 如果int64不足以表示,可以将big.Int转换为字符串(big.Int.String())存储到MongoDB,并在检索时通过new(big.Int).SetString(s, 10)将其解析回来。这种方法会增加存储空间和序列化/反序列化开销,但提供了无限精度。
错误处理: 示例代码中为了简洁使用了log.Fatalf,但在生产环境中,应采用更健壮的错误处理机制,例如返回错误而不是直接终止程序。特别是在RatToCurrencyValue和CurrencyValueToRat函数中,应处理分母为零的情况,避免除零错误。
性能考量: 每次存取都需要进行类型转换,这会带来一定的性能开销。对于读写频率极高的场景,需要评估这种开销是否可接受。
-
更高级的封装 (SetBSON/GetBSON): 为了使Product结构体中的Price字段能够更透明地处理big.Rat,可以为CurrencyValue类型实现mgo的SetBSON和GetBSON接口。这样,在Product结构体中就可以直接使用big.Rat类型,而mgo会在底层自动调用这些接口进行转换,从而隐藏了CurrencyValue这个中间层。
// 示例:为 big.Rat 实现 mgo 的 BSON 接口(需要一个包装类型) type BSONRat big.Rat func (br *BSONRat) GetBSON() (interface{}, error) { if br == nil { return nil, nil } return CurrencyValue{ Num: (*big.Rat)(br).Num().Int64(), Denom: (*big.Rat)(br).Denom().Int64(), }, nil } func (br *BSONRat) SetBSON(raw bson.Raw) error { var cv CurrencyValue if err := raw.Unmarshal(&cv); err != nil { return err } // 初始化 br *br = BSONRat(*big.NewRat(cv.Num, cv.Denom)) return nil } // 此时 Product 结构体可以这样定义: type ProductWithBSONRat struct { ID bson.ObjectId `bson:"_id,omitempty"` Name string `bson:"name"` Price BSONRat `bson:"price"` // 直接使用 BSONRat }这种方式使得上层业务代码在使用时更加方便,无需手动进行RatToCurrencyValue和CurrencyValueToRat的转换。
6. 总结
在Go语言中结合mgo和math/big.Rat处理高精度小数时,通过将big.Rat拆分为分子和分母(通常存储为int64类型),并使用自定义结构体进行持久化,可以有效地解决直接存储的难题。这种方法既保证了计算的精确性,又兼容了MongoDB的数据存储机制。根据具体需求,可以进一步利用mgo的BSON接口进行封装,以提供更平滑的开发体验。
以上就是Go语言mgo持久化高精度小数:big.Rat的存储方案的详细内容,更多请关注其它相关文章!
# 将其
# 如何平衡营销费用和推广
# vue如何seo优化
# 网站优化软件 site w
# seo推广培训中心
# 江苏网站优化哪家专业
# 抖音怎样推广营销内容呢
# 水果营销推广方案
# 澄迈县网站建设单价
# 冀州建设网站
# 营销推广萌宠活动方案
# 为零
# 序列化
# 是一个
# 极高
# 的是
# go
# 创建一个
# 自定义
# 重构
# 转换为
# red
# 币
# 数据丢失
# 敏感数据
# 金融
# win
# ai
# session
# go语言
# mongodb
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
qq邮箱发邮件给国外发不出去_QQ邮箱国际邮件发送失败原因与解决
解决macOS Tkinter应用双击启动崩溃:PyInstaller打包指南
iCloud登录入口网页版 苹果iCloud官网登录
火狐浏览器占用内存高卡顿怎么办 火狐浏览器性能优化设置技巧
PostgreSQL海量数据高效导入策略:Python与Django实践指南
在Go语言中利用后缀数组处理多字符串:实现高效文本匹配与自动补全
J*aScript对象创建方式_J*aScript设计模式应用
2026年CSGO开箱网站推荐 CSGO开箱平台精选
Excel文件在线转换快速入口 Excel在线格式转换网站
Excel中VLOOKUP的第四个参数是干什么用的_Excel VLOOKUP第四参数作用解析
在J*a项目里如何构建对象之间的契约_接口约束的实际落地
腾讯QQ邮箱登录入口_QQ邮箱官方网站使用地址
必由学官方平台入口 必由学在线课堂登录地址
漫蛙MANWA漫画主页官方入口 漫蛙漫画最新在线阅读地址
印象笔记如何设离线包出差查阅_印象笔记设离线包出差查阅【离线阅读】
Sublime Text怎么显示空格和制表符_Sublime显示不可见字符设置
MAC怎么安装Homebrew包管理器_MAC为开发者和高级用户安装命令行工具
小米14应用无法联网原因分析_小米14网络权限修复
QQ邮箱网页版入口页面 QQ邮箱在线登录入口官网
steam官方入口大全 steam账号注册及操作指南
excel如何生成目录 excel一键生成工作表目录超链接
特斯拉自动驾驶房车计划曝光 原型车将于2027年亮相
Win11文件资源管理器卡顿怎么修 Win11重置资源管理器进程优化响应速度【修复方法】
马斯克:Optimus 人形机器人复数形式为 Optimi
漫蛙漫画登录站点 漫蛙2正版漫画快速访问
C++如何操作注册表_Windows平台下C++读写注册表的API函数详解
Lar*el用户头像管理:实现图片缩放、存储与旧文件安全删除的最佳实践
Lar*el的路由模型绑定怎么用_Lar*el Route Model Binding简化控制器逻辑
Win10文件资源管理器“此电脑”分组怎么关 Win10恢复经典视图【技巧】
Golang如何实现状态模式管理对象状态_Golang State模式实现技巧
Golang如何通过reflect操作map_Golang reflect map操作与遍历技巧
在J*a中如何在J*a中使用异常机制记录错误日志_异常日志实践经验
探索高级语言到C/C++的转译路径:以Go为例及内存管理策略
Fabric Mod开发:在1.19.3+版本中正确添加自定义物品并管理物品组
Windows 11怎么彻底关闭定位_Windows 11服务中禁用Geolocation
Win11怎么隐藏桌面图标 Win11一键隐藏所有桌面元素及恢复显示
Mac怎么查看崩溃日志_Mac控制台错误报告分析
随机参数递归函数的基准调用次数与时间复杂度探究
初次安装JDK时环境变量如何正确配置_J*A_HOME与PATH设置规则讲解
QQ邮箱网页版入口 QQ邮箱官方邮箱登录通道
手机屏幕碎了但能正常使用怎么办 手机外屏碎裂的修复建议
品牌机怎么重装系统 联想/戴尔/惠普笔记本恢复出厂系统教程
支付宝解绑银行卡步骤_支付宝如何解除绑定银行卡
在Pyomo中实现基于变量的条件约束:Big-M方法详解
Go语言JSON解析深度指南:动态访问与结构体映射实践
SteamMachine定价或为699美元 大家想入手吗?
2025-2030年全球乘用车销量预测:新能源成增长主力
AO3访问入口汇总 AO3网页版同人作品一键直达
HuggingFaceEmbeddings中向量嵌入维度调整的限制与理解
Windows10怎么开启夜间模式 Windows10系统设置调整色温与亮度缓解夜间用眼疲劳【教程】


2025-11-18
浏览次数:次
返回列表
err = collection.Insert(&product)
if err != nil {
log.Fatalf("Failed to insert product: %v", err)
}
fmt.Printf("Inserted product: %s with price %s (stored as %v)\n",
product.Name, productPriceRat.FloatString(2), product.Price)
// 4. 从MongoDB检索产品
var retrievedProduct Product
err = collection.Find(bson.M{"_id": product.ID}).One(&retrievedProduct)
if err != nil {
log.Fatalf("Failed to retrieve product: %v", err)
}
// 5. 将检索到的 CurrencyValue 价格重构回 big.Rat
retrievedPriceRat := CurrencyValueToRat(retrievedProduct.Price)
fmt.Printf("Retrieved product: %s with price %s (reconstructed from %v)\n",
retrievedProduct.Name, retrievedPriceRat.FloatString(2), retrievedProduct.Price)
// 6. 进行计算验证
anotherProductPriceRat := big.NewRat(99, 100) // 0.99
sumPrice := new(big.Rat).Add(retrievedPriceRat, anotherProductPriceRat)
fmt.Printf("Calculated total price: %s\n", sumPrice.FloatString(2))
// 7. 存储一个更新后的产品
retrievedProduct.Qty = 5
updatedPriceRat := big.NewRat(150, 3) // 50.00
retrievedProduct.Price = RatToCurrencyValue(updatedPriceRat)
err = collection.UpdateId(retrievedProduct.ID, retrievedProduct)
if err != nil {
log.Fatalf("Failed to update product: %v", err)
}
fmt.Printf("Updated product: %s with new price %s\n",
retrievedProduct.Name, updatedPriceRat.FloatString(2))
// 再次检索验证更新
var updatedRetrievedProduct Product
err = collection.Find(bson.M{"_id": retrievedProduct.ID}).One(&updatedRetrievedProduct)
if err != nil {
log.Fatalf("Failed to retrieve updated product: %v", err)
}
updatedRetrievedPriceRat := CurrencyValueToRat(updatedRetrievedProduct.Price)
fmt.Printf("Verified updated product price: %s\n", updatedRetrievedPriceRat.FloatString(2))
}