The goal is this: given an unsigned (64 bit) integer, return the binary logarithm of it as a Q64.64 fixed point decimal. This means the lower 64 bits represent the fractional part of the logarithm when taken as a ratio with 2^64. I only need 5, maybe 6 bits of precision here.
It's easy to use the "count leading zeroes" trick to compute the integer part. However, I'm having trouble with the fractional part.
Rust is the recommended language as it has an unsigned 128 bit integer.
When I write the following equation, in which f is the fractional part and w is the whole part (where b is easily computable):
w + f/2^64 = log2(2^w + b/2^w)
I get the following simplification, which doesn't really help:
f = 2^64 * (log2(b + 2^2w) - 2w)
Since the number we're taking the logarithm of would be getting larger, not smaller. There is no recursive base case.
So how would you compute this fractional part of the logarithm without using floating point numbers or other libraries?
This code is kind of doing what I want, that is calculating the log base 2 of in that case a fixed point number: https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/libraries/TickMath.sol#L112
I'm not sure how it works though. Is anyone able to understand?
I previously showed and explained how to compute the binary logarithm of a pure fraction (that is, 0 < a < 1) in fixed-point arithmetic. As the asker notes, the integer portion of a binary logarithm can be computed using a count-leading-zero primitive (
clz). One therefore splits the integer inputxlike so:x = a * 2**expo, and computesexpoviaclzandlog2(a)using the algorithm I demonstrated previously.I do not know Rust, so the code below was written in ISO-C99, which I hope can be translated to Rust in a straightforward manner. The
clzfunctionality is implemented manually here. Obviously one would want to avail oneself of appropriate built-ins for this where available.The specification given by asker stated that the result be returned in UQ64.64 format which seemed unusual to me, so the
ilog2()function here returns a UQ16.16 result. If a result in UQ64.64 format is definitely required, a conversion is easily accomplished by converting the result ofilog2()to a__uint128_tand performing a left shift by 48 on he result.I have included some light-weight test scaffolding to demonstrate the correct operation of the code. Using built-ins for
clzshould result in about 50 to 60 machine instructions being generated on common CPU architectures like x86-64 and ARM64 when using the latest compilers, so performance should be sufficient for various use cases (the asker did not mention specific performance goals).