-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Minor errors in v3 FullMath.mulDiv #122
Comments
Another point to improve in this method - this time an actual performance improvement (and not just coding or documentation) - is that following this:
You can (once again) do this:
This can save the gas used in the remaining part of the function, for cases where |
I am aware and considered this when I wrote the function.
And it would add more gas in all other cases, which are (under reasonable assumptions) overwhelmingly more likely. So it would be a net loss. Optimization is about the expected gas cost, which depends on the probability distribution of your input values. This is context specific, and that's why it's important to benchmark with real data (or have a good understanding of the context) before deciding which early-exits are worth it, vs which ones are a net loss. |
It is there in my blog post, which is linked from the code. Not sure why the comment is not in the code. |
It should not, the comment is correct as is and documents how the code handles a division-by-zero (which is an important exceptional case to document for a division routine, although here it won't happen because // Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
} The evaluation for
So if |
New variables allocate stack space and cost more gas. |
Matter of style; I strongly dislike implicit returns because they obfuscate what is happening.
It changes the order in which Solidity allocates variables on the stack which (in this case) leads to gas savings. So not a coding error but another stack-allocation-order optimization. |
Well, first off, I don't see how However (and as I wrote in my initial post), there is a previous comment stating that variable And that comment is accurate (as I've also explained in my initial post, though I'm pretty sure that you know it already). So even if Finally, a quick examination of the code behavior for
Therefore, the comment "If twos is one, then it becomes zero" is the correct comment here IMO. BTW, at the beginning of the code where you recalculate |
I am well aware of the stack reordering issue, and of the fact that a "pre-declared return variable" may help (though AFAIK, this is mostly used for working around the "stack too deep" issue rather than reducing gas cost). However, I'm pretty sure that you want to use either one of the two options, and not a "mixture" of them. As I wrote, I believe that the general idea for declaring the return-variable name, is when the function consists of a single return point (at the end of the function, obviously). But this is a mere coding suggestion, so feel free to ignore it ;) |
P.S.: This function is useful to an unimaginable extent, as it allows for a significant increase in the supported input range for a vast number of applications. The alternative long-division version of it (which I once wrote), would allow for the output itself to be larger than 256 bits, but at the expense of a much higher gas consumption (while such output is not really needed in most cases). P.S.2: With regards to the comment about "twos" being "always >= 1", you might want to add an explanation of why that is the case. As I wrote in my initial post, at the point where this comment is mentioned, we know that The expression Hence So I believe that it is worth mentioning it briefly, for example, "twos is always >= 1, because denominator > 0". |
My 2 cents: Gas cost of an additional variable is minor, while using the same variable in this specific case has a significant negative impact on readability. Keep in mind that Every integer is divisible by at least 1, which is a power of 2, so That's why I think that you should use a new variable at the point where you recalculate |
I'm not permitted to open issues on the v3 repo for some reason, so posting it here instead.
In FullMath.mulDiv, there are several minor errors, mostly in the documentation.
I understand that this code was taken from https://xn--2-umb.com/21/muldiv (as is, I suppose), but you might still want to attend these errors.
There is a comment stating that variable
twos
is "Always >= 1".Further below it, there is another comment stating that "If twos is zero, then it becomes one".
One of these two comments is obviously wrong - and that would be the second one.
But let's start with the first one - "Always >= 1":
At the point where it is mentioned, we know that
twos >= 1
because we know thatdenominator > 0
.The expression
denominator & -denominator
is evaluated to 0 if and only ifdenominator == 0
.Hence
twos
, the highest power of 2 thatdenominator
is divisible by, is at least 1.It is worth mentioning that briefly, for example, "Always >= 1, because denominator > 0".
As for "If twos is zero, then it becomes one", it should actually be the opposite: "If twos is one, then it becomes zero".
More precisely (if you will), "If
twos
is 1, then its inverse (2**256 / twos
) is equal to2 ** 256
, hence 0".I would go further and use a new variable for this - something like
twosInv
, because there doesn't seem to be any good reason to "reuse" it (which just obfuscates the code IMO).Finally, a minor coding error is that the function is declared as
returns (uint256 result)
, and then ends withreturn result
.I understand the "incentive" for this, as the function consists of several return statements.
But specifically for that reason, I would actually remove the variable name from the function declaration, and just declare it inside the function.
I believe that the general idea for declaring the return-variable name, is when the function consists of a single return point (at the end of the function, obviously).
The text was updated successfully, but these errors were encountered: