128.128 Solidity 中的无符号定点除法

128.128 Unsigned fixed point Division in Solidity

我目前正在尝试找出一种对两个 128.128 uint256 数字执行定点除法的具体方法。这似乎是一件相当简单的事情,但一直无法编写解决方案。

对于两个 64.64 定点数,以下工作正常。

function div64x64 (uint128 x, uint128 y) internal pure returns (uint128) {
    unchecked {
        require (y != 0);
    
        uint256 answer = (uint256 (x) << 64) / y;

        require (answer <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        return uint128 (answer);
    }
}

但相同的逻辑不适用于 uint256 128.128 定点数,因为您不能将 x 转换为更大的 uint 类型以左移 x。这是我为 128.128 解决此问题的遗憾尝试,它在输出中不包含正确的小数,但我的尝试确实包含了小数点左边的正确值。

function div128x128 (uint256 x, uint256 y) internal pure returns (uint256) {
    unchecked {
        require (y != 0);

        uint256 xInt = x>>128;
        uint256 xDecimal = x<<128;
        uint256 yInt = y>>128;
        uint256 yDecimal = y<<128;

        uint256 hi = ((uint256(xInt) << 64)/yInt)<<64;
        uint256 lo = ((uint256(xDecimal)<<64)/yDecimal);
        
        require (hi+lo <= MAX_128x128);
        return hi+lo;
    }
}

有谁知道完成此操作的最佳方法,或者只是对如何操作的概念性解释将不胜感激。提前致谢!

好的,我会 post 在这里为下一个人提供解决方案。这里的关键是一个更明显的事实,即您可以将具有公分母的分数分解为两个加法部分。例如 12.525/9.5= (12/9.5)+(.525/9.5) 考虑到这一点,我们有一种方法可以将我们的数字分解为 2 个 uint256 数字,然后通过一些花哨的移位将它们连接起来。

    function div128x128 (uint256 x, uint256 y) internal pure returns (uint256) {
        unchecked {
            //Require denominator != 0
            require (y != 0);
            // xDec = x & 2**128-1 i.e 128 precision 128 bits of padding on the left
            uint256 xDec = x & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
             //xInt x *2**-128 i.e. 128 precision 128 bits of padding on the right
            uint256 xInt = x >> 128;
            //hi = xInt*2**256-1 /y ==> leave a full uint256 of bits to store the integer representation of the fractional decimal with 128.128 precision
            uint256 hi = xInt*(MAX_128x128/y);
            //xDec*2**256-1 /y ==> leave full uint256 of bits to store the integer representation of fractional decimal with 128.128 precision, right shift 128 bits since output should be the right 128 bits of precision on the output
            uint256 lo = (xDec*(MAX_128x128/y))>>128;
            /*Example: 12.525/9.5 := 12/9.5 + .525/9.5<-- legal to break up a fraction into additive pieces with common deniminator in the example above just padding to fit 128.128 output in a uint256
*/
            require (hi+lo <= MAX_128x128);
            return hi+lo;
        }
    }

这是一个解决方案,只要满足要求标准,它似乎就可以工作。几乎毫无疑问,需要进行优化改进。不过我在一些真实数据上测试过,好像准确到128.128精度。