From 8050d7d0f85634e79024dc8813c5cfadbf871477 Mon Sep 17 00:00:00 2001 From: jjz Date: Sun, 6 Mar 2022 11:30:32 +0800 Subject: [PATCH 01/17] Add sqrt for math --- contracts/mocks/MathMock.sol | 4 ++++ contracts/utils/math/Math.sol | 19 +++++++++++++++++++ test/utils/math/Math.test.js | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/contracts/mocks/MathMock.sol b/contracts/mocks/MathMock.sol index c651b6bb1fd..95fba070035 100644 --- a/contracts/mocks/MathMock.sol +++ b/contracts/mocks/MathMock.sol @@ -20,4 +20,8 @@ contract MathMock { function ceilDiv(uint256 a, uint256 b) public pure returns (uint256) { return Math.ceilDiv(a, b); } + + function sqrt(uint256 a) public pure returns (uint256) { + return Math.sqrt(a); + } } diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 291d257b0d8..8f6abf8ed10 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -40,4 +40,23 @@ library Math { // (a + b - 1) / b can overflow on addition, so we distribute. return a / b + (a % b == 0 ? 0 : 1); } + + /** + * @dev Returns the computing square roots. + * + * Methods of computing square roots + * + */ + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } } diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index 7e194dec713..b8ab3335d78 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -85,4 +85,24 @@ contract('Math', function (accounts) { expect(await this.math.ceilDiv(MAX_UINT256, b)).to.be.bignumber.equal(MAX_UINT256); }); }); + + describe('sqrt', function () { + it('Value less than 3', async function () { + const a = new BN('0'); + const b = new BN('1'); + const c = new BN('2'); + expect(await this.math.sqrt(a)).to.be.bignumber.equal('0'); + expect(await this.math.sqrt(b)).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(c)).to.be.bignumber.equal('1'); + }); + + it('Value greater than 3', async function () { + const a = new BN('3'); + const b = new BN('144'); + const c = new BN('1000000'); + expect(await this.math.sqrt(a)).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(b)).to.be.bignumber.equal('12'); + expect(await this.math.sqrt(c)).to.be.bignumber.equal('1000'); + }); + }); }); From 74076e3964a07cbbf714d603e968700c263552e3 Mon Sep 17 00:00:00 2001 From: jjz Date: Sun, 6 Mar 2022 11:48:39 +0800 Subject: [PATCH 02/17] Fix lint for .sol --- contracts/utils/math/Math.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 8f6abf8ed10..efba649606d 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -42,15 +42,15 @@ library Math { } /** - * @dev Returns the computing square roots. + * @dev Returns the computing square roots. * * Methods of computing square roots - * + * */ - function sqrt(uint y) internal pure returns (uint z) { + function sqrt(uint256 y) internal pure returns (uint256 z) { if (y > 3) { z = y; - uint x = y / 2 + 1; + uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; From 98e1358b10d7348d939611cbf9a5f6a7809adc64 Mon Sep 17 00:00:00 2001 From: jjz Date: Sun, 6 Mar 2022 11:56:02 +0800 Subject: [PATCH 03/17] Add new tests --- test/utils/math/Math.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index b8ab3335d78..eb22e61123e 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -100,9 +100,15 @@ contract('Math', function (accounts) { const a = new BN('3'); const b = new BN('144'); const c = new BN('1000000'); + const d = new BN('1000001'); + const e = new BN('1002000'); + const f = new BN('1002001'); expect(await this.math.sqrt(a)).to.be.bignumber.equal('1'); expect(await this.math.sqrt(b)).to.be.bignumber.equal('12'); expect(await this.math.sqrt(c)).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(d)).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(e)).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(f)).to.be.bignumber.equal('1001'); }); }); }); From 25950fa2f48307c319d11343cac21b32f26a7141 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 30 May 2022 22:50:43 +0200 Subject: [PATCH 04/17] slight improvement --- contracts/utils/math/Math.sol | 22 ++++++++-------------- test/utils/math/Math.test.js | 34 ++++++++++++---------------------- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index efba649606d..366459960b8 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -42,21 +42,15 @@ library Math { } /** - * @dev Returns the computing square roots. - * - * Methods of computing square roots - * + * @dev Returns the square root of a number. */ - function sqrt(uint256 y) internal pure returns (uint256 z) { - if (y > 3) { - z = y; - uint256 x = y / 2 + 1; - while (x < z) { - z = x; - x = (y / x + x) / 2; - } - } else if (y != 0) { - z = 1; + function sqrt(uint256 a) internal pure returns (uint256) { + uint256 x = a; + uint256 y = a / 2 + a % 2; + while (y < x) { + x = y; + y = (a / y + y) / 2; } + return x; } } diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index eb22e61123e..e1131b90353 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -87,28 +87,18 @@ contract('Math', function (accounts) { }); describe('sqrt', function () { - it('Value less than 3', async function () { - const a = new BN('0'); - const b = new BN('1'); - const c = new BN('2'); - expect(await this.math.sqrt(a)).to.be.bignumber.equal('0'); - expect(await this.math.sqrt(b)).to.be.bignumber.equal('1'); - expect(await this.math.sqrt(c)).to.be.bignumber.equal('1'); - }); - - it('Value greater than 3', async function () { - const a = new BN('3'); - const b = new BN('144'); - const c = new BN('1000000'); - const d = new BN('1000001'); - const e = new BN('1002000'); - const f = new BN('1002001'); - expect(await this.math.sqrt(a)).to.be.bignumber.equal('1'); - expect(await this.math.sqrt(b)).to.be.bignumber.equal('12'); - expect(await this.math.sqrt(c)).to.be.bignumber.equal('1000'); - expect(await this.math.sqrt(d)).to.be.bignumber.equal('1000'); - expect(await this.math.sqrt(e)).to.be.bignumber.equal('1000'); - expect(await this.math.sqrt(f)).to.be.bignumber.equal('1001'); + it('rounds down', async function () { + expect(await this.math.sqrt(new BN('0'))).to.be.bignumber.equal('0'); + expect(await this.math.sqrt(new BN('1'))).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(new BN('2'))).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(new BN('3'))).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(new BN('4'))).to.be.bignumber.equal('2'); + expect(await this.math.sqrt(new BN('144'))).to.be.bignumber.equal('12'); + expect(await this.math.sqrt(new BN('1000000'))).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(new BN('1000001'))).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(new BN('1002000'))).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(new BN('1002001'))).to.be.bignumber.equal('1001'); + expect(await this.math.sqrt(MAX_UINT256)).to.be.bignumber.equal('340282366920938463463374607431768211455'); }); }); }); From 8d5d2d2513939732c6c61bd0d9493a1812223a83 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 30 May 2022 22:51:41 +0200 Subject: [PATCH 05/17] add a changelog entry --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 365c4014639..82a08293afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ * `DoubleEndedQueue`: a new data structure that supports efficient push and pop to both front and back, useful for FIFO and LIFO queues. ([#3153](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3153)) * `Governor`: improved security of `onlyGovernance` modifier when using an external executor contract (e.g. a timelock) that can operate without necessarily going through the governance protocol. ([#3147](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3147)) * `Governor`: Add a way to parameterize votes. This can be used to implement voting systems such as fractionalized voting, ERC721 based voting, or any number of other systems. The `params` argument added to `_countVote` method, and included in the newly added `_getVotes` method, can be used by counting and voting modules respectively for such purposes. - + * `Math`: Add a `sqrt` function. ([#3242](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3242)) + ### Breaking changes * `Governor`: Adds internal virtual `_getVotes` method that must be implemented; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing voting module extension, rename `getVotes` to `_getVotes` and add a `bytes memory` argument. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) From 270e8fc94b8a7ac636ebfa45b4f1a95de9760a40 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 30 May 2022 23:14:59 +0200 Subject: [PATCH 06/17] lint --- contracts/utils/math/Math.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 366459960b8..1121372001b 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -46,7 +46,7 @@ library Math { */ function sqrt(uint256 a) internal pure returns (uint256) { uint256 x = a; - uint256 y = a / 2 + a % 2; + uint256 y = a / 2 + (a % 2); while (y < x) { x = y; y = (a / y + y) / 2; From 71ac6df001a3756bbf89ada9a983be12e542792b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 1 Jun 2022 14:44:00 +0200 Subject: [PATCH 07/17] significant gas improvement of sqrt --- contracts/utils/math/Math.sol | 56 +++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 1121372001b..82a55049b24 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -43,14 +43,60 @@ library Math { /** * @dev Returns the square root of a number. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we use the log2 of the square root. We do that by shifting `a` and only counting half + // of the bits. the partial result produced is a power of two that verifies `result <= sqrt(a) < 2 * result` + uint256 result = 1; uint256 x = a; - uint256 y = a / 2 + (a % 2); - while (y < x) { - x = y; - y = (a / y + y) / 2; + if (x > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { + x >>= 128; + result <<= 64; + } + if (x > 0xFFFFFFFFFFFFFFFF) { + x >>= 64; + result <<= 32; + } + if (x > 0xFFFFFFFF) { + x >>= 32; + result <<= 16; + } + if (x > 0xFFFF) { + x >>= 16; + result <<= 8; + } + if (x > 0xFF) { + x >>= 8; + result <<= 4; + } + if (x > 0xF) { + x >>= 4; + result <<= 2; + } + if (x > 0x3) { + result <<= 1; + } + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iterration). We thus need at most 7 iterration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + uint256 r0 = a / result; + return result < r0 ? result : r0; } - return x; } } From fa2b258270908b7864d0a309e4b83c6b4512df66 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 1 Jun 2022 14:54:19 +0200 Subject: [PATCH 08/17] use min --- contracts/utils/math/Math.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 82a55049b24..a711da1202c 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -95,8 +95,7 @@ library Math { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; - uint256 r0 = a / result; - return result < r0 ? result : r0; + return min(result, a / result); } } } From 27d025e4add2172bf12e8e60c1d3245aaae2d533 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 1 Jun 2022 20:24:02 +0200 Subject: [PATCH 09/17] Fix typo --- contracts/utils/math/Math.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index a711da1202c..4f8dded293b 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -85,7 +85,7 @@ library Math { // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at - // every iterration). We thus need at most 7 iterration to turn our partial result with one bit of precision + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; From 0218d359fc3b4a4b6079bb345528fc1804eeb15e Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 2 Jun 2022 11:14:39 +0200 Subject: [PATCH 10/17] fix lint --- test/utils/math/Math.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index 69b9335dff9..674448f23c7 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -196,5 +196,6 @@ contract('Math', function (accounts) { expect(await this.math.sqrt(new BN('1002000'))).to.be.bignumber.equal('1000'); expect(await this.math.sqrt(new BN('1002001'))).to.be.bignumber.equal('1001'); expect(await this.math.sqrt(MAX_UINT256)).to.be.bignumber.equal('340282366920938463463374607431768211455'); + }); }); }); From c940f219a040ccc9de349c0ed42fb282b30c56d3 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 6 Jun 2022 10:08:57 +0200 Subject: [PATCH 11/17] Apply suggestions from code review Co-authored-by: Francisco Giordano --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d07757f658d..72a64c938d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ * `ERC721`, `ERC1155`: simplified revert reasons. ([#3254](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3254), ([#3438](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3438))) * `ERC721`: removed redundant require statement. ([#3434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3434)) * `PaymentSplitter`: add `releasable` getters. ([#3350](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3350)) - * `Math`: Add a `sqrt` function. ([#3242](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3242)) + * `Math`: Add a `sqrt` function to compute square roots of integers. ([#3242](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3242)) ## 4.6.0 (2022-04-26) From 0b157fab564e9180a95c490b7db2f116dae026ef Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 6 Jun 2022 10:25:43 +0200 Subject: [PATCH 12/17] Update Math.sol --- contracts/utils/math/Math.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 9cb73ba1ce5..41a090ba5a0 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -151,7 +151,7 @@ library Math { } /** - * @dev Returns the square root of a number. + * @dev Returns the square root of a number. It the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ @@ -160,8 +160,13 @@ library Math { return 0; } - // For our first guess, we use the log2 of the square root. We do that by shifting `a` and only counting half - // of the bits. the partial result produced is a power of two that verifies `result <= sqrt(a) < 2 * result` + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. + // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`. + // This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) <= 2**((k+1)/2) < 2 ** (k/2+1)`. + // Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a + // good first aproximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1; uint256 x = a; if (x > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { From 6e1ce42b54eb949be00dc9fcc41bc71f2acb9067 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 6 Jun 2022 10:27:15 +0200 Subject: [PATCH 13/17] Update Math.sol --- contracts/utils/math/Math.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 41a090ba5a0..23ac114fa1f 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -164,7 +164,7 @@ library Math { // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`. - // This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) <= 2**((k+1)/2) < 2 ** (k/2+1)`. + // This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`. // Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a // good first aproximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1; From 6c138b20c75363357c371972f001171a2e3663af Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 6 Jun 2022 10:28:58 +0200 Subject: [PATCH 14/17] Update Math.sol --- contracts/utils/math/Math.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 23ac114fa1f..d3130fa1a3a 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -169,31 +169,31 @@ library Math { // good first aproximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1; uint256 x = a; - if (x > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { + if (x >= (1 << 128)) { x >>= 128; result <<= 64; } - if (x > 0xFFFFFFFFFFFFFFFF) { + if (x >= (1 << 64)) { x >>= 64; result <<= 32; } - if (x > 0xFFFFFFFF) { + if (x >= (1 << 32)) { x >>= 32; result <<= 16; } - if (x > 0xFFFF) { + if (x >= (1 << 16)) { x >>= 16; result <<= 8; } - if (x > 0xFF) { + if (x >= (1 << 8)) { x >>= 8; result <<= 4; } - if (x > 0xF) { + if (x >= (1 << 4)) { x >>= 4; result <<= 2; } - if (x > 0x3) { + if (x >= (1 << 2) { result <<= 1; } From beaf8667b83f46f2ae2ad7531d94fd13806899b8 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 6 Jun 2022 10:34:57 +0200 Subject: [PATCH 15/17] Add sqrt with rounding --- contracts/mocks/MathMock.sol | 4 ++-- contracts/utils/math/Math.sol | 19 ++++++++++++++---- test/utils/math/Math.test.js | 38 +++++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/contracts/mocks/MathMock.sol b/contracts/mocks/MathMock.sol index 372fc8fa22f..a9022aa4c96 100644 --- a/contracts/mocks/MathMock.sol +++ b/contracts/mocks/MathMock.sol @@ -30,7 +30,7 @@ contract MathMock { return Math.mulDiv(a, b, denominator, direction); } - function sqrt(uint256 a) public pure returns (uint256) { - return Math.sqrt(a); + function sqrt(uint256 a, Math.Rounding direction) public pure returns (uint256) { + return Math.sqrt(a, direction); } } diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index d3130fa1a3a..90eba911031 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -161,11 +161,11 @@ library Math { } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. - // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have - // `msb(a) <= a < 2*msb(a)`. + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`. // This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`. - // Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a + // Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a // good first aproximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1; uint256 x = a; @@ -193,7 +193,7 @@ library Math { x >>= 4; result <<= 2; } - if (x >= (1 << 2) { + if (x >= (1 << 2)) { result <<= 1; } @@ -212,4 +212,15 @@ library Math { return min(result, a / result); } } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + uint256 result = sqrt(a); + if (rounding == Rounding.Up && result * result < a) { + result += 1; + } + return result; + } } diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index 674448f23c7..93049b95dd0 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -185,17 +185,33 @@ contract('Math', function (accounts) { describe('sqrt', function () { it('rounds down', async function () { - expect(await this.math.sqrt(new BN('0'))).to.be.bignumber.equal('0'); - expect(await this.math.sqrt(new BN('1'))).to.be.bignumber.equal('1'); - expect(await this.math.sqrt(new BN('2'))).to.be.bignumber.equal('1'); - expect(await this.math.sqrt(new BN('3'))).to.be.bignumber.equal('1'); - expect(await this.math.sqrt(new BN('4'))).to.be.bignumber.equal('2'); - expect(await this.math.sqrt(new BN('144'))).to.be.bignumber.equal('12'); - expect(await this.math.sqrt(new BN('1000000'))).to.be.bignumber.equal('1000'); - expect(await this.math.sqrt(new BN('1000001'))).to.be.bignumber.equal('1000'); - expect(await this.math.sqrt(new BN('1002000'))).to.be.bignumber.equal('1000'); - expect(await this.math.sqrt(new BN('1002001'))).to.be.bignumber.equal('1001'); - expect(await this.math.sqrt(MAX_UINT256)).to.be.bignumber.equal('340282366920938463463374607431768211455'); + expect(await this.math.sqrt(new BN('0'), Rounding.Down)).to.be.bignumber.equal('0'); + expect(await this.math.sqrt(new BN('1'), Rounding.Down)).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(new BN('2'), Rounding.Down)).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(new BN('3'), Rounding.Down)).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(new BN('4'), Rounding.Down)).to.be.bignumber.equal('2'); + expect(await this.math.sqrt(new BN('144'), Rounding.Down)).to.be.bignumber.equal('12'); + expect(await this.math.sqrt(new BN('999999'), Rounding.Down)).to.be.bignumber.equal('999'); + expect(await this.math.sqrt(new BN('1000000'), Rounding.Down)).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(new BN('1000001'), Rounding.Down)).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(new BN('1002000'), Rounding.Down)).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(new BN('1002001'), Rounding.Down)).to.be.bignumber.equal('1001'); + expect(await this.math.sqrt(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal('340282366920938463463374607431768211455'); + }); + + it('rounds up', async function () { + expect(await this.math.sqrt(new BN('0'), Rounding.Up)).to.be.bignumber.equal('0'); + expect(await this.math.sqrt(new BN('1'), Rounding.Up)).to.be.bignumber.equal('1'); + expect(await this.math.sqrt(new BN('2'), Rounding.Up)).to.be.bignumber.equal('2'); + expect(await this.math.sqrt(new BN('3'), Rounding.Up)).to.be.bignumber.equal('2'); + expect(await this.math.sqrt(new BN('4'), Rounding.Up)).to.be.bignumber.equal('2'); + expect(await this.math.sqrt(new BN('144'), Rounding.Up)).to.be.bignumber.equal('12'); + expect(await this.math.sqrt(new BN('999999'), Rounding.Up)).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(new BN('1000000'), Rounding.Up)).to.be.bignumber.equal('1000'); + expect(await this.math.sqrt(new BN('1000001'), Rounding.Up)).to.be.bignumber.equal('1001'); + expect(await this.math.sqrt(new BN('1002000'), Rounding.Up)).to.be.bignumber.equal('1001'); + expect(await this.math.sqrt(new BN('1002001'), Rounding.Up)).to.be.bignumber.equal('1001'); + expect(await this.math.sqrt(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('340282366920938463463374607431768211456'); }); }); }); From abe47be448e516d2ed37be2a28d95e14909ba99c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 6 Jun 2022 14:28:32 +0200 Subject: [PATCH 16/17] fix lint --- test/utils/math/Math.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/utils/math/Math.test.js b/test/utils/math/Math.test.js index 93049b95dd0..a71deb50db9 100644 --- a/test/utils/math/Math.test.js +++ b/test/utils/math/Math.test.js @@ -196,7 +196,8 @@ contract('Math', function (accounts) { expect(await this.math.sqrt(new BN('1000001'), Rounding.Down)).to.be.bignumber.equal('1000'); expect(await this.math.sqrt(new BN('1002000'), Rounding.Down)).to.be.bignumber.equal('1000'); expect(await this.math.sqrt(new BN('1002001'), Rounding.Down)).to.be.bignumber.equal('1001'); - expect(await this.math.sqrt(MAX_UINT256, Rounding.Down)).to.be.bignumber.equal('340282366920938463463374607431768211455'); + expect(await this.math.sqrt(MAX_UINT256, Rounding.Down)) + .to.be.bignumber.equal('340282366920938463463374607431768211455'); }); it('rounds up', async function () { @@ -211,7 +212,8 @@ contract('Math', function (accounts) { expect(await this.math.sqrt(new BN('1000001'), Rounding.Up)).to.be.bignumber.equal('1001'); expect(await this.math.sqrt(new BN('1002000'), Rounding.Up)).to.be.bignumber.equal('1001'); expect(await this.math.sqrt(new BN('1002001'), Rounding.Up)).to.be.bignumber.equal('1001'); - expect(await this.math.sqrt(MAX_UINT256, Rounding.Up)).to.be.bignumber.equal('340282366920938463463374607431768211456'); + expect(await this.math.sqrt(MAX_UINT256, Rounding.Up)) + .to.be.bignumber.equal('340282366920938463463374607431768211456'); }); }); }); From 361fe6b19f8d4797466ca941c67ff3ce8e74ce5b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 6 Jun 2022 14:40:04 +0200 Subject: [PATCH 17/17] Gas improvement. --- contracts/utils/math/Math.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 90eba911031..470aa1f1dde 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -169,31 +169,31 @@ library Math { // good first aproximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1; uint256 x = a; - if (x >= (1 << 128)) { + if (x >> 128 > 0) { x >>= 128; result <<= 64; } - if (x >= (1 << 64)) { + if (x >> 64 > 0) { x >>= 64; result <<= 32; } - if (x >= (1 << 32)) { + if (x >> 32 > 0) { x >>= 32; result <<= 16; } - if (x >= (1 << 16)) { + if (x >> 16 > 0) { x >>= 16; result <<= 8; } - if (x >= (1 << 8)) { + if (x >> 8 > 0) { x >>= 8; result <<= 4; } - if (x >= (1 << 4)) { + if (x >> 4 > 0) { x >>= 4; result <<= 2; } - if (x >= (1 << 2)) { + if (x >> 2 > 0) { result <<= 1; }