Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expand domain of "pow" #182

Merged
merged 1 commit into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 37 additions & 8 deletions src/ud60x18/Math.sol
Original file line number Diff line number Diff line change
Expand Up @@ -432,12 +432,22 @@ function mul(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {

/// @notice Raises x to the power of y.
///
/// @dev Uses the following formula:
/// @dev For $1 \leq x \leq \infty$, the following standard formula is used:
///
/// $$
/// x^y = 2^{log_2{x} * y}
/// $$
///
/// For $0 \leq x < 1$, the following equivalent formula is used:
///
/// $$
/// i = \frac{1}{x}
/// w = 2^{log_2{i} * y}
/// x^y = \frac{1}{w}
/// $$
///
/// This is because the unsigned {log2} is undefined for values less than 1.
///
/// Requirements:
/// - All from {exp2}, {log2} and {mul}.
///
Expand All @@ -453,14 +463,33 @@ function pow(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
uint256 xUint = x.unwrap();
uint256 yUint = y.unwrap();

// If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
if (xUint == 0) {
result = yUint == 0 ? UNIT : ZERO;
} else {
if (yUint == uUNIT) {
result = x;
} else {
result = exp2(mul(log2(x), y));
}
return yUint == 0 ? UNIT : ZERO;
}
// If x is `UNIT`, the result is always `UNIT`.
else if (xUint == uUNIT) {
return UNIT;
}

// If y is zero, the result is always `UNIT`.
if (yUint == 0) {
return UNIT;
}
// If y is `UNIT`, the result is always x.
else if (yUint == uUNIT) {
return x;
}

// If x is greater than `UNIT`, use the standard formula.
if (xUint > uUNIT) {
result = exp2(mul(log2(x), y));
}
// Conversely, if x is less than `UNIT`, use the equivalent formula.
else {
UD60x18 i = wrap(uUNIT_SQUARED / xUint);
UD60x18 w = exp2(mul(log2(i), y));
result = wrap(uUNIT_SQUARED / w.unwrap());
}
}

Expand Down
153 changes: 70 additions & 83 deletions test/ud60x18/math/pow/pow.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,119 +2,75 @@
pragma solidity >=0.8.19 <0.9.0;

import { ud } from "src/ud60x18/Casting.sol";
import { E, PI, ZERO } from "src/ud60x18/Constants.sol";
import { PRBMath_UD60x18_Exp2_InputTooBig, PRBMath_UD60x18_Log_InputTooSmall } from "src/ud60x18/Errors.sol";
import { E, PI, UNIT, ZERO } from "src/ud60x18/Constants.sol";
import { pow } from "src/ud60x18/Math.sol";
import { UD60x18 } from "src/ud60x18/ValueType.sol";

import { UD60x18_Test } from "../../UD60x18.t.sol";

contract PowTest is UD60x18_Test {
UD60x18 internal constant MAX_PERMITTED = UD60x18.wrap(2 ** 192 * 10 ** 18 - 1);
contract Pow_Test is UD60x18_Test {
modifier baseZero() {
_;
}

function test_Pow_BaseAndExponentZero() external {
function test_Pow_ExponentZero() external baseZero {
UD60x18 x = ZERO;
UD60x18 y = ZERO;
UD60x18 actual = pow(x, y);
UD60x18 expected = ud(1e18);
UD60x18 expected = UNIT;
assertEq(actual, expected);
}

function baseZeroExponentNotZero_Sets() internal returns (Set[] memory) {
delete sets;
sets.push(set({ x: 0, y: 1e18, expected: 0 }));
sets.push(set({ x: 0, y: E, expected: 0 }));
sets.push(set({ x: 0, y: PI, expected: 0 }));
return sets;
}

function test_Pow_BaseZeroExponentNotZero() external parameterizedTest(baseZeroExponentNotZero_Sets()) {
UD60x18 actual = pow(s.x, s.y);
assertEq(actual, s.expected);
function testFuzz_Pow_ExponentNotZero(UD60x18 y) external baseZero {
vm.assume(y != ZERO);
UD60x18 x = ZERO;
UD60x18 actual = pow(x, y);
UD60x18 expected = ZERO;
assertEq(actual, expected);
}

modifier whenBaseNotZero() {
_;
}

function test_RevertWhen_BaseLessThanOne() external whenBaseNotZero {
UD60x18 x = ud(1e18 - 1);
UD60x18 y = PI;
vm.expectRevert(abi.encodeWithSelector(PRBMath_UD60x18_Log_InputTooSmall.selector, x));
pow(x, y);
function testFuzz_Pow_BaseUnit(UD60x18 y) external whenBaseNotZero {
UD60x18 x = UNIT;
UD60x18 actual = pow(x, y);
UD60x18 expected = UNIT;
assertEq(actual, expected);
}

modifier whenBaseGreaterThanOrEqualToOne() {
modifier whenBaseNotUnit() {
_;
}

function exponentZero_Sets() internal returns (Set[] memory) {
delete sets;
sets.push(set({ x: 1e18, y: 0, expected: 1e18 }));
sets.push(set({ x: E, y: 0, expected: 1e18 }));
sets.push(set({ x: PI, y: 0, expected: 1e18 }));
return sets;
}

function test_Pow_ExponentZero()
external
parameterizedTest(exponentZero_Sets())
whenBaseNotZero
whenBaseGreaterThanOrEqualToOne
{
UD60x18 actual = pow(s.x, s.y);
assertEq(actual, s.expected);
function testFuzz_Pow_ExponentZero(UD60x18 x) external whenBaseNotZero whenBaseNotUnit {
vm.assume(x != ZERO && x != UNIT);
UD60x18 y = ZERO;
UD60x18 actual = pow(x, y);
UD60x18 expected = UNIT;
assertEq(actual, expected);
}

modifier whenExponentNotZero() {
_;
}

function exponentOne_Sets() internal returns (Set[] memory) {
delete sets;
sets.push(set({ x: 1e18, y: 1e18, expected: 1e18 }));
sets.push(set({ x: E, y: 1e18, expected: E }));
sets.push(set({ x: PI, y: 1e18, expected: PI }));
return sets;
}

function test_Pow_ExponentOne()
external
parameterizedTest(exponentOne_Sets())
whenBaseNotZero
whenBaseGreaterThanOrEqualToOne
whenExponentNotZero
whenExponentLessThanOrEqualToMaxPermitted
{
UD60x18 actual = pow(s.x, s.y);
assertEq(actual, s.expected);
}

modifier whenExponentNotOne() {
_;
}

function test_RevertWhen_ExponentGreaterThanOrEqualToMaxPermitted()
external
whenBaseNotZero
whenBaseGreaterThanOrEqualToOne
whenExponentNotZero
whenExponentNotOne
{
UD60x18 x = MAX_PERMITTED.add(ud(1));
UD60x18 y = ud(1e18 + 1);
vm.expectRevert(abi.encodeWithSelector(PRBMath_UD60x18_Exp2_InputTooBig.selector, ud(192e18 + 192)));
pow(x, y);
function testFuzz_Pow_ExponentUnit(UD60x18 x) external whenBaseNotZero whenBaseNotUnit whenExponentNotZero {
vm.assume(x != ZERO && x != UNIT);
UD60x18 y = UNIT;
UD60x18 actual = pow(x, y);
UD60x18 expected = x;
assertEq(actual, expected);
}

modifier whenExponentLessThanOrEqualToMaxPermitted() {
modifier whenExponentNotUnit() {
_;
}

function pow_Sets() internal returns (Set[] memory) {
function baseGreaterThanUnit_Sets() internal returns (Set[] memory) {
delete sets;
sets.push(set({ x: 1e18, y: 2e18, expected: 1e18 }));
sets.push(set({ x: 1e18, y: PI, expected: 1e18 }));
sets.push(set({ x: 1e18 + 1, y: 2e18, expected: 1e18 }));
sets.push(set({ x: 2e18, y: 1.5e18, expected: 2_828427124746190097 }));
sets.push(set({ x: E, y: 1.66976e18, expected: 5_310893029888037560 }));
sets.push(set({ x: E, y: E, expected: 15_154262241479263793 }));
Expand All @@ -131,17 +87,48 @@ contract PowTest is UD60x18_Test {
expected: 340282366920938487757736552507248225013_000000000004316573
})
);
sets.push(set({ x: MAX_PERMITTED, y: 1e18 - 1, expected: 6277101735386679823624773486129835356722228023657461399187e18 }));
sets.push(
set({ x: 2 ** 192 * 1e18 - 1, y: 1e18 - 1, expected: 6277101735386679823624773486129835356722228023657461399187e18 })
);
return sets;
}

function test_Pow_BaseGreaterThanUnit()
external
parameterizedTest(baseGreaterThanUnit_Sets())
whenBaseNotZero
whenBaseNotUnit
whenExponentNotZero
whenExponentNotUnit
{
UD60x18 actual = pow(s.x, s.y);
assertEq(actual, s.expected);
}

function baseLessThanUnit_Sets() internal returns (Set[] memory) {
delete sets;
sets.push(set({ x: 0.000000000000000001e18, y: 1.78e18, expected: 0 }));
sets.push(set({ x: 0.01e18, y: E, expected: 0.000003659622955309e18 }));
sets.push(set({ x: 0.125e18, y: PI, expected: 0.001454987061394186e18 }));
sets.push(set({ x: 0.25e18, y: 3e18, expected: 0.015625e18 }));
sets.push(set({ x: 0.45e18, y: 2.2e18, expected: 0.172610627076774731e18 }));
sets.push(set({ x: 0.5e18, y: 0.481e18, expected: 0.716480825186549911e18 }));
sets.push(set({ x: 0.6e18, y: 0.95e18, expected: 0.615522152723696171e18 }));
sets.push(set({ x: 0.7e18, y: 3.1e18, expected: 0.330981655626097448e18 }));
sets.push(set({ x: 0.75e18, y: 4e18, expected: 0.316406250000000008e18 }));
sets.push(set({ x: 0.8e18, y: 5e18, expected: 0.327680000000000015e18 }));
sets.push(set({ x: 0.9e18, y: 2.5e18, expected: 0.768433471420916194e18 }));
sets.push(set({ x: 0.999999999999999999e18, y: 0.08e18, expected: UNIT }));
return sets;
}

function test_Pow()
function test_Pow_BaseLessThanUnit()
external
parameterizedTest(pow_Sets())
parameterizedTest(baseLessThanUnit_Sets())
whenBaseNotZero
whenBaseNotUnit
whenExponentNotZero
whenExponentNotOne
whenExponentLessThanOrEqualToMaxPermitted
whenExponentNotUnit
{
UD60x18 actual = pow(s.x, s.y);
assertEq(actual, s.expected);
Expand Down
27 changes: 15 additions & 12 deletions test/ud60x18/math/pow/pow.tree
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
pow.t.sol
├── when the base is zero
│ ├── when the exponent is zero
│ │ └── it should return one
│ │ └── it should return the unit number
│ └── when the exponent is not zero
│ └── it should return zero
└── when the base is not zero
├── when the base is less than one
│ └── it should revert
└── when the base is greater than or equal to one
├── when the base is the unit number
│ └── it should return the unit number
└── when the base is not the unit number
├── when the exponent is zero
│ └── it should return one
│ └── it should return the base
└── when the exponent is not zero
├── when the exponent is one
│ └── it should return the base
└── when the exponent is not one
├── when the exponent is greater than the maximum permitted
│ └── it should revert
└── when the exponent is less than or equal to the maximum permitted
└── it should return the correct value
├── when the exponent is the unit number
│ └── it should return the base
└── when the exponent is not the unit number
├── when the base is greater than the maximum permitted
│ └── it should revert
└── when the base is less than or equal to the maximum permitted
├── when the base is greater than the unit number
│ └── it should use the standard formula and return the correct value
└── when the base is less than the unit number
└── it should use the equivalent formula and return the correct value