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

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方法做一次矫正。

http://www.xdnf.cn/news/2837.html

相关文章:

  • 相机-IMU联合标定:IMU标定
  • 代码随想录算法训练营第六十一天 | floyd算法
  • 夜莺监控V8(Nightingale)二进制部署教程(保姆级)
  • Virtualbox虚拟机全屏后黑屏问题解决
  • Linux用户管理命令:su与useradd
  • 常用网址合集
  • 如何利用表格解决 Python 嵌套循环难题
  • SDK游戏盾、高防IP、高防CDN三者的区别与选型指南
  • 海外独立站VUE3加载优化
  • 第二届材料工程与智能制造国际学术会议
  • 【QinAgent应用案例】从开发到管理,QinAgent为某智能家居企业提效50%,降本20%
  • Airbnb更智能的搜索:嵌入式检索(Embedding-Based Retrieval,EBR)工作原理解析
  • git 如何清空当前分支的历史提交记录,仅保留最后一次提交
  • Vue3中Hooks与普通函数的区别
  • Python pip下载包及依赖到指定文件夹
  • 23.开关电源干扰控制的EMC改善措施
  • 正常流布局
  • Terraform的加密功能
  • 解决 Win11/Win10 “为了对电脑进行保护,已经阻止此应用”问题
  • Linux环境变量配置与std访问环境变量
  • 【Linux实践系列】:进程间通信:万字详解命名管道实现通信
  • 谷歌浏览器如何优化网页的视频播放体验【提升播放效果】
  • 二极管钳位电路——Multisim电路仿真
  • 数组滑动窗口单调栈单调队列trick集【leetcode hot100 c++速查!!!】
  • 遇到前后端半分离老项目的速度解决方法
  • 如何选择合适的RFID手持终端设备?
  • 【C++QT】Item Views 项目视图控件详解
  • Nginx支持HTTP2/HTTP3的并用CURL测试
  • RSYNC命令使用详解
  • JLink,程序烧写流程、步骤