Skip to content

Commit

Permalink
Threshold check (#229)
Browse files Browse the repository at this point in the history
* fix: threshold check in SD59x18 exp2 function

* refactor: alphabetical ordering

docs: improve writing
refactor: change order of checks
style: fix formatting issues
test: order by value
test: use MIN_SD59x18

* fix: Update threshold check in SD59x18 exp function, add EXP_MIN_THRESHOLD constant and add tests

* fix: increase max_test_rejects to 100_000

* docs: improve explanation

---------

Co-authored-by: Paul Razvan Berg <[email protected]>
  • Loading branch information
0x2me and PaulRBerg authored May 6, 2024
1 parent 16419e5 commit cceb7f6
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 14 deletions.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
test = "test"

[profile.ci]
fuzz = { runs = 10_000 }
fuzz = { runs = 10_000, max_test_rejects = 100_000 }
verbosity = 4

[fmt]
Expand Down
8 changes: 8 additions & 0 deletions src/sd59x18/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ SD59x18 constant E = SD59x18.wrap(2_718281828459045235);
int256 constant uEXP_MAX_INPUT = 133_084258667509499440;
SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT);

/// @dev Any value less than this returns 0 in {exp}.
int256 constant uEXP_MIN_THRESHOLD = -41_446531673892822322;
SD59x18 constant EXP_MIN_THRESHOLD = SD59x18.wrap(uEXP_MIN_THRESHOLD);

/// @dev The maximum input permitted in {exp2}.
int256 constant uEXP2_MAX_INPUT = 192e18 - 1;
SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT);

/// @dev Any value less than this returns 0 in {exp2}.
int256 constant uEXP2_MIN_THRESHOLD = -59_794705707972522261;
SD59x18 constant EXP2_MIN_THRESHOLD = SD59x18.wrap(uEXP2_MIN_THRESHOLD);

/// @dev Half the UNIT number.
int256 constant uHALF_UNIT = 0.5e18;
SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT);
Expand Down
12 changes: 10 additions & 2 deletions src/sd59x18/Math.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import "./Errors.sol" as Errors;
import {
uEXP_MAX_INPUT,
uEXP2_MAX_INPUT,
uEXP_MIN_THRESHOLD,
uEXP2_MIN_THRESHOLD,
uHALF_UNIT,
uLOG2_10,
uLOG2_E,
Expand Down Expand Up @@ -168,6 +170,12 @@ function div(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
function exp(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();

// Any input less than the threshold returns zero.
// This check also prevents an overflow for very small numbers.
if (xInt < uEXP_MIN_THRESHOLD) {
return ZERO;
}

// This check prevents values greater than 192e18 from being passed to {exp2}.
if (xInt > uEXP_MAX_INPUT) {
revert Errors.PRBMath_SD59x18_Exp_InputTooBig(x);
Expand Down Expand Up @@ -201,8 +209,8 @@ function exp(SD59x18 x) pure returns (SD59x18 result) {
function exp2(SD59x18 x) pure returns (SD59x18 result) {
int256 xInt = x.unwrap();
if (xInt < 0) {
// The inverse of any number less than this is truncated to zero.
if (xInt < -59_794705707972522261) {
// The inverse of any number less than the threshold is truncated to zero.
if (xInt < uEXP2_MIN_THRESHOLD) {
return ZERO;
}

Expand Down
10 changes: 5 additions & 5 deletions test/unit/sd59x18/math/exp/exp.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
pragma solidity >=0.8.19 <0.9.0;

import { sd } from "src/sd59x18/Casting.sol";
import { E, EXP_MAX_INPUT, MIN_SD59x18, MIN_WHOLE_SD59x18, PI, UNIT, ZERO } from "src/sd59x18/Constants.sol";
import { E, EXP_MAX_INPUT, EXP_MIN_THRESHOLD, MIN_SD59x18, MIN_WHOLE_SD59x18, PI, UNIT, ZERO } from "src/sd59x18/Constants.sol";
import { PRBMath_SD59x18_Exp_InputTooBig } from "src/sd59x18/Errors.sol";
import { exp } from "src/sd59x18/Math.sol";
import { SD59x18 } from "src/sd59x18/ValueType.sol";

import { SD59x18_Unit_Test } from "../../SD59x18.t.sol";

contract Exp_Unit_Test is SD59x18_Unit_Test {
SD59x18 internal constant THRESHOLD = SD59x18.wrap(-41_446531673892822322);

function test_Exp_Zero() external pure {
SD59x18 x = ZERO;
SD59x18 actual = exp(x);
Expand All @@ -27,7 +25,7 @@ contract Exp_Unit_Test is SD59x18_Unit_Test {
delete sets;
sets.push(set({ x: MIN_SD59x18 }));
sets.push(set({ x: MIN_WHOLE_SD59x18 }));
sets.push(set({ x: THRESHOLD - sd(1) }));
sets.push(set({ x: EXP_MIN_THRESHOLD - sd(1) }));
return sets;
}

Expand All @@ -38,7 +36,9 @@ contract Exp_Unit_Test is SD59x18_Unit_Test {

function negativeAndGteThreshold_Sets() internal returns (Set[] memory) {
delete sets;
sets.push(set({ x: THRESHOLD, expected: 0.000000000000000001e18 }));
sets.push(set({ x: MIN_SD59x18, expected: 0 }));
sets.push(set({ x: EXP_MIN_THRESHOLD - sd(1), expected: 0 }));
sets.push(set({ x: EXP_MIN_THRESHOLD, expected: 0.000000000000000001e18 }));
sets.push(set({ x: -33.333333e18, expected: 0.000000000000003338e18 }));
sets.push(set({ x: -20.82e18, expected: 0.0000000009077973e18 }));
sets.push(set({ x: -16e18, expected: 0.000000112535174719e18 }));
Expand Down
11 changes: 5 additions & 6 deletions test/unit/sd59x18/math/exp2/exp2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
pragma solidity >=0.8.19 <0.9.0;

import { sd } from "src/sd59x18/Casting.sol";
import { E, EXP2_MAX_INPUT, MIN_SD59x18, MIN_WHOLE_SD59x18, PI, UNIT, ZERO } from "src/sd59x18/Constants.sol";
import { E, EXP2_MAX_INPUT, EXP2_MIN_THRESHOLD, MIN_SD59x18, MIN_WHOLE_SD59x18, PI, UNIT, ZERO } from "src/sd59x18/Constants.sol";
import { PRBMath_SD59x18_Exp2_InputTooBig } from "src/sd59x18/Errors.sol";
import { exp2 } from "src/sd59x18/Math.sol";
import { SD59x18 } from "src/sd59x18/ValueType.sol";

import { SD59x18_Unit_Test } from "../../SD59x18.t.sol";

contract Exp2_Unit_Test is SD59x18_Unit_Test {
/// @dev Any input smaller than this makes the result zero.
SD59x18 internal constant THRESHOLD = SD59x18.wrap(-59_794705707972522261);

function test_Exp2_Zero() external pure {
SD59x18 x = ZERO;
SD59x18 actual = exp2(x);
Expand All @@ -28,7 +25,7 @@ contract Exp2_Unit_Test is SD59x18_Unit_Test {
delete sets;
sets.push(set({ x: MIN_SD59x18, expected: 0 }));
sets.push(set({ x: MIN_WHOLE_SD59x18, expected: 0 }));
sets.push(set({ x: THRESHOLD - sd(1), expected: 0 }));
sets.push(set({ x: EXP2_MIN_THRESHOLD - sd(1), expected: 0 }));
return sets;
}

Expand All @@ -39,7 +36,9 @@ contract Exp2_Unit_Test is SD59x18_Unit_Test {

function negativeAndGteMinPermitted_Sets() internal returns (Set[] memory) {
delete sets;
sets.push(set({ x: THRESHOLD, expected: 0.000000000000000001e18 }));
sets.push(set({ x: MIN_SD59x18, expected: 0 }));
sets.push(set({ x: EXP2_MIN_THRESHOLD - sd(1), expected: 0 }));
sets.push(set({ x: EXP2_MIN_THRESHOLD, expected: 0.000000000000000001e18 }));
sets.push(set({ x: -33.333333e18, expected: 0.000000000092398923e18 }));
sets.push(set({ x: -20.82e18, expected: 0.000000540201132438e18 }));
sets.push(set({ x: -16e18, expected: 0.0000152587890625e18 }));
Expand Down

0 comments on commit cceb7f6

Please sign in to comment.