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

Avoid overflow #42

Merged
merged 16 commits into from
Jul 18, 2019
Merged

Avoid overflow #42

merged 16 commits into from
Jul 18, 2019

Conversation

maxbla
Copy link
Contributor

@maxbla maxbla commented Apr 18, 2019

fix #13

this addresses the fact that 1/255 as Rational<u8> + 1/255 as Rational<u8> caused an overflow even though 2/255 is representable as a Rational<u8>. This overflow also occured for subtraction and modulo, as these are implemented with the same macro. This PR addresses the issue by calculating the LCM of denominators rather than naively multiplying denominators, then simplifying. Basically pre-simplify, then add, rather than add then simplify. As a comment reads, Abstracts a/b op c/d = (a*lcm/b op c*lcm/d)/lcm where lcm = lcm(b,d) (in this case op is exactly +,- and %)

The performance penalties of this approach have not been measured, but I would expect it to be a moderate performance regression for word-sized integer types, as calculating LCM is O(logn) whereas the naive approach requires only multiplication and addition, O(1).

For BigInts, I expect the performance regression to be minimal, as the old approach required multiplication, O(nlogn)? While calculating the LCM is also O(nlogn) for BigInts.

maxbla added 5 commits April 16, 2019 03:39
Tries to fix #13 by using least common multiple (LCM) for denominator
when summing Ratios. i.e. 1/255 + 1/255 -> (1+1)/LCM(255,255) -> 2/255
instead of 1/255 + 1/255 -> (1*255 + 1*255)/(255*255), which would cause
an overflow if the integer type is u8.

Also adds minmal tests making sure 1/u8::max_value() + 1/u8::max_value()
doesn't overflow for all integer types
implements least common multiple rational {addition, subtraction, modulo}
using the macro present in previous versions of this library
@maxbla
Copy link
Contributor Author

maxbla commented Apr 19, 2019

This fails in rust 1.15.0 because ? does not work on options in Rust 1.15. Should this project still support Rust 1.15? See #43.

@maxbla
Copy link
Contributor Author

maxbla commented Apr 21, 2019

Can I get some code review? maybe from @cuviper?

@maxbla
Copy link
Contributor Author

maxbla commented Jun 14, 2019

Hey @cuviper it seems like you're actively maintaining this project. Could you please let me know how to get code review?

Copy link
Member

@cuviper cuviper left a comment

Choose a reason for hiding this comment

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

Hi, thanks for the PR, and sorry for the late review. It's a good approach, and I'm not too concerned with the overhead since Ratio does such gcd reductions frequently anyway.

I think we should also address Mul, Div, and the Assign ops before we call #13 fixed though.

src/lib.rs Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
@maxbla maxbla mentioned this pull request Jun 14, 2019
src/lib.rs Show resolved Hide resolved
maxbla added 4 commits June 16, 2019 12:48
add assertions that multiplying a number near the max value of its data
type do indeed overflow. These assertions aim to guarantee that naive
implementations of those operations would indeed overflow, in other
words, that the generated large number is indeed large
@maxbla
Copy link
Contributor Author

maxbla commented Jun 17, 2019

So many failed builds! I learned my lesson. always run /ci/test_full.sh before pushing.

@maxbla
Copy link
Contributor Author

maxbla commented Jun 19, 2019

This PR now addresses add, sub, rem, mul, div, those operations' assign variants and checked_add and checked_mul. I still need to work on checked_mul and checked_div.

@maxbla
Copy link
Contributor Author

maxbla commented Jun 24, 2019

Checked operations in this crate only happen between Ratio<T> and Ratio<T>. I could add it for between Ratio<T> and T. I think that's a separate issue though, and won't address it here.

@maxbla
Copy link
Contributor Author

maxbla commented Jun 25, 2019

Pending further review, I believe this branch is ready for a merge

@maxbla
Copy link
Contributor Author

maxbla commented Jun 27, 2019

Should 1/2 - 1/2 = 0/2 or 0/1 or does it matter? Currently it is 0/1. To enforce the current behavior on most operations, there is a call to reduce() at the end. This is unnecessary in all cases except when the numerator is 0. To fix this, there could be a special case in most operations for a zero numerator, which would let us remove the relatively expensive call to reduce(). Alternatively we could allow 1/2 - 1/2 = 0/2. I'm leaning toward the former.

Copy link
Member

@cuviper cuviper left a comment

Choose a reason for hiding this comment

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

I think this is good, just a few places with unnecessary clones.

src/lib.rs Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
@maxbla
Copy link
Contributor Author

maxbla commented Jul 18, 2019

In addition to all the extraneous clone()ing you pointed out, I also found one on line 877, and one on line 801, which I also fixed.

@cuviper
Copy link
Member

cuviper commented Jul 18, 2019

Thanks!

bors r+

bors bot added a commit that referenced this pull request Jul 18, 2019
42: Avoid overflow r=cuviper a=maxbla

fix #13 

this addresses the fact that 1/255 as `Rational<u8>` + 1/255 as `Rational<u8>` caused an overflow even though 2/255 is representable as a `Rational<u8>`. This overflow also occured for subtraction and modulo, as these are implemented with the same macro. This PR addresses the issue by calculating the LCM of denominators rather than naively multiplying denominators, then simplifying. Basically pre-simplify, then add, rather than add then simplify. As a comment reads, `Abstracts a/b op c/d = (a*lcm/b op c*lcm/d)/lcm where lcm = lcm(b,d)` (in this case op is exactly +,- and %)

The performance penalties of this approach have not been measured, but I would expect it to be a moderate performance regression for word-sized integer types, as calculating LCM is O(logn) whereas the naive approach requires only multiplication and addition, O(1).

For BigInts, I expect the performance regression to be minimal, as the old approach required multiplication, O(nlogn)? While calculating the LCM is also O(nlogn) for BigInts.

Co-authored-by: Max Blachman <blachmanmax@gmail.com>
@bors
Copy link
Contributor

bors bot commented Jul 18, 2019

Build succeeded

@bors bors bot merged commit 7b83bb4 into rust-num:master Jul 18, 2019
bors bot added a commit to nervosnetwork/ckb that referenced this pull request Sep 1, 2019
1352: [HOLD] fix: conduct GCD before rational ops r=doitian,keroro520 a=u2

updated:

1. rust-num/num-rational#42
2. For mul/div Rational or U256, we use new_raw is well, the same as add/sub U256, we assume that the Rational is already reduced when initialized.
3. for add/sub Rational, we compare the denom firstly.

Co-authored-by: u2 <zhangyaning1985@gmail.com>
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.

Avoid operator overflows where possible
2 participants