Skip to content

add a round_ties_even for Ratio<T>#141

Open
m4rch3n1ng wants to merge 2 commits intorust-num:masterfrom
m4rch3n1ng:round-ties-even
Open

add a round_ties_even for Ratio<T>#141
m4rch3n1ng wants to merge 2 commits intorust-num:masterfrom
m4rch3n1ng:round-ties-even

Conversation

@m4rch3n1ng
Copy link

@m4rch3n1ng m4rch3n1ng commented Sep 5, 2025

i am working on a rust scheme interpreter, and the scheme (round x) procedure is specified to "round[...] to even when x is halfway between two integers", and is supposed to work on all number types available in scheme, which includes rationals.

since that interpreter uses this library, i figured i'd try to get my implementation into upstream.

the actual implementation i did seems "too easy", i just cmp it with a Ratio::new(one, two) and then do the same with Greater and Less like the round fn does it. if it is Equal i first round towards zero (via trunc, like in the Less path) see if it is_even, and then round away from zero (by adding/subtracting 1, like in the Greater path) if not.

@mikem8891
Copy link

Something that struck me was how complicated the current cmp function is due to the potential for overflows. For the simple comparison against 1/2 consider fractional.numer.cmp(&(fractional.denom.clone() - fractional.numer.clone())) instead of fractional.cmp(&half). There shouldn't be any risk of overflow and it will be a quicker comparison.

@m4rch3n1ng
Copy link
Author

i'll have to think about that with a pen and paper tomorrow (after getting some sleep) to see if that works or has the potential of overflowing, but i am not actually sure, if that is even a really worthwhile optimization. if you give in a ratio of -5/2, you will run into two other comparisons (at the top at the fractional < zero branch and at the bottom in the *self >= Zero::zero()). both of those will (afaict) invoke cmp anyway, so i don't think we are saving that much. (however i know, every optimization is an optimization, but i am not sure how much we are saving, but we are losing a bunch of clarity around the code imo)

i did notice an unnecessary call to self.trunc() in the Ordering::Equal branch of the match, that i can just remove in a second.

@mikem8891
Copy link

The basic idea is if a / b = fractional and we want (a / b).cmp(1 / 2), then this is the same as (2 * a).cmp(b) and the same as a.cmp(b - a). Since a / b is only the fractional part and always positive, then 0 <= a && a <= b and b - a cannot overflow.

All that said, I get your point about losing clarity in the code. My method also might not be valid if b < 0 for some reason.

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.

2 participants