uniswap getTickAtSqrtPrice 方法解析

先上代码:

javascript 复制代码
    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 - 1
            if ((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 too
            int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);

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

公式推导

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

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

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

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

以 a 为底的 b 的对数,等于以 c 为底的 b 的对数除以以 c 为底的 a 的对数。

(其中 a,b,c>0且 a,b≠1, c 是任意可选的新底数。)

总结下来就是:

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

设:,也就是说

两边同时取以c 为底的对数得到:

进一步得出

所以:

为了后续的计算可以转换成位运算,我们用2作为底数,整个计算就可以转换成:

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

我们假设,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

于是我们得出:

进一步推导得出:

所以


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

接下来看第一行代码:

javascript 复制代码
if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
    InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
}

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

javascript 复制代码
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

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


转换成Q64.96格式的整数需要先乘以,也就是,于是

MIN_SQRT_PRICE = 4295128739;

而maxSqrtPrice转换成Q64.96则是,所以

MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342

sqrtPriceX96 必然在这个范围中。

javascript 复制代码
uint256 price = uint256(sqrtPriceX96) << 32;

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

<< 32后===>[64个0][64 位整数][96 位小数][32个0]

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

对数整数部分的计算

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

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

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

这里其实有个定律,假设,那么对数的整数部分m就是最高有效位的值

比如,各位读者也可以拿其他的数字尝试一下,所以要求

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

javascript 复制代码
uint256 msb = BitMath.mostSignificantBit(r); 

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

对数小数部分的计算

假设其中a代表整数部分,

那么

所以

我们知道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,则

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

javascript 复制代码
if (msb >= 128) r = price >> (msb - 127);
            else r = price << (127 - msb);

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

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

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

javascript 复制代码
    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)
    }

根据逐位逼近法的思想要计算

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

也就是

,我们需要将的整数部分化掉,所以两边同时除以2,相当于

这就是r := shr(f, r)的作用,如果计算出f=0,则,一定要仔细阅读**逐位逼近法,**否则很难理解!

整数部分和小数部分都计算出得到,我们的目的是计算是计算,根据对数换底公式

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

javascript 复制代码
int256 log_2 = (int256(msb) - 128) << 64;

我们知道在进行乘法运算之前先要进行小数点对齐,所以要先转换成一个小数点部分有64位的数字:

,

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

javascript 复制代码
 // 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);

误差补偿

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

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

getSqrtPriceAtTick的方法解析看这里

javascript 复制代码
tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;

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

相关推荐
傻小胖6 小时前
密码学的hash函数,哈希碰撞, collision resistance, BTC用到的SHA-256简介
区块链·密码学·哈希算法
Xiaoxiaoxiao02091 天前
GAEA情感坐标背后的技术原理
人工智能·web3·区块链
这个懒人1 天前
使用 Truffle 和 Ganache 搭建本地以太坊开发环境并部署一个简单智能合约
智能合约
阿雄不会写代码1 天前
FISCO BCOS 智能合约开发详解
区块链·智能合约
OneBlock Community1 天前
什么是模块化区块链?Polkadot 架构解析
架构·区块链
拾忆-eleven1 天前
区块链:去中心化应用(DApp)开发全流程解析
去中心化·区块链
GWQ3332 天前
买币永续合约成全球交易热点,XBIT去中心化交易所平台显著提升市场流动性
去中心化·区块链
倒霉男孩2 天前
二、信息时代社会结构的转变
区块链
数据与人工智能律师2 天前
正确应对监管部门的数据安全审查
大数据·网络·数据库·人工智能·区块链