当前位置: 首页 > news >正文

uniswap getTickAtSqrtPrice 方法解析

先上代码:

    function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {unchecked {// Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();// second inequality must be >= because the price can never reach the price at the max tick// if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true// if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);}uint256 price = uint256(sqrtPriceX96) << 32;uint256 r = price;uint256 msb = BitMath.mostSignificantBit(r);if (msb >= 128) r = price >> (msb - 127);else r = price << (127 - msb);int256 log_2 = (int256(msb) - 128) << 64;assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(63, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(62, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(61, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(60, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(59, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(58, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(57, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(56, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(55, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(54, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(53, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(52, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(51, f))r := shr(f, r)}assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(50, f))}int256 log_sqrt10001 = log_2 * 255738958999603826347141; // Q22.128 number// Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x)int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);// Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when// sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE// is changed, this may need to be changed tooint24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;}}


公式推导

首先回忆一下uniswap v3/v4的价格公式


\frac{token1}{token0}=price=1.0001^{tick}


sqrtPrice=\sqrt{price}=\sqrt{1.0001^{tick}}

这个方法顾名思义是通过价格的平方根求出tick。

对于这类数学计算方法的解析首先要从公式的推导开始,了解其计算步骤才能逐步拆解代码。

整个计算步骤基于的是对数换底公式:

以 a 为底的 b 的对数,等于以 c 为底的 b 的对数除以以 c 为底的 a 的对数。
(其中 a,b,c>0且 a,b≠1, c 是任意可选的新底数。)

总结下来就是:

\log_{a}b=\frac{\log_{c}b}{\log_{c}a}

高中的数学知识,这里稍微回忆一下:
 

设:x=\log_{a}b,也就是说a^{x}=b

两边同时取以c 为底的对数得到:\log_{c}(a^{x})=\log_{c}(b) 

进一步得出 x\log_{c}(a)=\log_{c}(b)

所以:\log_{a}b=x=\frac{\log_{c}b}{\log_{c}a}

为了后续的计算可以转换成位运算,我们用2作为底数,整个计算就可以转换成:
tick=\log_{\sqrt{1.0001}}sqrtPrice=\frac{\log_{2}sqrtPrice}{\log_{2}\sqrt{1.0001}}
 

{\log_{2}\sqrt{1.0001}}作为一个常数,我们线下计算出定义成代码中的常量即可。整个方法的关键就是计算\log_{2}sqrtPrice

我们假设\log_{2}sqrtPrice=m.n,m是整数部分,n是小数部分,接下来的计算主要分为三步:

  • 避免浮点型运算,对sqrtPrice做一些前置处理,将其转换成整数
  • 计算整数部分
  • 计算小数部分

精度转换

这个方法只有一个参数sqrtPriceX96,x96的意思代表此参数是一个小数位精度96的数字,什么意思呢?我们知道计算机在处理计算的过程中所有的数字都用二进制保存,而sqrtPriceX96这个数字的小数是用96位的二进制数保存,精度可以说相当的高了。

@param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96

通过代码的注释,我们也可以看出 sqrtPriceX96是一个Q64.96 格式的定点数,就是说整数部分是64位,小数部分是96位。

64和96位数的制定是有一些来由的,这里要从tick说起,我们知道tick的范围是-887272到887272

于是我们得出:

minPrice = 1.0001^{-887272}=\frac{1}{1.0001^{887272}}

1.0001^{887272}\approx 2^{128}

进一步推导得出:

minPrice = \frac{1}{1.0001^{887272}}\approx2^{-128}

maxPrice \approx2^{128}
 

所以

minSqrtPrice=\sqrt{minPrice }\approx2^{-64}
maxSqrtPrice=\sqrt{minPrice }\approx2^{64}

也就是说sqrtPrice的小数部分最多是64位,整数部分最多也是64位,所以sqrtPrice的精度制定最起码需要Q64.64,而实际上Uniswap的许多计算(例getTickAtSqrtPrice 和 getSqrtPriceAtTick)涉及多次乘法、除法,这些计算的中间结果其精度可能会超过64,为了减小误差,需要把小数部分的精度进一步扩大最终定在Q64.96,这就是sqrtPriceX96参数名称的由来。

接下来看第一行代码:

if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
}

这里的几个常量定义如下:

uint160 internal constant MIN_SQRT_PRICE = 4295128739;uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =1461446703485210103287273052203988822378723970342 - 4295128739 - 1;

首先要明确Qn.m定点数的表示方式,为了避免浮点型运算,所有的小数都会被转换成整数,打个比方2.7这个小数转换成Q2.4是什么样呢?
首先2.7转换成二进制是大约是10.1011,接着我们将其转换成Q2.4,Q2.4就是说小数部分最多是4位,所以转换成整数先乘以2的四次方也就是101011后面4位代表小数,前面两位是整数。101011,转换成十进制是43,也就是说给你一个整数43告诉你他的是一个Q2.4格式的定点数,你需要反向推导出它其实是2.7

说这么多就是要解释这几个常量值的由来,前面推导过

minSqrtPrice=\sqrt{minPrice }\approx2^{-64}
2^{-64}转换成Q64.96格式的整数需要先乘以2^{96},也就是2^{32},于是
MIN_SQRT_PRICE = 4295128739;
而maxSqrtPrice转换成Q64.96则是2^{160},所以
MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342

sqrtPriceX96 必然在这个范围中。

uint256 price = uint256(sqrtPriceX96) << 32;

为了方便后续的计算需要进一步扩展精度
uint160 → uint256,高位补零===》[96个0][64 位整数][96 位小数]
<< 32后===>[64个0][64 位整数][96 位小数][32个0]

此时小数和整数的分界点在第127位和第128位之间(从0位开始算)

对数整数部分的计算
 

先介绍一个概念:最高有效位(msb)

其代表位二进制格式中值为 1 的最高位的位置(从 0 开始计数)

比如说5转换成二进制是101,他的最高有效位msb=2

这里其实有个定律,假设\log_{2}x=m.n,那么对数的整数部分m就是最高有效位的值

比如\log_{2}5=2.n,各位读者也可以拿其他的数字尝试一下,所以要求\log_{2}sqrtPrice=m.n

中m的值,第一步就是要计算出sqrtPrice的最高有效位(msb)

uint256 msb = BitMath.mostSignificantBit(r); 

后面我会专门开一篇介绍mostSignificantBit方法的实现,这里只需要知晓其作用即可。

对数小数部分的计算

假设\log_{2}sqrtPrice=a.b_{1}b_{2}b_{3}b_{4}b_{5}...b_{n}其中a代表整数部分,b_{1..n}\epsilon[0,1]

那么2^{a.b_{1}b_{2}b_{3}b_{4}b_{5}...b_{n}}=sqrtPrice 

所以2^{0.b_{1}b_{2}b_{3}b_{4}b_{5}...b_{n}}=sqrtPrice/2^{a}=sqrtPrice>>a=r
我们知道msb就是对数的整数部分,然而这里的msb和a并不等价,前面我们说到小数和整数的分界点在第127位和第128位之间,

所以当msb>=128,则a=msb-127,

此时r = price >> (msb - 127)

当msb<128,则a=0,并且后面连续若干个b也为0,具体多少个b为0,取决于msb和127中间的差值,假设msb=125,则

2^{0.00b_{1}b_{2}b_{3}...b_{n}}=sqrtPrice

这时为了方便后面的计算我们需要做一些调整,使

r=2^{0.b_{1}b_{2}b_{3}...b_{n}}=sqrtPrice*2^{127-msb}=sqrtPrice<<(127-msb)

if (msb >= 128) r = price >> (msb - 127);else r = price << (127 - msb);

故而这两行代码的意义在于计算sqrtPrice排除掉msb部分后的值r

int256 log_2 = (int256(msb) - 128) << 64;

接下来我们要学习一下逐位逼近法,后面的一大段代码都是基于这个算法来的。

    assembly ("memory-safe") {r := shr(127, mul(r, r))let f := shr(128, r)log_2 := or(log_2, shl(63, f))r := shr(f, r)}

根据逐位逼近法的思想要计算\log_{2}r=0.b_{1}b_{2}b_{3}b_{4}b_{5}...b_{n}

第一步计算r^{2},前面的推导可以看出sqrtPrice在去除掉msb部分后是一个127位的数(从0开始算),其小于2^{128},所以r^{2}的位数为255位,我们取最高位也就是255位的值 f 作为 b_{1} 的值,假设此时f=1,

也就是\log_{2}r=1.b_{2}b_{3}b_{4}b_{5}...b_{n}

2^{1.b_{2}b_{3}b_{4}b_{5}...b_{n}}=r,我们需要将1.b_{2}b_{3}b_{4}b_{5}...b_{n}的整数部分化掉,所以两边同时除以2,相当于2^{0.b_{2}b_{3}b_{4}b_{5}...b_{n}}=r>>1

这就是r := shr(f, r)的作用,如果计算出f=0,则2^{0.b_{2}b_{3}b_{4}b_{5}...b_{n}}=r>>0,一定要仔细阅读逐位逼近法,否则很难理解!

整数部分和小数部分都计算出得到\log_{2}sqrtPrice,我们的目的是计算是计算\log_{\sqrt{1.0001}}sqrtPrice,根据对数换底公式

\log_{\sqrt{1.0001}}sqrtPrice=\log_{2}sqrtPrice*\frac{1}{​{\log_{2}\sqrt{1.0001}}}

在计算小数部分之前,\log_{2}sqrtPrice的结果log_2已经被定义成了一个 Q64.64 格式的数

int256 log_2 = (int256(msb) - 128) << 64;

我们知道在进行乘法运算之前先要进行小数点对齐,所以\frac{1}{​{\log_{2}\sqrt{1.0001}}}要先转换成一个小数点部分有64位的数字:

\frac{1}{​{\log_{2}\sqrt{1.0001}}}\approx 13863.92,

13863.92*2^{64}\approx 255738958999603826347141

两个64位小数的二进制数相乘会得到一个128位小数的数字,将128位的小数去除掉就是真正的tick值

 // Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x)
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);// Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE  is changed, this may need to be changed too
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

误差补偿

由于直接计算\log_{\sqrt{1.0001}}sqrtPrice的成本过高,整个计算过程是通过\log_{2}sqrtPrice计算的,中间有好几步近似转换,这期间必然会产生误差,所以在方法的背后还需要做一些误差补偿。

TickMath这个文件里面还有一个方法getSqrtPriceAtTick,通过tick计算sqrtPrice,我们把每个tick都通过getSqrtPriceAtTick方法计算出sqrtPrice,再通过getTickAtSqrtPrice方法计算出tick,这里记为tick1,我们统计每一个tick和tick1之间的误差,最终统计出log_sqrt10001 的上限误差和下限误差。这就是最后两个魔法数的来历。

getSqrtPriceAtTick的方法解析看这里。

tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;

如果在考虑上限误差和下限误差后再去除掉小数点部分tickLow == tickHi则罢了。否则通过getSqrtPriceAtTick方法做一次矫正。

相关文章:

  • C语言教程(十八):C 语言共用体详解
  • 【原创】从s3桶将对象导入ES建立索引,以便快速查找文件
  • JavaScript-基础语法
  • [Spring] Seata详解
  • 数据要素如何驱动的新质IDC一体化运营体系发展?
  • 考研系列-计算机组成原理第七章、输入/输出系统
  • 项目上线流程梳理(Linux宝塔面板)
  • css网格布局Grid
  • 夜莺 v8.0.0-beta.10 部署
  • QT—布局管理器之BoxLayout篇
  • 解锁健康密码:养生的多维智慧
  • Python 正则表达式 re 包
  • 考研408-计算机组成原理冲刺考点(1-3章)
  • 使用 Vue3 + Webpack 和 Vue3 + Vite 实现微前端架构(基于 Qiankun)
  • BoxMOT:Yolov8+多目标跟踪方案_笔记1
  • 网络安全漏洞库科普手册
  • 实验研究 | 千眼狼高速摄像机驱动精密制造创新
  • 4G FS800DTU上传图像至巴法云
  • 发那科机器人(基本操作、坐标系、I/O通信)
  • STM32 ADC模数转换器
  • 费高云调研党的建设工作:营造风清气正劲足的政治生态
  • TAE联手加州大学开发出新型核聚变装置:功率提升百倍,成本减半
  • 工信部:加快自动驾驶系统安全要求强制性国家标准研制
  • 第1现场|无军用物资!伊朗港口爆炸已遇难40人伤1200人
  • “中国游”带火“中国购”,“即买即退”让外国游客购物更丝滑
  • 这场迪图瓦纪念拉威尔的音乐会,必将成为乐迷反复品味的回忆