Skip to content

Commit

Permalink
perf: increase the accuracy of exp and exp2
Browse files Browse the repository at this point in the history
chore: close issue #32
  • Loading branch information
PaulRBerg committed Jun 12, 2021
1 parent f9f55c6 commit 6d28ff2
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 109 deletions.
279 changes: 203 additions & 76 deletions contracts/PRBMath.sol

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions contracts/PRBMathSD59x18.sol
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ library PRBMathSD59x18 {
///
/// Requirements:
/// - All from "log2".
/// - x must be less than 88.722839111672999628.
/// - x must be less than 133.084258667509499441.
///
/// Caveats:
/// - All from "exp2".
Expand All @@ -172,8 +172,8 @@ library PRBMathSD59x18 {
return PRBMath.SD59x18({ value: 0 });
}

// Without this check, the value passed to "exp2" would be greater than 128e18.
require(x.value < 88722839111672999628);
// Without this check, the value passed to "exp2" would be greater than 192.
require(x.value < 133084258667509499441);

// Do the fixed-point multiplication inline to save gas.
unchecked {
Expand All @@ -188,7 +188,7 @@ library PRBMathSD59x18 {
/// @dev See https://ethereum.stackexchange.com/q/79903/24693.
///
/// Requirements:
/// - x must be 128e18 or less.
/// - x must be 192 or less.
/// - The result must fit within MAX_SD59x18.
///
/// Caveats:
Expand All @@ -210,15 +210,15 @@ library PRBMathSD59x18 {
result = PRBMath.SD59x18({ value: 1e36 / exp2(exponent).value });
}
} else {
// 2**128 doesn't fit within the 128.128-bit fixed-point representation.
require(x.value < 128e18);
// 2**192 doesn't fit within the 192.64-bit fixed-point representation.
require(x.value < 192e18);

unchecked {
// Convert x to the 128.128-bit fixed-point format.
uint256 x128x128 = (uint256(x.value) << 128) / uint256(SCALE);
// Convert x to the 192-64-bit fixed-point format.
uint256 x192x64 = (uint256(x.value) << 64) / uint256(SCALE);

// Safe to convert the result to int256 directly because the maximum input allowed is 128e18.
result = PRBMath.SD59x18({ value: int256(PRBMath.exp2(x128x128)) });
// Safe to convert the result to int256 directly because the maximum input allowed is 192.
result = PRBMath.SD59x18({ value: int256(PRBMath.exp2(x192x64)) });
}
}
}
Expand Down
19 changes: 10 additions & 9 deletions contracts/PRBMathUD60x18.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: WTFPL
pragma solidity >=0.8.0;

import "hardhat/console.sol";
import "./PRBMath.sol";

/// @title PRBMathUD60x18
Expand Down Expand Up @@ -113,8 +114,8 @@ library PRBMathUD60x18 {
/// @param x The exponent as an unsigned 60.18-decimal fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
function exp(PRBMath.UD60x18 memory x) internal pure returns (PRBMath.UD60x18 memory result) {
// Without this check, the value passed to "exp2" would be greater than 128e18.
require(x.value < 88722839111672999628);
// Without this check, the value passed to "exp2" would be greater than 192.
require(x.value < 133084258667509499441);

// Do the fixed-point multiplication inline to save gas.
unchecked {
Expand All @@ -129,21 +130,21 @@ library PRBMathUD60x18 {
/// @dev See https://ethereum.stackexchange.com/q/79903/24693.
///
/// Requirements:
/// - x must be 128e18 or less.
/// - x must be 192 or less.
/// - The result must fit within MAX_UD60x18.
///
/// @param x The exponent as an unsigned 60.18-decimal fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
function exp2(PRBMath.UD60x18 memory x) internal pure returns (PRBMath.UD60x18 memory result) {
// 2**128 doesn't fit within the 128.128-bit format used internally in this function.
require(x.value < 128e18);
// 2**192 doesn't fit within the 192.64-bit format used internally in this function.
require(x.value < 192e18);

unchecked {
// Convert x to the 128.128-bit fixed-point format.
uint256 x128x128 = (x.value << 128) / SCALE;
// Convert x to the 192.64-bit fixed-point format.
uint256 x192x64 = (x.value << 64) / SCALE;

// Pass x to the PRBMath.exp2 function, which uses the 128.128-bit fixed-point number representation.
result = PRBMath.UD60x18({ value: PRBMath.exp2(x128x128) });
// Pass x to the PRBMath.exp2 function, which uses the 192.64-bit fixed-point number representation.
result = PRBMath.UD60x18({ value: PRBMath.exp2(x192x64) });
}
}

Expand Down
7 changes: 4 additions & 3 deletions test/prbMathSd59x18/pure/exp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ export default function shouldBehaveLikeExp(): void {
});

context("when x is positive", function () {
context("when x is greater than or equal to 88.722839111672999628", function () {
const testSets = [fp("88.722839111672999628"), MAX_WHOLE_SD59x18, MAX_SD59x18];
context("when x is greater than or equal to 133.084258667509499440", function () {
const testSets = [fp("133.084258667509499441"), MAX_WHOLE_SD59x18, MAX_SD59x18];

forEach(testSets).it("takes %e and reverts", async function (x: BigNumber) {
await expect(this.contracts.prbMathSd59x18.doExp(x)).to.be.reverted;
});
});

context("when x is less than 88.722839111672999628", function () {
context("when x is less than or equal to 133.084258667509499440", function () {
const testSets = [
["1e-18"],
["1e-15"],
Expand All @@ -77,6 +77,7 @@ export default function shouldBehaveLikeExp(): void {
["64"],
["71.002"],
["88.722839111672999627"],
["133.084258667509499440"],
];

forEach(testSets).it("takes %e and returns the correct value", async function (x: string) {
Expand Down
10 changes: 6 additions & 4 deletions test/prbMathSd59x18/pure/exp2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,19 @@ export default function shouldBehaveLikeExp2(): void {
});

context("when x is positive", function () {
context("when x is greater than or equal to 128", function () {
const testSets = [fp("128"), fp(MAX_WHOLE_SD59x18), fp(MAX_SD59x18)];
context("when x is greater than or equal to 192", function () {
const testSets = [fp("192"), fp(MAX_WHOLE_SD59x18), fp(MAX_SD59x18)];

forEach(testSets).it("takes %e and reverts", async function (x: BigNumber) {
await expect(this.contracts.prbMathSd59x18.doExp2(x)).to.be.reverted;
});
});

context("when x is less than 128", function () {
context("when x is less than 192", function () {
const testSets = [
["1e-18"],
["1e-15"],
["0.3212"],
["1"],
["2"],
[E],
Expand All @@ -79,7 +80,8 @@ export default function shouldBehaveLikeExp2(): void {
["88.7494"],
["95"],
["127"],
["127.999999999999999999"],
["152.9065"],
["191.999999999999999999"],
];

forEach(testSets).it("takes %e and returns the correct value", async function (x: string) {
Expand Down
7 changes: 4 additions & 3 deletions test/prbMathUd60x18/pure/exp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ export default function shouldBehaveLikeExp(): void {
});

context("when x is positive", function () {
context("when x is greater than or equal to 88.722839111672999628", function () {
const testSets = [fp("88.722839111672999628"), fp(MAX_WHOLE_UD60x18), fp(MAX_UD60x18)];
context("when x is greater than or equal to 133.084258667509499440", function () {
const testSets = [fp("133.084258667509499441"), fp(MAX_WHOLE_UD60x18), fp(MAX_UD60x18)];

forEach(testSets).it("takes %e and reverts", async function (x: BigNumber) {
await expect(this.contracts.prbMathUd60x18.doExp(x)).to.be.reverted;
});
});

context("when x is less than 88.722839111672999628", function () {
context("when x is less than or equal to 133.084258667509499440", function () {
const testSets = [
["1e-18"],
["1e-15"],
Expand All @@ -42,6 +42,7 @@ export default function shouldBehaveLikeExp(): void {
["64"],
["71.002"],
["88.722839111672999627"],
["133.084258667509499440"],
];

forEach(testSets).it("takes %e and returns the correct value", async function (x: string) {
Expand Down
10 changes: 6 additions & 4 deletions test/prbMathUd60x18/pure/exp2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ export default function shouldBehaveLikeExp2(): void {
});

context("when x is positive", function () {
context("when x is greater than or equal to 128", function () {
const testSets = [fp("128"), fp(MAX_WHOLE_UD60x18), fp(MAX_UD60x18)];
context("when x is greater than or equal to 192", function () {
const testSets = [fp("192"), fp(MAX_WHOLE_UD60x18), fp(MAX_UD60x18)];

forEach(testSets).it("takes %e and reverts", async function (x: BigNumber) {
await expect(this.contracts.prbMathUd60x18.doExp2(x)).to.be.reverted;
});
});

context("when x is less than 128", function () {
context("when x is less than 192", function () {
const testSets = [
["1e-18"],
["1e-15"],
["0.3212"],
["1"],
["2"],
[E],
Expand All @@ -44,7 +45,8 @@ export default function shouldBehaveLikeExp2(): void {
["88.7494"],
["95"],
["127"],
["127.999999999999999999"],
["152.9065"],
["191.999999999999999999"],
];

forEach(testSets).it("takes %e and returns the correct value", async function (x: string) {
Expand Down

0 comments on commit 6d28ff2

Please sign in to comment.