Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 119 additions & 80 deletions Sources/IntegerUtilities/DivideWithRounding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,66 +64,80 @@ extension BinaryInteger {
// disagree, we have to adjust q downward and r to match.
if other.signum() != r.signum() { return q-1 }
return q

case .up:
// For rounding up, we want to have r have the opposite sign of
// other; if not, we adjust q upward and r to match.
if other.signum() == r.signum() { return q+1 }
return q

case .towardZero:
// This is exactly what the `/` operator did for us.
return q
case .toOdd:
// If q is already odd, we're done.
if q._lowWord & 1 == 1 { return q }
// Otherwise, q is even but inexact; it was originally rounded toward
// zero, so rounding away from zero instead will make it odd.
fallthrough

case .awayFromZero:
// To round away from zero, we apply the adjustments for both down
// and up.
if other.signum() != r.signum() { return q-1 }
return q+1
case .toNearestOrAwayFromZero:
// For round to nearest or away, the condition we want to satisfy is
// |r| <= |other/2|, with sign(q) != sign(r) when equality holds.
break

case .toNearestOrDown:
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
2*r.magnitude == other.magnitude && other.signum() != r.signum() {
break
}
return q

case .toNearestOrUp:
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
2*r.magnitude == other.magnitude && other.signum() == r.signum() {
break
}
return q

case .toNearestOrZero:
if r.magnitude <= other.magnitude.shifted(rightBy: 1, rounding: .down) {
return q
}
// Otherwise, round q away from zero.

case .toNearestOrAway:
if r.magnitude < other.magnitude.shifted(rightBy: 1, rounding: .up) {
return q
}
// The (q,r) we have does not satisfy the to nearest or away condition;
// round away from zero to choose the other representative of (q, r).
if other.signum() != r.signum() { return q-1 }
return q+1

case .toNearestOrEven:
// For round to nearest or away, the condition we want to satisfy is
// |r| <= |other/2|, with q even when equality holds.
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
2*r.magnitude == other.magnitude && q._lowWord & 1 == 1 {
if (other > 0) != (r > 0) { return q-1 }
return q+1
// First guarantee that |r| <= |other/2|; if not we have to round away
// instead, so break to do that.
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
2*r.magnitude == other.magnitude && !q.isMultiple(of: 2) {
break
}
return q

case .toOdd:
// If q is already odd, we have the correct result.
if q._lowWord & 1 == 1 { return q }

case .stochastically:
var qhi: UInt64
let bmag = other.magnitude
let rmag = r.magnitude
var bhi: UInt64
var rhi: UInt64
if other.magnitude <= UInt64.max {
qhi = UInt64(other.magnitude)
rhi = UInt64(r.magnitude)
bhi = UInt64(bmag)
rhi = UInt64(rmag)
} else {
// TODO: this is untested currently.
let qmag = other.magnitude
let shift = qmag._msb - 1
qhi = UInt64(truncatingIfNeeded: qmag >> shift)
rhi = UInt64(truncatingIfNeeded: r.magnitude >> shift)
let shift = bmag._msb - 63
bhi = UInt64(truncatingIfNeeded: bmag >> shift)
rhi = UInt64(truncatingIfNeeded: rmag >> shift)
}
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< qhi))
if car || sum >= qhi {
if (other > 0) != (r > 0) { return q-1 }
return q+1
}
return q
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< bhi))
if sum < bhi && !car { return q }

case .requireExact:
preconditionFailure("Division was not exact.")
}

// We didn't have the right result, so round q away from zero.
return other.signum() == r.signum() ? q+1 : q-1
}

// TODO: make this API and make it possible to implement more efficiently.
Expand Down Expand Up @@ -221,68 +235,93 @@ extension SignedInteger {
// For rounding down, we want to have r match the sign of other
// rather than self; this means that if the signs of r and other
// disagree, we have to adjust q downward and r to match.
if other.signum() != r.signum() { return (q-1, r+other) }
return (q, r)
return other.signum() == r.signum() ? (q, r) : (q-1, r+other)

case .up:
// For rounding up, we want to have r have the opposite sign of
// other; if not, we adjust q upward and r to match.
if other.signum() == r.signum() { return (q+1, r-other) }
return (q, r)
return other.signum() == r.signum() ? (q+1, r-other) : (q, r)

case .towardZero:
// This is exactly what the `/` operator did for us.
return (q, r)
case .toOdd:
// If q is already odd, we're done.
if q._lowWord & 1 == 1 { return (q, r) }
// Otherwise, q is even but inexact; it was originally rounded toward
// zero, so rounding away from zero instead will make it odd.
fallthrough

case .awayFromZero:
// To round away from zero, we apply the adjustments for both down
// and up.
if other.signum() != r.signum() { return (q-1, r+other) }
return (q+1, r-other)
case .toNearestOrAwayFromZero:
// For round to nearest or away, the condition we want to satisfy is
// |r| <= |other/2|, with sign(q) != sign(r) when equality holds.
if r.magnitude < other.magnitude.shifted(rightBy: 1, rounding: .up) {
break

case .toNearestOrDown:
// If |r| < |other/2|, we already rounded q to nearest. If the are
// equal and q is negative, then we already broke the tie in the right
// direction. However, we don't have access to the before-rounding q,
// which may have rounded up to zero, losing the sign information, so
// we have to look at other and r instead.
if 2*r.magnitude < other.magnitude ||
2*r.magnitude == other.magnitude && other.signum() == r.signum() {
return (q, r)
}

case .toNearestOrUp:
// If |r| < |other/2|, we already rounded q to nearest. If the are
// equal and q is non-negative, then we already broke the tie in the
// right direction.
if 2*r.magnitude < other.magnitude ||
2*r.magnitude == other.magnitude && other.signum() != r.signum() {
return (q, r)
}

case .toNearestOrZero:
// Check first if |r| <= |other/2|. If this holds, we have already
// rounded q correctly. Because we're working with magnitudes, we can
// safely compute 2r without worrying about overflow, even for fixed-
// width types, because r cannot be .min (because |r| < |other| by
// construction).
if 2*r.magnitude <= other.magnitude {
return (q, r)
}
// The (q,r) we have does not satisfy the to nearest or away condition;
// round away from zero to choose the other representative of (q, r).
if other.signum() != r.signum() { return (q-1, r+other) }
return (q+1, r-other)

case .toNearestOrAway:
// Check first if |r| < |other/2|. If this holds, we already rounded
// q to nearest.
if 2*r.magnitude < other.magnitude {
return (q, r)
}

case .toNearestOrEven:
// For round to nearest or away, the condition we want to satisfy is
// |r| <= |other/2|, with q even when equality holds.
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
2*r.magnitude == other.magnitude && q._lowWord & 1 == 1 {
if (other > 0) != (r > 0) { return (q-1, r+other) }
return (q+1, r-other)
// If |r| < |other/2|, we already rounded q to nearest. If the are
// equal and q is even, then we already broke the tie in the right
// direction.
if 2*r.magnitude < other.magnitude ||
2*r.magnitude == other.magnitude && q.isMultiple(of: 2) {
return (q, r)
}
return (q, r)

case .toOdd:
// If q is already odd, we have the correct result.
if q._lowWord & 1 == 1 { return (q, r) }

case .stochastically:
var qhi: UInt64
let bmag = other.magnitude
let rmag = r.magnitude
var bhi: UInt64
var rhi: UInt64
if other.magnitude <= UInt64.max {
qhi = UInt64(other.magnitude)
rhi = UInt64(r.magnitude)
bhi = UInt64(bmag)
rhi = UInt64(rmag)
} else {
// TODO: this is untested currently.
let qmag = other.magnitude
let shift = qmag._msb - 1
qhi = UInt64(truncatingIfNeeded: qmag >> shift)
rhi = UInt64(truncatingIfNeeded: r.magnitude >> shift)
}
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< qhi))
if car || sum >= qhi {
if (other > 0) != (r > 0) { return (q-1, r+other) }
return (q+1, r-other)
let shift = bmag._msb - 63
bhi = UInt64(truncatingIfNeeded: bmag >> shift)
rhi = UInt64(truncatingIfNeeded: rmag >> shift)
}
return (q, r)
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< bhi))
if sum < bhi && !car { return (q, r) }

case .requireExact:
preconditionFailure("Division was not exact.")
}

// Fallthrough behavior is to round q away from zero and adjust r to
// match.
return other.signum() == r.signum() ? (q+1, r-other) : (q-1, r+other)
}
}

Expand Down
Loading