-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Add SignedMath with math utilities for signed integers #2686
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
Merged
Merged
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
2102a94
add contract and tests
rotcivegaf d9546eb
avoid implicit cast
rotcivegaf 26d4ac9
Merge branch 'master' of github.com:rotcivegaf/openzeppelin-contracts…
rotcivegaf 105f24d
add test cases
rotcivegaf 0fb603c
fix test names
rotcivegaf 995bcca
modify avarage and add tests
rotcivegaf bf99844
Merge remote-tracking branch 'upstream/master' into feature/SignedMath
Amxx 234cfb8
improve signed average formula
Amxx 7f54245
fix lint
Amxx c2dbd67
better average formula
Amxx f3f5f46
refactor signed average testing
Amxx 39ea6c8
add doc and changelog entry
Amxx 4299249
Update contracts/utils/math/SignedMath.sol
Amxx 9241143
Merge branch 'master' into feature/SignedMath
Amxx b186ca4
Merge branch 'master' into feature/SignedMath
Amxx b1082cb
remove ceilDiv
frangio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,23 @@ | ||
| // 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); | ||
| } | ||
|
|
||
| function ceilDiv(int256 a, int256 b) public pure returns (int256) { | ||
| return SignedMath.ceilDiv(a, b); | ||
| } | ||
| } |
This file contains hidden or 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 hidden or 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,44 @@ | ||
| // 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. The result is rounded | ||
| * towards zero. | ||
| */ | ||
| function average(int256 a, int256 b) internal pure returns (int256) { | ||
| // (a + b) / 2 can overflow, and the unsigned formula doesn't simply translate to signed integers | ||
| int256 x = (a & b) + ((a ^ b) >> 1); | ||
| return x + (int256(uint256(x) >> 255) & (a ^ b)); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Returns the ceiling of the division of two signed numbers. | ||
| * | ||
| * This differs from standard division with `/` in that it rounds up instead | ||
| * of rounding down. | ||
frangio marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| */ | ||
| function ceilDiv(int256 a, int256 b) internal pure returns (int256) { | ||
| int256 z = a / b; | ||
| int256 r = a % b == 0 ? int256(0) : int256(1); | ||
| return z < 0 ? z - r : z + r; | ||
| } | ||
| } | ||
This file contains hidden or 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,126 @@ | ||
| 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})`); | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('ceilDiv', function () { | ||
| it('does not round up on exact division, unsigned numbers', async function () { | ||
| const a = new BN('10'); | ||
| const b = new BN('5'); | ||
| expect(await this.math.ceilDiv(a, b)).to.be.bignumber.equal('2'); | ||
| }); | ||
|
|
||
| it('does not round up on exact division, signed numbers', async function () { | ||
| const a = new BN('-10'); | ||
| const b = new BN('-5'); | ||
| expect(await this.math.ceilDiv(a, b)).to.be.bignumber.equal('2'); | ||
| }); | ||
|
|
||
| it('does not round up on exact division', async function () { | ||
| const a = new BN('-10'); | ||
| const b = new BN('5'); | ||
| expect(await this.math.ceilDiv(a, b)).to.be.bignumber.equal('-2'); | ||
| }); | ||
|
|
||
| it('rounds up on division with remainders, unsigned numbers', async function () { | ||
| const a = new BN('42'); | ||
| const b = new BN('13'); | ||
| expect(await this.math.ceilDiv(a, b)).to.be.bignumber.equal('4'); | ||
| }); | ||
|
|
||
| it('rounds up on division with remainders signed numbers', async function () { | ||
| const a = new BN('-42'); | ||
| const b = new BN('-13'); | ||
| expect(await this.math.ceilDiv(a, b)).to.be.bignumber.equal('4'); | ||
| }); | ||
|
|
||
| it('rounds up on division with remainders', async function () { | ||
| const a = new BN('-42'); | ||
| const b = new BN('13'); | ||
| expect(await this.math.ceilDiv(a, b)).to.be.bignumber.equal('-4'); | ||
| }); | ||
|
|
||
| it('does not overflow', async function () { | ||
| const b = new BN('2'); | ||
| const result = new BN('1').shln(254); | ||
| expect(await this.math.ceilDiv(MAX_INT256, b)).to.be.bignumber.equal(result); | ||
| }); | ||
|
|
||
| it('correctly computes max int256 divided by 1', async function () { | ||
| const b = new BN('1'); | ||
| expect(await this.math.ceilDiv(MAX_INT256, b)).to.be.bignumber.equal(MAX_INT256); | ||
| }); | ||
| }); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.