diff --git a/src/utils/math/LibTanh.sol b/src/utils/math/LibTanh.sol new file mode 100644 index 0000000..672c35e --- /dev/null +++ b/src/utils/math/LibTanh.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol"; +import {SafeCastLib} from "solady/utils/SafeCastLib.sol"; +import {console2} from "forge-std/console2.sol"; + +library LibTanh { + using SafeCastLib for uint256; + using SafeCastLib for int256; + using FixedPointMathLib for int256; + using FixedPointMathLib for uint256; + + error TanhOverflow(); + + int256 private constant MAX_EXP_WAD = 135305999368893231588; + + /// @notice Computes the hyperbolic tangent of a number. + /// @param x The number to compute the hyperbolic tangent of. + /// @return The hyperbolic tangent of x. + /// @dev tanh can be computed as (exp(x) - exp(-x)) / (exp(x) + exp(-x)) + /// but we need to be careful with overflow: x must be less than 135 * WAD. + /// This function will revert if the calculation overflows. + function tanh(uint256 x) public pure returns (uint256) { + return _tanh(x, true); + } + + /// @notice Computes the hyperbolic tangent of a number, with no revert on overflow. + /// @param x The number to compute the hyperbolic tangent of. + /// @return The hyperbolic tangent of x. + /// @dev tanh can be computed as (exp(x) - exp(-x)) / (exp(x) + exp(-x)) + /// x must be less than 135 * WAD or the calculation will overflow; this function + /// will return the absolute ceiling of 1e18 in this case. + function rawTanh(uint256 x) public pure returns (uint256) { + return _tanh(x, false); + } + + function _tanh(uint256 x, bool checked) private pure returns (uint256) { + int256 xInt = x.toInt256(); + + if (xInt > MAX_EXP_WAD) { + if (checked) { + revert TanhOverflow(); + } + xInt = MAX_EXP_WAD; + } + uint256 expX = xInt.expWad().toUint256(); + uint256 invExpX = (xInt * -1).expWad().toUint256(); + + return (expX - invExpX).fullMulDiv(1e18, expX + invExpX); + } +} diff --git a/test/utils/math/LibTanh.t.sol b/test/utils/math/LibTanh.t.sol new file mode 100644 index 0000000..b1a5525 --- /dev/null +++ b/test/utils/math/LibTanh.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {Test} from "forge-std/Test.sol"; +import {console2} from "forge-std/console2.sol"; +import {LibTanh} from "../../../src/utils/math/LibTanh.sol"; + +contract LibTanhTest is Test { + using LibTanh for uint256; + + function testTanh1() public pure { + uint256 x = 1e18; + uint256 y = x.tanh(); + // 0.761594155955764888 + assertEq(y, 761594155955764888); + } + + function testTanh2() public pure { + uint256 x = 2e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 964027580075816884); + } + + function testTanh3() public pure { + uint256 x = 3e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 995054753686730451); + } + + function testTanh4() public pure { + uint256 x = 4e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 999329299739067043); + } + + function testTanh5() public pure { + uint256 x = 5e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 999909204262595131); + } + + function testTanh6() public pure { + uint256 x = 6e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 999987711650795570); + } + + function testTanh7() public pure { + uint256 x = 7e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 999998336943944671); + } + + function testTanh8() public pure { + uint256 x = 8e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 999999774929675889); + } + + function testTanh9() public pure { + uint256 x = 9e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 999999969540040974); + } + + function testTanh10() public pure { + uint256 x = 10e18; + uint256 y = LibTanh.tanh(x); + assertEq(y, 999999995877692763); + } + + function testTanhOverflow() public { + uint256 x = 136e18; + vm.expectRevert(LibTanh.TanhOverflow.selector); + LibTanh.tanh(x); + } + + function testTanhCeilOverflow() public pure { + uint256 x = 136e18; + uint256 y = LibTanh.rawTanh(x); + assertEq(y, 1e18); + } +}