在之前的章节中,我们了解到该协议存储的是价格的平方根而不是价格本身。因此,有必要将 tick 与定点数 Q64.96 格式表示的价格平方根联系起来。
在本章中,我们将探讨如何在 sqrtPriceX96 和 tick 之间进行转换。
协议存储 Tick 索引
tick 是离散的价格,由公式 给出,其中 被称为 tick 索引(tick index),或简称为 tick。拥有 tick 索引后,就可以唯一确定 tick 价格,反之亦然。由于存储 tick 索引 比存储 占用的位数更少,因此协议存储的是 tick 索引而不是 tick 价格。
在本书中,我们将交替使用 tick(价格)和 tick 索引 这两个术语。在代码库中,名为 tick 的变量始终指代 tick 索引 。
给定 sqrtPriceX96 计算 Tick 索引
我们首先回顾一下价格的平方根与变量 sqrtPriceX96 之间的关系:
因此,通过对两边求平方,即可得出价格与 sqrtPriceX96 之间的关系:
由于价格和 tick 索引之间的关系为 ,我们可以得出
为了用 sqrtPriceX96 来确定 tick 索引 ,我们对上述等式的两边取对数。
对数总是基于某个底数计算的。例如,如果 ,那么 (以 b 为底取对数)。然而,上述等式中的对数可以以任意数值为底。对此的原因将在本章最后一节中解释。
Tick 索引是一个离散值
实际上,上述公式并不完全正确,原因在于:sqrtPriceX96 是一个连续值,而 tick 索引 i 是离散的(一个整数)。因此,该值需要向下取整。
这可以从下面的动画中看出来,其中我们使用了一个可以在我们关于 tick 的文章中找到的工具。请注意,虽然价格在不断变化,但该价格对应的 tick 始终是紧挨着它的下方那一个。
因此,tick 的精确公式为:
其中符号 表示向下取整,例如,。
给定 Tick 索引计算 sqrtPriceX96
为了从 tick 索引计算价格的平方根,我们从以下公式开始:
然后,两边取平方根并乘以 ,
在代码库中实现 Tick 与价格之间的相互转换
在 Solidity 中,tick 和 sqrtPriceX96 之间的转换是由 TickMath library 处理的,由于在 Solidity 中实现对数和平方根较为复杂,因此我们将在单独的一章中对其进行研究。
它包含两个函数:getSqrtRatioAtTick 和 getTickAtSqrtRatio,它们负责执行 sqrtPriceX96 与其对应的 tick 索引之间的相互转换。如下所示。

我们在上一节中已经了解了这些公式的数学原理。在本节中,我们将使用 Python 来执行这一计算。
getSqrtRatioAtTick 函数
Python 中的 getSqrtRatioAtTick 函数可以写为:
def getSqrtRatioAtTick(i):
return math.sqrt(1.0001 ** i) * 2**96
举个例子,价格上限的 sqrtPriceX96 值可以使用 getSqrtRatioAtTick(887272) 来计算,结果为 1.4614467034780703e+48,这(大约)等于 TickMath 库中的 MAX_SQRT_RATIO 常量。

使用 Decimal 库
为了获得更精确的结果,我们可以使用 Python 中的 Decimal 库。下面是使用该库编写的相同公式,以及计算得出的 MAX_SQRT_RATIO 的值。
import math
from decimal import Decimal, getcontext
getcontext().prec = 50
def getSqrtRatioAtTick(i):
base = Decimal("1.0001")
exponent = Decimal(i)
sqrt_value = base ** (exponent / 2)
multiplier = Decimal(2) ** 96
return sqrt_value * multiplier
print(int(getSqrtRatioAtTick(887272))) # 1461446703485210103244672773810124308346321380902
getTickAtSqrtRatio 函数
Python 中的 getTickAtSqrtRatio 函数可以写为:
def getTickAtSqrtRatio(sqrtPriceX96):
return math.floor(2*math.log(sqrtPriceX96/2**96)/math.log(1.0001))
举个例子,sqrtPriceX96 下限值 4295128739 对应的 tick,可以使用 getTickAtSqrtRatio(4295128739) 来计算,结果为 -887272,这符合 tick 下限的预期。
getTickAtSqrtRatio(4295128739) // -887272
改变对数的底数
当我们推导公式时:
我们提到过对数可以以任意数值为底。在 Python 的公式实现中,我们使用了自然对数,但我们也可以使用以 10 为底或任何其他底数的对数,这都会产生相同的结果。
原因在于对数的一个基本性质(换底公式),它将不同底数的对数联系起来:
其中 和 是两个不同的底数,而 是我们想要计算的对数的真数。例如,将以自然数 为底的对数转换为以 10 为底的对数,我们得到:
需要注意的关键点是,除数 仅取决于底数,而与真数无关。假设我们有一个分数:
如果我们想将对数转换为某个任意底数 b,我们将分子和分母都除以 。然而,将一个分数的分子和分母都除以同一个值对最终答案没有影响,因为它会被消掉。
让我们将这一关系应用到我们的 tick 索引 公式中,并将其从自然底数转换为以 10 为底,来更清楚地说明这一点:
正如我们所看到的,转换因子被抵消了,无论底数是多少,计算结果都是一样的。
总结
- 协议需要在
sqrtPriceX96和 tick 索引之间进行转换。这可以通过位于TickMath库中的getSqrtRatioAtTick和getTickAtSqrtRatio函数来完成。 - 要将
sqrtPriceX96转换为 tick 索引,应使用以下公式:
- 要将 tick 索引转换为
sqrtPriceX96,应使用以下公式:
练习题
浏览 Uniswap V3 Pools 找到矿池地址,然后在区块链浏览器中打开该地址。找到公共变量 slot0 并将 tick 转换为 price(价格),反之亦然。

检查你的计算结果是否接近 Uniswap 提供的值,然后将 sqrtPriceX96 转换为美元金额。
对以下矿池进行练习:
- 以太坊上的 USDC/ETH
- Base 上的 ETH/USDC
- 主网上的 WBTC/USDC
稍后我们将学习为什么智能合约直接使用 slot0 中的 sqrtPriceX96(可用于获取价格)和 tick 数据是不安全的,目前这仅仅是为了练习。