西班牙商人奠定了 GnuCash 数据库设计标准
摘要
本文解释了西班牙商人不用拇指计数的做法如何影响了 GnuCash 数据库的设计选择——将货币值存储为整数最小单位,并借此类比历史奇闻如何塑造现代技术。
暂无内容
查看缓存全文
缓存时间: 2026/06/08 18:18
# 马的屁股、大拇指与GnuCash的数据设计 — HandsOnMoney博客
来源:https://handson.money/blog/2026-06-06-horse-arse-and-design/
2026年6月6日 • 作者:Vitalik
## 马的屁股决定了铁路标准,西班牙商人决定了GnuCash数据库设计
**太长不看:17世纪的西班牙商人不想数自己的大拇指,这影响了1997年GnuCash的数据库设计选择——就像马的屁股决定了铁路标准一样(https://x.com/BillHolohanSC/status/1177631604186996737)。看似奇怪的设计,最终却成为巧妙服务用户的绝佳方案。**
又是一个这样的夜晚。桌上放着一杯热咖啡。我正在为 HandsOnMoney (https://handson.money/) 实现商品支持,表面上看起来微不足道,实际上却相当深奥。毕竟,有什么比货币更简单呢?无非是美元和美分。有什么比存储一个数字并计算总数更简单呢?
别急,这背后有很多层面……
## 第一层:文化
1美元并不总是等于100美分。嗯,实际上是的。但其他货币可能没有“分”,或者有1000个“分”。这些“分”通常被称为“辅币单位”。
这里有几个具体例子:
1. 日元没有辅币单位(二战后通货膨胀所致)。
2. 科威特第纳尔有1000个辅币单位(这使得该国可以使用更小的价值增量,并为贸易保持精确计价)。
3. 比特币则有1亿聪!
但还有更古老的货币——西班牙银元(real de a ocho),可以分成8份!也就是说,硬币的最小不可分单位是1/8。
这引出了下一层——我们到底该如何存储它?
## 第二层:软件
计算机不擅长处理分数。有抽象类型:`float` 和 `double`。但它们只是近似值,并非真正的分数。90年代末和21世纪初,我们有了 `decimal` 类型,这好得多;然而,GnuCash 发布时可能还没有这种类型。
关于这个问题已经有很多讨论。通常,开发者使用 `double` 数值类型来记录和存储浮点数。但这种记录浮点数的方法并不适用于货币。
例如,有可能出现这种情况:
1.03 - 0.42 = 0.6100000000000001
这看起来像是一个微小的舍入误差。但在大量交易中,舍入误差会累积,最终你会看到奇怪的账户余额。
此外,货币具有固定精度和特定的舍入规则。因此,软件工程师们多次踩中这个雷区,以至于他们开发出了一个通用模式——Money。简而言之,就是将辅币单位以整数形式存储,因为计算机非常擅长处理整数。所以,不存储 $5.23,而是存储 523 美分。
问题解决了吗?和你想的一样——没有。
## 第三层:历史
这不只是钱的问题。嗯,也是钱的问题。但不仅仅是钱的问题。还涉及商品。简而言之,货币是一种商品。人们交易各种商品——货币、股票、基金。
这种泛化需要我们先回到过去。然后……
### 像1998年一样狂欢
那是1998年。GnuCash(当时叫 xacc)首次发布那年。互联网泡沫资金像消防水带一样喷涌而出,到处都在狂欢……
但最重要的是——纽交所报价不是十进制,而是分数制!
GnuCash 是在2001年美国交易所完成十进制化之前发布的。
为什么?再坐上时光机……
### ……像16世纪一样交易
当纽交所于200多年前成立时,它采用了17世纪的西班牙交易体系,基于分数,因为交易者用手指数金币(达布隆),跳过拇指。这就是为什么最小的股票增量是1/8美元。这不是技术决策,而是拇指决策。(来源:Investopedia (https://www.investopedia.com/ask/answers/why-nyse-switch-fractions-to-decimals/))
好了,数手指数到16世纪也够了。让我们回到2026年。
## 第四层:数据工程
2026年,一切都用十进制交易。我们又开始数手指和拇指了。
GnuCash 存储分数的设计决策是过去的遗留物吗?
它已经引发过一个 Bug:
> 2.7.8 版本中的寄存器对商品账户显示分数价格。这看起来有点混乱,因为我的大脑处理 1250/2449 这样的除法太慢了。详见 https://bugzilla.gnome.org/show_bug.cgi?id=794755
大体上是的。GnuCash 充满了过去的古怪之处(它仍然支持意大利里拉,而里拉早在1999年就被欧元取代了!)。
但这个看似过时的存储分数的设计决策,在现代世界中却表现得极其出色。
考虑这个例子:如果我在2011年用90美分买了1个比特币。在我的 GnuCash 账簿中,我记录如下:
| 账户 | 借方 | 贷方 |
|------|------|------|
| 2011年 支票账户 | 9/10 美分 | |
| 比特币 | | 1 个比特币 |
现在到了2026年。我可以卖出部分持仓,并以比特币的最小单位——聪来定价。
我可以轻松地在商品编辑器中把比特币的精度从1改为100,000,000,而 GnuCash 仍然能毫无问题地计算余额。
例如,这里我在2026年卖出3聪:
| 账户 | 借方 | 贷方 |
|------|------|------|
| 2026年 支票账户 | 1$ | |
| 比特币 | | 3 / 100,000,000 个比特币 |
就这么简单,这就是它巧妙之处!
然而,没有一个现代系统是这样工作的。为什么?
## 第五层:可扩展性
将每个金额都存为分数是一个非常灵活的解决方案。但是,它很慢。
做加法或减法?你需要找到最小公分母。
你需要将结果约分,否则就有可能超出类型边界。
在大规模场景下,复杂性和性能问题要严重得多。而且考虑到现在一切都以十进制分数交易,从计算上来说,处理“1150美分 + 1075美分”远比“11 1/2 美元 + 10 3/4 美元”要容易得多。
## HandsOnMoney 是如何工作的?
当我最初设计 HandsOnMoney 时,它反映了我对货币的有限理解。HandsOnMoney 设计为以辅币单位存储金额,每个账户有自己的固定精度商品。这个决定至今仍然成立,使用分数所带来的权衡仍然大于使用辅币单位的简单性。实际上,这意味着与 GnuCash 有些东西不完全兼容:
1. 不支持每个账户使用非标准分数。
2. 无法在运行中更改商品精度。
3. 显然,不支持分数商品。
所以,除非你是16世纪的西班牙商人,或者你有一本90年代的分数制股票账簿,HandsOnMoney 都能很好地为你服务。
*令人惊讶的是,这是人类写的 :)*
相似文章
GnuCash 没错。这也是我打造自己财务应用的原因。
作者是一名注册会计师,他认为 GnuCash 提供了正确的复式记账功能,但日常使用过于繁琐。他构建了 K-Id,一款面向 Windows 的本地优先桌面应用,在保持复式记账严谨性的同时降低了使用门槛,以一次性购买方式销售。
纯文本记账真的超酷
作者讨论了他们在个人财务追踪方面的历程,并介绍了纯文本记账作为一种可靠的方法,使用纯文本文件和复式记账法,比传统的财务应用如Rocket Money和Origin更受青睐。
软件狂人
在一篇个人随笔中,Craig Mod 描述了他对使用 Claude Code 构建定制软件的痴迷,最终构建了一个自定义会计系统,用于处理他在多个国家和货币中的复杂财务需求。
默认有符号整数
一篇讨论编程语言设计中默认使用有符号整数还是无符号整数的文章,以 Odin 和 C3 为例。作者认为尽管无符号整数在理论上有优势,但默认使用有符号整数更实用。
@DamiDefi: https://x.com/DamiDefi/status/2071192941750599725
一名交易者在Obsidian中建立了交易日志,并使用Claude分析了六个月的记录,发现71%的亏损交易与库中已有的笔记相矛盾。这篇文章分享了日志结构以及AI辅助分析带来的洞察。