池子定价用的数是整数:AMM 定点数舍入在一笔兑换里丢了什么 图 1
池子定价用的数是整数:AMM 定点数舍入在一笔兑换里丢了什么 · 图 1

把一笔链上兑换的报价和实际到账数量放在一起对比,很多人会发现两者差着最后几位——模拟说能拿一万个单位,链上到账九千九百九十九,差的那一个既不是手续费也不是滑点,而是取整。这不是某个交易所的毛病,而是整个链上自动做市共同的底层设定:智能合约做乘除运算时不用小数,只用整数,凡是除不尽的部分必须有个去处。本文把这件事拆开讲清楚:整数运算在兑换的哪一步发生、取整方向怎么选、误差会不会越滚越大,以及普通用户到底需不需要为这几个最小单位操心。

先说合约为什么不用小数。编程语言里的浮点数靠二进制近似表示小数,同样的算式在不同节点、不同编译器上可能得出末位不同的结果。区块链对一致性的要求是全网每个节点执行同一笔交易必须得到一模一样的状态,哪怕最后一位的分歧都会导致分叉,所以合约层的金融计算普遍采用定点数:约定好一个放大倍数,把价格、数量统统放大成整数来存来算,用的时候再按比例读回来。这样每个节点做的都只是整数加减乘除,结果天然确定。代价也随之而来:整数世界里没有四舍五入的天然选项,任何一次除法都必须事先规定丢弃尾数还是进位,这个事先规定就是取整方向。

再看兑换流水线上取整发生的位置。以最常见的乘积公式池为例,计算输出数量的算式里至少藏着两次除法:一次在算输出,一次在随后更新储备或记账。协议的设计者在写代码时面对一个选择题:宁可让用户多拿一点,还是宁可让池子多留一点。几乎所有成熟实现的取向是让不利于交易者的方向取整——输出向下取整、输入向上取整,也就是宁可用户少收一个最小单位,也不让池子多付一个。原因不是小气,而是防御:如果取整方向有利于交易者,攻击者可以构造一连串只涉及一个最小单位的兑换,每次都靠进位多薅一个单位,用几千笔小额交易把池子磨穿。向下取整则把这扇门焊死,被舍掉的那部分留在池子里,归全体做市人按份额所有。

于是常见的现象有了解释。第一,报价和到账差一两个最小单位是常态,代币的小数位越多,这个差额在法币计价下通常小到分以下;代币精度低、小数位少时,一个最小单位本身价值就不小,舍入差反而值得看一眼。第二,这个差额在单笔里是确定性的,同一输入在同一储备下永远舍去同样多;而报价器在自己的模拟合约里可能用了另一套取整顺序,与执行合约差出末位,这属于报价与执行的口径差,不是亏损放大。第三,反复做同一种超小额兑换并不会让协议吃掉更多——每笔舍去的尾数本来就只有一两个单位,且不会因为你换得勤而改变规则,真正会积累的是手续费和 gas,而不是取整本身。

对普通用户的操作含义可以收敛成四条。其一,看到到账比报价少一两个最小单位不必惊慌,这属于合约算式的一部分;真正要警惕的是差额达到可见量级,那要回头查滑点、费率和路由。其二,涉及低精度代币的大额兑换,可以在发送前用预览接口看合约算出的精确输出,以它而不是网页报价为准设置最低输出。其三,写自动化脚本时别在本地用浮点数重算价格——本地浮点结果和合约整数结果不一致会导致整笔回滚,正确做法是本地只做范围校验,精确值交给链上预览调用。其四,评估任何新协议时,可以在开源实现里搜它的取整库,一个认真做过金融计算的协议会明确写出每个方向的舍入规则并配套测试用例,这比宣传页上的精确透明字样可靠得多。

最后把边界说清楚。取整损失在绝大多数正常兑换里属于尘埃量级,把它渲染成隐形税费是夸大;但在极低精度代币、超高频微额操作和自写清算脚本这三类场景里,取整方向会真实影响结果,值得写代码的人逐处确认。本文仅为链上计算机制科普,不构成投资建议与收益承诺,具体协议的取整实现以其公开合约代码和文档为准。

池子定价用的数是整数:AMM 定点数舍入在一笔兑换里丢了什么 图 2
池子定价用的数是整数:AMM 定点数舍入在一笔兑换里丢了什么 · 图 2