1
1
package types
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
5
- "math"
6
+ "math/big "
6
7
)
7
8
8
9
const (
9
- TICK_SIZE float64 = 1.0001 // Equivalent to 10^(-4)
10
- MAX_PRICE float64 = 2.421902e11 // Equivalent to 1.0001 ** ((2**19) - 1 - (2**18))
11
- MIN_PRICE float64 = 4.128986e-12 // Equivalent to 1.0001 ** (1 - (2**18))
12
- OFFSET float64 = 262144 // Equivalent to 2**18
13
- BILLION uint64 = 1e9 // Equivalent to 10^9
10
+ MaxTick int64 = 262143 // Equivalent to 2**18 - 1
11
+ MinTick int64 = - MaxTick // Equivalent to -2**18 + 1
12
+ Offset int64 = 262144 // Equivalent to 2**18
13
+ )
14
+
15
+ var (
16
+ priceX96AtBinaryTicks = getPricesX96AtBinaryTicks ()
17
+
18
+ maxUint192 , _ = new (big.Int ).SetString ("ffffffffffffffffffffffffffffffffffffffffffffffff" , 16 )
19
+ maxUint96 , _ = new (big.Int ).SetString ("ffffffffffffffffffffffff" , 16 )
20
+ maxUint64 , _ = new (big.Int ).SetString ("ffffffffffffffff" , 16 )
21
+ q96 , _ = new (big.Int ).SetString ("1000000000000000000000000" , 16 )
22
+ zero = new (big.Int ).SetUint64 (0 )
23
+ one = new (big.Int ).SetUint64 (1 )
24
+ billion = new (big.Int ).SetUint64 (1000000000 )
14
25
)
15
26
16
27
// ToTick converts the price to tick
17
28
func (p * Price ) ToTick () error {
18
- price , err := PriceToTick (ConvertToRealPrice ( p .Price ) )
29
+ price , err := PriceToTick (p .Price )
19
30
if err != nil {
20
31
return err
21
32
}
@@ -24,24 +35,165 @@ func (p *Price) ToTick() error {
24
35
return nil
25
36
}
26
37
27
- // ConvertToRealPrice converts the price multiplied by 1e9 to real price
28
- func ConvertToRealPrice (price uint64 ) float64 {
29
- realPrice := float64 (price ) / float64 (BILLION )
30
- return realPrice
31
- }
38
+ // TickToPrice converts the tick to price with 10^9 precision. It will return an error
39
+ // if the tick is out of range or the tick is so large that cannot be converted to uint64.
40
+ // NOTE: the result is rounded up to the nearest integer, this is aligned with the UniswapV3 calculation.
41
+ func TickToPrice (tick int64 ) (uint64 , error ) {
42
+ priceX96 , err := tickToPriceX96 (tick )
43
+ if err != nil {
44
+ return 0 , err
45
+ }
32
46
33
- // PriceToTick converts the price to tick
34
- func PriceToTick (price float64 ) (uint64 , error ) {
35
- // Check if price is less than or equal to zero to prevent NaN results
36
- if price <= 0 {
37
- return 0 , fmt .Errorf ("price must be greater than 0" )
47
+ // round up the price and convert to uint64
48
+ // we round up in the division so PriceX1E9ToTick of the output price is always consistent
49
+ // var price *big.Int
50
+ price := new (big.Int ).Div (priceX96 , q96 )
51
+ if price .Cmp (zero ) <= 0 {
52
+ return 0 , fmt .Errorf ("price out of range" )
38
53
}
39
54
40
- // For safely convert from i64 to u64 since the price is already checked
41
- // to ensure `tick` is always a positive value
42
- if price < MIN_PRICE || price > MAX_PRICE {
55
+ if new (big.Int ).Rem (priceX96 , q96 ).Cmp (zero ) > 0 {
56
+ priceNextTickX96 := new (big.Int ).Div (new (big.Int ).Mul (priceX96 , big .NewInt (10001 )), big .NewInt (10000 ))
57
+ priceNextTick := new (big.Int ).Div (priceNextTickX96 , q96 )
58
+
59
+ if priceNextTick .Cmp (price ) > 0 {
60
+ price = new (big.Int ).Add (price , one )
61
+ }
62
+ }
63
+
64
+ if price .Cmp (maxUint64 ) > 0 {
43
65
return 0 , fmt .Errorf ("price out of range" )
44
66
}
67
+ return price .Uint64 (), nil
68
+ }
69
+
70
+ // PriceToTick converts the price to tick, it will return the nearest tick that yields
71
+ // less than or equal to the given price.
72
+ // ref: https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation
73
+ func PriceToTick (price uint64 ) (uint64 , error ) {
74
+ if price == 0 {
75
+ return 0 , errors .New ("price must be greater than 0" )
76
+ }
77
+
78
+ // find the most significant bit (msb) of the log2(price);
79
+ msb := uint64 (0 )
80
+ p := price
81
+ bits := []uint64 {4294967295 , 65535 , 255 , 15 , 3 , 1 }
82
+ for i , bit := range bits {
83
+ if p > bit {
84
+ n := uint64 (1 << (len (bits ) - i - 1 ))
85
+ msb += n
86
+ p >>= n
87
+ }
88
+ }
89
+
90
+ // find the remaining r = price / 2^msb and shift significant bits to 2^31;
91
+ r := uint64 (0 )
92
+ if msb >= 32 {
93
+ r = price >> (msb - 31 )
94
+ } else {
95
+ r = price << (31 - msb )
96
+ }
97
+
98
+ // approximate log2(r) using iterations of base-2 logarithm with 16-bit precision;
99
+ log2 := int64 (msb ) << 16
100
+ for i := 0 ; i < 16 ; i ++ {
101
+ r = (r * r ) >> 31
102
+ f := r >> 32
103
+ log2 |= int64 (f ) << (15 - i )
104
+ r >>= f
105
+ }
106
+
107
+ // convert to tick value;
108
+ // tick = (log2 - log2(10^9) *2^16) * (1/log2(1.0001))/(2^16/2^32)
109
+ log1p0001 := (log2 - 1959352 ) * 454283648
110
+ tick := log1p0001 >> 32
111
+ if tick > MaxTick || tick < MinTick {
112
+ return 0 , fmt .Errorf ("tick out of range" )
113
+ }
114
+
115
+ // the result will differ by 1 tick if the price is not exactly at the tick value;
116
+ // it will return the largest tick whose price are less than or equal to the given price.
117
+ // NOTE: cannot use the previous result divided by 1.0001 as the fraction has been reduced.
118
+ expectPriceX96 := new (big.Int ).Mul (new (big.Int ).SetUint64 (price ), q96 )
119
+ for i := int64 (1 ); i >= 0 ; i -- {
120
+ t := tick + i
121
+ pX96 , err := tickToPriceX96 (t )
122
+ if err == nil && pX96 .Cmp (expectPriceX96 ) <= 0 {
123
+ return uint64 (t + Offset ), nil
124
+ }
125
+ }
126
+
127
+ return uint64 (tick - 1 + Offset ), nil
128
+ }
129
+
130
+ // mulShift multiplies two big.Int and shifts the result to the right by 96 bits.
131
+ // It returns a new big.Int object.
132
+ func mulShift (val * big.Int , mulBy * big.Int ) * big.Int {
133
+ return new (big.Int ).Rsh (new (big.Int ).Mul (val , mulBy ), 96 )
134
+ }
135
+
136
+ // tickToPriceX96 converts the tick to price in x96 (2^96) * 10^9 format.
137
+ func tickToPriceX96 (tick int64 ) (* big.Int , error ) {
138
+ if tick > MaxTick || tick < MinTick {
139
+ return nil , fmt .Errorf ("tick out of range" )
140
+ }
141
+
142
+ absTick := tick
143
+ if tick < 0 {
144
+ absTick = - tick
145
+ }
146
+
147
+ // multiply the price ratio at each binary tick
148
+ priceX96 := new (big.Int ).Set (q96 )
149
+ for i , pX96 := range priceX96AtBinaryTicks {
150
+ if absTick & (1 << uint (i )) != 0 {
151
+ priceX96 = mulShift (priceX96 , pX96 )
152
+ }
153
+ }
154
+
155
+ // inverse the price if tick is positive.
156
+ if tick > 0 {
157
+ priceX96 = new (big.Int ).Div (maxUint192 , priceX96 )
158
+ }
159
+
160
+ priceX96 = new (big.Int ).Mul (priceX96 , billion )
161
+
162
+ return priceX96 , nil
163
+ }
164
+
165
+ // getPricesX96AtBinaryTicks returns the prices at each binary tick in x96 format.
166
+ // the prices are in the term of 1.0001^-(2^i) * 2^96.
167
+ func getPricesX96AtBinaryTicks () []* big.Int {
168
+ x96Hexes := []string {
169
+ "fff97272373d413259a46990" ,
170
+ "fff2e50f5f656932ef12357c" ,
171
+ "ffe5caca7e10e4e61c3624ea" ,
172
+ "ffcb9843d60f6159c9db5883" ,
173
+ "ff973b41fa98c081472e6896" ,
174
+ "ff2ea16466c96a3843ec78b3" ,
175
+ "fe5dee046a99a2a811c461f1" ,
176
+ "fcbe86c7900a88aedcffc83b" ,
177
+ "f987a7253ac413176f2b074c" ,
178
+ "f3392b0822b70005940c7a39" ,
179
+ "e7159475a2c29b7443b29c7f" ,
180
+ "d097f3bdfd2022b8845ad8f7" ,
181
+ "a9f746462d870fdf8a65dc1f" ,
182
+ "70d869a156d2a1b890bb3df6" ,
183
+ "31be135f97d08fd981231505" ,
184
+ "9aa508b5b7a84e1c677de54" ,
185
+ "5d6af8dedb81196699c329" ,
186
+ "2216e584f5fa1ea92604" ,
187
+ }
188
+
189
+ prices := make ([]* big.Int , 0 , len (x96Hexes ))
190
+ for _ , x96Hex := range x96Hexes {
191
+ p , ok := new (big.Int ).SetString (x96Hex , 16 )
192
+ if ! ok {
193
+ panic ("failed to parse hex string" )
194
+ }
195
+ prices = append (prices , p )
196
+ }
45
197
46
- return uint64 ( math . Round ( math . Log ( price ) / math . Log ( TICK_SIZE )) + OFFSET ), nil
198
+ return prices
47
199
}
0 commit comments