// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

library FullMath {
    function fullMul(uint256 x, uint256 y) private pure returns (uint256 l, uint256 h) {
        unchecked {
            uint256 mm = mulmod(x, y, type(uint256).max);
            l = x * y;
            h = mm - l;
            if (mm < l) h -= 1;
        }
    }

    function fullDiv(
        uint256 l,
        uint256 h,
        uint256 d
    ) private pure returns (uint256) {
        unchecked {
            uint256 pow2 = d & (~d + 1);
            d /= pow2;
            l /= pow2;
            l += h * ((~pow2 + 1) / pow2 + 1);
            uint256 r = 1;
            r *= 2 - d * r;
            r *= 2 - d * r;
            r *= 2 - d * r;
            r *= 2 - d * r;
            r *= 2 - d * r;
            r *= 2 - d * r;
            r *= 2 - d * r;
            r *= 2 - d * r;
            return l * r;
        }
    }

    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 d
    ) internal pure returns (uint256) {
        if (d == 0) revert("FullMath: DIVISION_BY_ZERO");

        unchecked {
            (uint256 l, uint256 h) = fullMul(x, y);
            uint256 mm = mulmod(x, y, d);

            if (mm > l) h -= 1;
            l -= mm;

            if (h == 0) return l / d;

            require(h < d, "FullMath: FULLDIV_OVERFLOW");
            return fullDiv(l, h, d);
        }
    }
}
