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

Right shift and division with rounding. #203

Merged
merged 10 commits into from
Oct 15, 2021

Conversation

stephentyrone
Copy link
Member

@stephentyrone stephentyrone commented Sep 26, 2021

Fairly correct implementation of right-shift and division with rounding. Also fairly efficient, though I'm sure I'll find some cases where we're generating uncessary overflow checks upon further inspection. Nonetheless, a decent enough start.

The methods are added to BinaryInteger:

  • shifted<Count: BinaryInteger>(right: Count, rounding: RoundingRule) -> Self
  • divided(by: Self, rounding: RoundingRule) -> Self

The following methods are added to SignedInteger (not BinaryInteger, because the remainder is not generally representable in an UnsignedType when the rounding mode is not .down or .towardZero):

  • remainder(dividingBy: Self, rounding: RoundingRule) -> Self
  • divded(by: Self, rounding: RoundingRule) -> (quotient: Self, remainder: Self)

The following free function is added:

  • euclideanDivision<T: BinaryInteger>(_: T, _: T) -> (quotient: Self, remainder: Self)

Fairly correct implementation of right-shift with rounding. Also fairly efficient, though I'm sure I'll find some cases where we're generating uncessary overflow checks upon further inspection. Nonetheless, a decent enough start.
@stephentyrone stephentyrone marked this pull request as draft September 26, 2021 20:26
@stephentyrone
Copy link
Member Author

@swift-ci test

@stephentyrone stephentyrone marked this pull request as ready for review September 27, 2021 01:47
@stephentyrone
Copy link
Member Author

@swift-ci test

@stephentyrone stephentyrone changed the title Skeleton of integer shift with rounding. Right shift with rounding. Sep 27, 2021
@stephentyrone
Copy link
Member Author

@swift-ci test

Copy link
Member

@Catfish-Man Catfish-Man left a comment

Choose a reason for hiding this comment

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

Reviewed API only, didn't look at the implementation at all


/// A rule that defines how to select one of the two representable results
/// closest to a given value.
public enum RoundingRule {
Copy link
Member

Choose a reason for hiding this comment

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

This feels a little inconsistent.

…, rounding: .down
..., rounding: .stochastic should this be "stochastically"?
..., rounding: .trap in what circumstances does it trap? (I realize this is clear if you think about it for a moment, but maybe we can make it obvious at the point of use?)

Related, do we want rounding or do we want rounded? With the name shifted we're describing the resulting value, rather than the action, but with rounding we're describing the action.

Copy link
Member Author

@stephentyrone stephentyrone Sep 27, 2021

Choose a reason for hiding this comment

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

Yeah, I'm not super-delighted with "trap" either. Still trying to come up with a better name for it. In the meantime, it has the virtue of being short, and strongly suggesting that it can trap, so that's not a surprise.

Copy link
Member

Choose a reason for hiding this comment

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

trapIfUnrepresentable?

Copy link
Contributor

Choose a reason for hiding this comment

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

.exactOrTrap maybe?

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe just .exact, though I would like to mention "trap" in the name. I like the symmetry of exact with the exactly: inits from the stdlib.

Copy link

Choose a reason for hiding this comment

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

I like .exactOrTrap or something similar. "Trap" is kind of a specialist word, though, so maybe .exactOrFatal? .exactOrAssert? .mustBeExact? I think it makes sense to have this one be linguistically distinct, so .onlyExact or .mustBeExact or .remainderMustBeZero or ...

Copy link
Contributor

Choose a reason for hiding this comment

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

If this was implemented in the standard library, would the new cases be added to FloatingPointRoundingRule, or should the new enum be renamed to IntegerRoundingRule instead?

Copy link
Member Author

@stephentyrone stephentyrone Sep 28, 2021

Choose a reason for hiding this comment

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

I would like to deprecate FloatingPointRoundingRule eventually and replace it with RoundingRule. Exactly what that migration path looks like isn't totally clear, but I think it's the direction that we'll take if/when these show up in the stdlib.

The existing FloatingPointRoundingRule has a few problems:

  • it's an open enum, which has some not-ideal representation/performance tradeoffs for Swift. It should have been a frozen struct with static vars instead (so the layout could be frozen while still being able to add values), but that pattern wasn't yet widely used when the type was added.
  • rounding rules generally make sense for integers as well as floating-point (see this API). Even if a given rule is more/less efficient to implement or more/less useful for one type or another, it's nice to have a uniform set.

/// 5.shifted(right: 1, rounding: .trap)
@inlinable
public func shifted<Count: BinaryInteger>(
right count: Count,
Copy link
Member

Choose a reason for hiding this comment

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

I forget, what's the precedent here: do we usually include or omit "By"?

@inlinable
public func shifted<Count: BinaryInteger>(
right count: Count,
rounding rule: RoundingRule = .down
Copy link
Member

Choose a reason for hiding this comment

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

As someone who doesn't think about rounding very often I'm just going to assume that you've thought about this and there's an obvious default here, but I'm leaving this comment just on the tiny chance that on seeing it again you'll go "hm actually maybe they should always have to say what they want" :)

Copy link

Choose a reason for hiding this comment

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

For right shift, the default should certainly be to truncate. That's down or towardsZero for positive values. I have to stop and think for a bit before I can remember the right behavior for negative values.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right; "normal" right shifts (>>) round down, and there's no good reason to make anything else the default. "Normal" division (/) rounds toward zero, which is why divide by power of two can't be replaced with a shift for signed values, but the way to resolve that mismatch is to make the forthcoming divide-with-rounding default to .down as well, as that gives a more sensible remainder for most uses.

///
/// This is the default rounding mode for integer shifts, including the
/// shift operators defined in the standard library.
case down

Choose a reason for hiding this comment

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

What do you think about additionally providing something like:

static var `default`: Self { .down }

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the main concern would be that we might add an operation someday that wants to use a different default. round-to-nearest multiple comes to mind as a possibility.

Choose a reason for hiding this comment

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

Ah, so default depends on operation (so either current design or name it defaultShifting, etc.). What about a "fastest" or "I don't care" computed var, would that also be dependent on the operation?

Copy link
Member Author

@stephentyrone stephentyrone Sep 28, 2021

Choose a reason for hiding this comment

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

"don't care" here would just be >>, and for divide it would be /. Expectation is that people reach for these when they do care.

Comment on lines 123 to 124
// have to take the RNG in-out. The same problem applies to rounding
// with dithering. We should consider adding a stateful rounding API
Copy link
Contributor

Choose a reason for hiding this comment

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

Is mentioning dithering relevant? I find it confusing, especially since I thought that it is exactly this case.

Copy link
Member Author

@stephentyrone stephentyrone Sep 27, 2021

Choose a reason for hiding this comment

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

The // comments are mostly notes for myself, rather than public documentation. =) Referring to error-diffusion or ordered dithering here, though that isn't obvious.

Copy link

Choose a reason for hiding this comment

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

I'm not sure I'd include stochastic rounding in this API. It raises too many questions. (Ulichney's "Digital Halftoning" is a great introduction that discusses the importance of controlling the frequency distribution of your noise source.)

Copy link
Member Author

@stephentyrone stephentyrone Sep 27, 2021

Choose a reason for hiding this comment

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

Blue noise and the like and ordered dithers require a stateful rounding API, but even stochastic rounding without state is quite useful for some purposes.

Also implemented some basic statistical tests for .stochastic and reorganized code a little bit. The statistical justification of the thresholds for these tests should be explained a bit more, and we should decide on a policy in general for statistical tests that may fail sporadically, but these shouldn't be very noisy for now.
@stephentyrone
Copy link
Member Author

@swift-ci test

@stephentyrone stephentyrone changed the title Right shift with rounding. Right shift and division with rounding. Oct 12, 2021
@stephentyrone
Copy link
Member Author

@swift-ci test

@stephentyrone
Copy link
Member Author

@swift-ci test

@stephentyrone
Copy link
Member Author

@swift-ci test linux

@stephentyrone stephentyrone merged commit f956c23 into apple:main Oct 15, 2021
@stephentyrone stephentyrone deleted the rounding branch October 15, 2021 15:40
@stephentyrone stephentyrone mentioned this pull request Oct 15, 2021
9 tasks
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.

None yet

8 participants