定点数的更多内容

  • Jeiwan
  • 发布于 2025-10-04 18:34
  • 阅读 17

在本奖励章节中,我想向您展示如何在 Solidity 中将价格转换为 tick。我们不需要在主合约中执行此操作,但是在测试中拥有这样的函数会很有帮助,这样我们就不必硬编码 tick,而是可以编写类似 tick(5000) 的代码——这使得代码更易于阅读,因为我们更容易以价格而不是 tick 指数来思考。

回想一下,为了找到 tick,我们使用 TickMath.getTickAtSqrtRatio 函数,该函数以 $\sqrt{P}$ 作为参数,并且 $\sqrt{P}$ 是一个 Q64.96 定点数。在智能合约测试中,我们需要在许多不同的测试用例中多次检查 $\sqrt{P}$:主要是在铸造和交换之后。与其硬编码实际值,不如使用像 sqrtP(5000) 这样的辅助函数来将价格转换为 $\sqrt{P}$,这可能更简洁。

那么,问题是什么?

问题在于 Solidity 本身不支持平方根运算,这意味着我们需要第三方库。 另一个问题是,价格通常是相对较小的数字,例如 10、5000、0.01 等,并且我们不想在取平方根时损失精度。

您可能还记得,我们在本书前面使用了 PRBMath 来实现一个先乘后除的运算,这样在乘法过程中不会溢出。 如果您检查 PRBMath.sol 合约,您会注意到 sqrt 函数。但是,该函数不支持定点数,正如函数描述所说的那样。 您可以尝试一下,会发现 PRBMath.sqrt(5000) 的结果是 70,这是一个失去精度的整数(没有小数部分)。

如果您查看 prb-math 存储库,您会看到这些合约:PRBMathSD59x18.solPRBMathUD60x18.sol。 啊哈! 这些是定点数实现。 让我们选择后者,看看会发生什么:PRBMathUD60x18.sqrt(5000 * PRBMathUD60x18.SCALE) 返回 70710678118654752440。 这看起来很有趣! PRBMathUD60x18 是一个库,它实现了小数部分有 18 位小数的定点数。 所以我们得到的数字是 70.710678118654752440(使用 cast --from-wei 70710678118654752440)。

但是,我们不能使用这个数字!

有定点数和定点数。 Uniswap V3 使用的 Q64.96 定点数是一个二进制数——64 和 96 表示二进制位。 但是 PRBMathUD60x18 实现了一个十进制定点数(合约名称中的 UD 表示“无符号,十进制”),其中 60 和 18 表示十进制位。 这种差异非常显着。

让我们看看如何将任意数字 (42) 转换为上述任何一种定点数:

  1. Q64.96: $42 * 2^{96}$ 或者使用按位左移,2 << 96。 结果是 3327582825599102178928845914112。
  2. UD60.18: $42 * 10^{18}$。 结果是 42000000000000000000。

现在让我们看看如何转换带小数部分的数字 (42.1337):

  1. Q64.96: $421337 * 2^{92}$ 或者 421337 << 92。 结果是 2086359769329537075540689212669952。
  2. UD60.18: $421337 * 10^{14}$。 结果是 42133700000000000000。

第二种变体对我们来说更有意义,因为它使用了我们在童年时代学到的十进制系统。 第一种变体使用二进制系统,我们更难阅读。

但是不同变体带来的最大问题是它们很难相互转换。

这一切都意味着我们需要一个不同的库,一个实现二进制定点数的库,以及一个用于它的 sqrt 函数。 幸运的是,有这样的一个库:abdk-libraries-solidity。 该库实现了 Q64.64,不完全是我们需要的(小数部分不是 96 位),但这不成问题。

以下是我们如何使用新库来实现价格到刻度的函数:

function tick(uint256 price) internal pure returns (int24 tick_) {
    tick_ = TickMath.getTickAtSqrtRatio(
        uint160(
            int160(
                ABDKMath64x64.sqrt(int128(int256(price << 64))) <<
                    (FixedPoint96.RESOLUTION - 64)
            )
        )
    );
}

ABDKMath64x64.sqrt 接受 Q64.64 数字,因此我们需要将 price 转换为此类数字。 预计价格没有小数部分,因此我们将其左移 64 位。 sqrt 函数也返回一个 Q64.64 数字,但是 TickMath.getTickAtSqrtRatio 接受一个 Q64.96 数字——这就是为什么我们需要将平方根运算的结果左移 96 - 64 位。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论