Skip to content
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

Merged
merged 20 commits into from
Mar 24, 2022
Merged

Add Muldiv() #110

merged 20 commits into from
Mar 24, 2022

Conversation

Planxnx
Copy link
Contributor

@Planxnx Planxnx commented Feb 21, 2022

(x * y) / denominator is the most common case that cannot be calculated with full precision by normal methods z.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

BenchmarkMulDivOverflow/small/uint256-8         	491599033	         2.297 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div64/uint256-8         	512714014	         2.351 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div128/uint256-8        	488145638	         2.415 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div192/uint256-8        	484725356	         2.457 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div256/uint256-8        	438809512	         2.509 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/small/big-8             	58039941	        20.62 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div64/big-8             	58061472	        20.61 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div128/big-8            	43477892	        24.84 ns/op	       1 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div192/big-8            	46356553	        24.83 ns/op	       2 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div256/big-8            	40076848	        28.15 ns/op	       4 B/op	       0 allocs/op

@Planxnx Planxnx changed the title Add Muldiv Add Muldiv() Feb 21, 2022
@codecov
Copy link

codecov bot commented Feb 21, 2022

Codecov Report

Merging #110 (4db6416) into master (d97bdee) will not change coverage.
The diff coverage is 100.00%.

❗ Current head 4db6416 differs from pull request most recent head 1c0d673. Consider uploading reports for the commit 1c0d673 to get more accurate results

@@            Coverage Diff            @@
##            master      #110   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         4           
  Lines         1388      1396    +8     
=========================================
+ Hits          1388      1396    +8     

@holiman
Copy link
Owner

holiman commented Feb 22, 2022

I pushed a fuzzer to this PR, looks good so far. Also, I noticed that the fuzzer was broken, which is also fixed.

fuzz.go Outdated Show resolved Hide resolved
@Planxnx
Copy link
Contributor Author

Planxnx commented Feb 22, 2022

I've optimized a bit by zero-check on the p[4:] directly instead copy to new Int.

Before

var ph Int
copy(ph[:], p[4:])
if ph.IsZero() { ... }

After
if (p[4] | p[5] | p[6] | p[7]) == 0 { ... }

BenchmarkMulDivOverflow/small/uint256-8         	474043095	         2.285 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div64/uint256-8         	501923428	         2.294 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div128/uint256-8        	498558622	         2.374 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div192/uint256-8        	484291431	         2.404 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div256/uint256-8        	441282002	         2.491 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/small/big-8             	58109386	        20.55 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div64/big-8             	58929327	        20.56 ns/op	       0 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div128/big-8            	43171965	        24.07 ns/op	       1 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div192/big-8            	45164564	        24.88 ns/op	       2 B/op	       0 allocs/op
BenchmarkMulDivOverflow/div256/big-8            	41473440	        26.91 ns/op	       4 B/op	       0 allocs/op

@holiman
Copy link
Owner

holiman commented Feb 22, 2022

cc @chfast would you mind taking a look?

@Planxnx
Copy link
Contributor Author

Planxnx commented Mar 1, 2022

@holiman Is everything okay? How long will it take?

@holiman
Copy link
Owner

holiman commented Mar 7, 2022

@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 {
Copy link
Collaborator

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.

Copy link
Collaborator

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)

Copy link
Contributor Author

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.

Copy link
Collaborator

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).

Copy link
Collaborator

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done ✅
@chfast thanks !!

uint256.go Outdated Show resolved Hide resolved
@Planxnx
Copy link
Contributor Author

Planxnx commented Mar 8, 2022

@chfast I've updated the code. Can you do another review?

@holiman
Copy link
Owner

holiman commented Mar 8, 2022

The last change made the code coverage from the tests drop a bit - see https://app.codecov.io/gh/holiman/uint256/compare/110/diff .

Screenshot 2022-03-08 at 20-05-39 Codecov

Not sure if I think the pl.Eq(d) case warrants a separate check, I would rather just remove it personally.

Copy link
Collaborator

@chfast chfast left a 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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Owner

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...?

Copy link
Collaborator

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 {
Copy link
Collaborator

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?

Copy link
Owner

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
Copy link
Collaborator

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.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right

@holiman holiman merged commit 77643b2 into holiman:master Mar 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants