-
Notifications
You must be signed in to change notification settings - Fork 58
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
Add Muldiv() #110
Add Muldiv() #110
Conversation
Codecov Report
@@ Coverage Diff @@
## master #110 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 4 4
Lines 1388 1396 +8
=========================================
+ Hits 1388 1396 +8 |
I pushed a fuzzer to this PR, looks good so far. Also, I noticed that the fuzzer was broken, which is also fixed. |
I've optimized a bit by zero-check on the p[4:] directly instead copy to new Int. Before
After
|
cc @chfast would you mind taking a look? |
@holiman Is everything okay? How long will it take? |
@Planxnx just came back from a vacation, we'll get this merged soon |
uint256.go
Outdated
p := umul(x, y) | ||
|
||
// If the multiplication is within 256 bits use Div(). | ||
if (p[4] | p[5] | p[6] | p[7]) == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't have to do it. The udivrem()
will skip leading zero words itself and Div()
is using udivrem()
internally. So this optimization does not improve it algorithmically. My preference is too leave such optimization to at least another PR where it can be shown it improves any particular use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This requires a fix to udivrem()
to handle case when uLen < dLen
(quick return).
Benchmarks (current PR vs simplified version):
name old time/op new time/op delta
MulDivOverflow/small/uint256-8 2.23ns ± 0% 2.24ns ± 1% +0.66% (p=0.000 n=9+10)
MulDivOverflow/div64/uint256-8 2.25ns ± 0% 2.25ns ± 0% -0.05% (p=0.008 n=8+8)
MulDivOverflow/div128/uint256-8 2.37ns ± 0% 2.36ns ± 0% -0.51% (p=0.000 n=9+9)
MulDivOverflow/div192/uint256-8 2.41ns ± 0% 2.39ns ± 0% -0.64% (p=0.000 n=8+9)
MulDivOverflow/div256/uint256-8 2.56ns ± 0% 2.54ns ± 0% -0.66% (p=0.000 n=8+9)
MulDivOverflow/small/big-8 18.4ns ± 0% 19.0ns ± 0% +3.16% (p=0.000 n=9+10)
MulDivOverflow/div64/big-8 18.4ns ± 0% 19.0ns ± 1% +3.22% (p=0.000 n=9+10)
MulDivOverflow/div128/big-8 22.2ns ± 1% 22.9ns ± 1% +3.10% (p=0.000 n=10+10)
MulDivOverflow/div192/big-8 23.2ns ± 1% 23.8ns ± 1% +2.78% (p=0.000 n=9+9)
MulDivOverflow/div256/big-8 26.7ns ± 1% 27.4ns ± 1% +2.69% (p=0.000 n=10+10)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed Div()
and added a handle case where the denominator is greater than the numerator instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea is to handle this case inside udivrem()
. My attempt is in #111 but we need to figure out why one of the tests fails (help welcome).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The #111 looks to be ready. Can you try to rebase on top of it and completely remove this block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done ✅
@chfast thanks !!
@chfast I've updated the code. Can you do another review? |
The last change made the code coverage from the tests drop a bit - see https://app.codecov.io/gh/holiman/uint256/compare/110/diff . Not sure if I think the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me with some questions around testing.
} | ||
return -1 | ||
switch { | ||
case len(data) < 64: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured it would be better to use the input we have, rather than be too picky about the input size...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is good idea because you have unused bytes in the end of the input. They will give you exactly the same coverage path and also for all inputs giving the same coverage fuzzers prefer the shortest one and should reduce corpus entries finally. So the end result should be the same but this may be just longer route to get there.
I will experiment with two versions to see which one is faster.
return b1.Div(b1, b4) | ||
} | ||
|
||
func intMulDiv(f1, f2, f3, f4 *Int) *Int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be upgraded to public function in future so maybe change it to func (z *Int) mulDiv(x, y, d *Int) *Int
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do that later in that case
fuzz.go
Outdated
return fuzzTernaryOp(data) // needs 96 byte | ||
} | ||
// Too large input | ||
return 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should still return -1 I believe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right
(x * y) / denominator
is the most common case that cannot be calculated with full precision by normal methodsz.Mul(x, y).Div(z, denominator)
ex.
(max_uint256 * 2)/10
an overflow occurs when you try to calculated by normal methods, but the result is smaller than
max_uint256
.MulDivOverflow()
is 512-bit multiplication and 512-bit by 256-bit division.returns The 256-bit result and overflow occurred.
inspired and realworld use-case from UniswapV3
https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol