forked from OpenZeppelin/openzeppelin-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SignedMath with math utilities for signed integers (OpenZeppelin#…
…2686) * add contract and tests * avoid implicit cast * add test cases * fix test names * modify avarage and add tests * improve signed average formula * fix lint * better average formula * refactor signed average testing * add doc and changelog entry * Update contracts/utils/math/SignedMath.sol Co-authored-by: Francisco Giordano <frangio.1@gmail.com> * remove ceilDiv Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
- Loading branch information
1 parent
dee772a
commit 3458c1e
Showing
5 changed files
with
131 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../utils/math/SignedMath.sol"; | ||
|
||
contract SignedMathMock { | ||
function max(int256 a, int256 b) public pure returns (int256) { | ||
return SignedMath.max(a, b); | ||
} | ||
|
||
function min(int256 a, int256 b) public pure returns (int256) { | ||
return SignedMath.min(a, b); | ||
} | ||
|
||
function average(int256 a, int256 b) public pure returns (int256) { | ||
return SignedMath.average(a, b); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
/** | ||
* @dev Standard signed math utilities missing in the Solidity language. | ||
*/ | ||
library SignedMath { | ||
/** | ||
* @dev Returns the largest of two signed numbers. | ||
*/ | ||
function max(int256 a, int256 b) internal pure returns (int256) { | ||
return a >= b ? a : b; | ||
} | ||
|
||
/** | ||
* @dev Returns the smallest of two signed numbers. | ||
*/ | ||
function min(int256 a, int256 b) internal pure returns (int256) { | ||
return a < b ? a : b; | ||
} | ||
|
||
/** | ||
* @dev Returns the average of two signed numbers without overflow. | ||
* The result is rounded towards zero. | ||
*/ | ||
function average(int256 a, int256 b) internal pure returns (int256) { | ||
// Formula from the book "Hacker's Delight" | ||
int256 x = (a & b) + ((a ^ b) >> 1); | ||
return x + (int256(uint256(x) >> 255) & (a ^ b)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
const { BN, constants } = require('@openzeppelin/test-helpers'); | ||
const { expect } = require('chai'); | ||
const { MIN_INT256, MAX_INT256 } = constants; | ||
|
||
const SignedMathMock = artifacts.require('SignedMathMock'); | ||
|
||
contract('SignedMath', function (accounts) { | ||
const min = new BN('-1234'); | ||
const max = new BN('5678'); | ||
|
||
beforeEach(async function () { | ||
this.math = await SignedMathMock.new(); | ||
}); | ||
|
||
describe('max', function () { | ||
it('is correctly detected in first argument position', async function () { | ||
expect(await this.math.max(max, min)).to.be.bignumber.equal(max); | ||
}); | ||
|
||
it('is correctly detected in second argument position', async function () { | ||
expect(await this.math.max(min, max)).to.be.bignumber.equal(max); | ||
}); | ||
}); | ||
|
||
describe('min', function () { | ||
it('is correctly detected in first argument position', async function () { | ||
expect(await this.math.min(min, max)).to.be.bignumber.equal(min); | ||
}); | ||
|
||
it('is correctly detected in second argument position', async function () { | ||
expect(await this.math.min(max, min)).to.be.bignumber.equal(min); | ||
}); | ||
}); | ||
|
||
describe('average', function () { | ||
function bnAverage (a, b) { | ||
return a.add(b).divn(2); | ||
} | ||
|
||
it('is correctly calculated with various input', async function () { | ||
const valuesX = [ | ||
new BN('0'), | ||
new BN('3'), | ||
new BN('-3'), | ||
new BN('4'), | ||
new BN('-4'), | ||
new BN('57417'), | ||
new BN('-57417'), | ||
new BN('42304'), | ||
new BN('-42304'), | ||
MIN_INT256, | ||
MAX_INT256, | ||
]; | ||
|
||
const valuesY = [ | ||
new BN('0'), | ||
new BN('5'), | ||
new BN('-5'), | ||
new BN('2'), | ||
new BN('-2'), | ||
new BN('57417'), | ||
new BN('-57417'), | ||
new BN('42304'), | ||
new BN('-42304'), | ||
MIN_INT256, | ||
MAX_INT256, | ||
]; | ||
|
||
for (const x of valuesX) { | ||
for (const y of valuesY) { | ||
expect(await this.math.average(x, y)) | ||
.to.be.bignumber.equal(bnAverage(x, y), `Bad result for average(${x}, ${y})`); | ||
} | ||
} | ||
}); | ||
}); | ||
}); |