Skip to content

Commit b5adc8c

Browse files
Merge pull request #296 from stephentyrone/round-out-rounding
Add new rounding modes: toNearestOr[Down,Up,Zero,Away]
2 parents 53afff1 + 683c8f2 commit b5adc8c

File tree

7 files changed

+631
-280
lines changed

7 files changed

+631
-280
lines changed

Sources/IntegerUtilities/DivideWithRounding.swift

+119-80
Original file line numberDiff line numberDiff line change
@@ -64,66 +64,80 @@ extension BinaryInteger {
6464
// disagree, we have to adjust q downward and r to match.
6565
if other.signum() != r.signum() { return q-1 }
6666
return q
67+
6768
case .up:
6869
// For rounding up, we want to have r have the opposite sign of
6970
// other; if not, we adjust q upward and r to match.
7071
if other.signum() == r.signum() { return q+1 }
7172
return q
73+
7274
case .towardZero:
7375
// This is exactly what the `/` operator did for us.
7476
return q
75-
case .toOdd:
76-
// If q is already odd, we're done.
77-
if q._lowWord & 1 == 1 { return q }
78-
// Otherwise, q is even but inexact; it was originally rounded toward
79-
// zero, so rounding away from zero instead will make it odd.
80-
fallthrough
77+
8178
case .awayFromZero:
82-
// To round away from zero, we apply the adjustments for both down
83-
// and up.
84-
if other.signum() != r.signum() { return q-1 }
85-
return q+1
86-
case .toNearestOrAwayFromZero:
87-
// For round to nearest or away, the condition we want to satisfy is
88-
// |r| <= |other/2|, with sign(q) != sign(r) when equality holds.
79+
break
80+
81+
case .toNearestOrDown:
82+
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
83+
2*r.magnitude == other.magnitude && other.signum() != r.signum() {
84+
break
85+
}
86+
return q
87+
88+
case .toNearestOrUp:
89+
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
90+
2*r.magnitude == other.magnitude && other.signum() == r.signum() {
91+
break
92+
}
93+
return q
94+
95+
case .toNearestOrZero:
96+
if r.magnitude <= other.magnitude.shifted(rightBy: 1, rounding: .down) {
97+
return q
98+
}
99+
// Otherwise, round q away from zero.
100+
101+
case .toNearestOrAway:
89102
if r.magnitude < other.magnitude.shifted(rightBy: 1, rounding: .up) {
90103
return q
91104
}
92-
// The (q,r) we have does not satisfy the to nearest or away condition;
93-
// round away from zero to choose the other representative of (q, r).
94-
if other.signum() != r.signum() { return q-1 }
95-
return q+1
105+
96106
case .toNearestOrEven:
97-
// For round to nearest or away, the condition we want to satisfy is
98-
// |r| <= |other/2|, with q even when equality holds.
99-
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
100-
2*r.magnitude == other.magnitude && q._lowWord & 1 == 1 {
101-
if (other > 0) != (r > 0) { return q-1 }
102-
return q+1
107+
// First guarantee that |r| <= |other/2|; if not we have to round away
108+
// instead, so break to do that.
109+
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
110+
2*r.magnitude == other.magnitude && !q.isMultiple(of: 2) {
111+
break
103112
}
104113
return q
114+
115+
case .toOdd:
116+
// If q is already odd, we have the correct result.
117+
if q._lowWord & 1 == 1 { return q }
118+
105119
case .stochastically:
106-
var qhi: UInt64
120+
let bmag = other.magnitude
121+
let rmag = r.magnitude
122+
var bhi: UInt64
107123
var rhi: UInt64
108124
if other.magnitude <= UInt64.max {
109-
qhi = UInt64(other.magnitude)
110-
rhi = UInt64(r.magnitude)
125+
bhi = UInt64(bmag)
126+
rhi = UInt64(rmag)
111127
} else {
112-
// TODO: this is untested currently.
113-
let qmag = other.magnitude
114-
let shift = qmag._msb - 1
115-
qhi = UInt64(truncatingIfNeeded: qmag >> shift)
116-
rhi = UInt64(truncatingIfNeeded: r.magnitude >> shift)
128+
let shift = bmag._msb - 63
129+
bhi = UInt64(truncatingIfNeeded: bmag >> shift)
130+
rhi = UInt64(truncatingIfNeeded: rmag >> shift)
117131
}
118-
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< qhi))
119-
if car || sum >= qhi {
120-
if (other > 0) != (r > 0) { return q-1 }
121-
return q+1
122-
}
123-
return q
132+
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< bhi))
133+
if sum < bhi && !car { return q }
134+
124135
case .requireExact:
125136
preconditionFailure("Division was not exact.")
126137
}
138+
139+
// We didn't have the right result, so round q away from zero.
140+
return other.signum() == r.signum() ? q+1 : q-1
127141
}
128142

129143
// TODO: make this API and make it possible to implement more efficiently.
@@ -221,68 +235,93 @@ extension SignedInteger {
221235
// For rounding down, we want to have r match the sign of other
222236
// rather than self; this means that if the signs of r and other
223237
// disagree, we have to adjust q downward and r to match.
224-
if other.signum() != r.signum() { return (q-1, r+other) }
225-
return (q, r)
238+
return other.signum() == r.signum() ? (q, r) : (q-1, r+other)
239+
226240
case .up:
227241
// For rounding up, we want to have r have the opposite sign of
228242
// other; if not, we adjust q upward and r to match.
229-
if other.signum() == r.signum() { return (q+1, r-other) }
230-
return (q, r)
243+
return other.signum() == r.signum() ? (q+1, r-other) : (q, r)
244+
231245
case .towardZero:
232246
// This is exactly what the `/` operator did for us.
233247
return (q, r)
234-
case .toOdd:
235-
// If q is already odd, we're done.
236-
if q._lowWord & 1 == 1 { return (q, r) }
237-
// Otherwise, q is even but inexact; it was originally rounded toward
238-
// zero, so rounding away from zero instead will make it odd.
239-
fallthrough
248+
240249
case .awayFromZero:
241-
// To round away from zero, we apply the adjustments for both down
242-
// and up.
243-
if other.signum() != r.signum() { return (q-1, r+other) }
244-
return (q+1, r-other)
245-
case .toNearestOrAwayFromZero:
246-
// For round to nearest or away, the condition we want to satisfy is
247-
// |r| <= |other/2|, with sign(q) != sign(r) when equality holds.
248-
if r.magnitude < other.magnitude.shifted(rightBy: 1, rounding: .up) {
250+
break
251+
252+
case .toNearestOrDown:
253+
// If |r| < |other/2|, we already rounded q to nearest. If the are
254+
// equal and q is negative, then we already broke the tie in the right
255+
// direction. However, we don't have access to the before-rounding q,
256+
// which may have rounded up to zero, losing the sign information, so
257+
// we have to look at other and r instead.
258+
if 2*r.magnitude < other.magnitude ||
259+
2*r.magnitude == other.magnitude && other.signum() == r.signum() {
260+
return (q, r)
261+
}
262+
263+
case .toNearestOrUp:
264+
// If |r| < |other/2|, we already rounded q to nearest. If the are
265+
// equal and q is non-negative, then we already broke the tie in the
266+
// right direction.
267+
if 2*r.magnitude < other.magnitude ||
268+
2*r.magnitude == other.magnitude && other.signum() != r.signum() {
269+
return (q, r)
270+
}
271+
272+
case .toNearestOrZero:
273+
// Check first if |r| <= |other/2|. If this holds, we have already
274+
// rounded q correctly. Because we're working with magnitudes, we can
275+
// safely compute 2r without worrying about overflow, even for fixed-
276+
// width types, because r cannot be .min (because |r| < |other| by
277+
// construction).
278+
if 2*r.magnitude <= other.magnitude {
249279
return (q, r)
250280
}
251-
// The (q,r) we have does not satisfy the to nearest or away condition;
252-
// round away from zero to choose the other representative of (q, r).
253-
if other.signum() != r.signum() { return (q-1, r+other) }
254-
return (q+1, r-other)
281+
282+
case .toNearestOrAway:
283+
// Check first if |r| < |other/2|. If this holds, we already rounded
284+
// q to nearest.
285+
if 2*r.magnitude < other.magnitude {
286+
return (q, r)
287+
}
288+
255289
case .toNearestOrEven:
256-
// For round to nearest or away, the condition we want to satisfy is
257-
// |r| <= |other/2|, with q even when equality holds.
258-
if r.magnitude > other.magnitude.shifted(rightBy: 1, rounding: .down) ||
259-
2*r.magnitude == other.magnitude && q._lowWord & 1 == 1 {
260-
if (other > 0) != (r > 0) { return (q-1, r+other) }
261-
return (q+1, r-other)
290+
// If |r| < |other/2|, we already rounded q to nearest. If the are
291+
// equal and q is even, then we already broke the tie in the right
292+
// direction.
293+
if 2*r.magnitude < other.magnitude ||
294+
2*r.magnitude == other.magnitude && q.isMultiple(of: 2) {
295+
return (q, r)
262296
}
263-
return (q, r)
297+
298+
case .toOdd:
299+
// If q is already odd, we have the correct result.
300+
if q._lowWord & 1 == 1 { return (q, r) }
301+
264302
case .stochastically:
265-
var qhi: UInt64
303+
let bmag = other.magnitude
304+
let rmag = r.magnitude
305+
var bhi: UInt64
266306
var rhi: UInt64
267307
if other.magnitude <= UInt64.max {
268-
qhi = UInt64(other.magnitude)
269-
rhi = UInt64(r.magnitude)
308+
bhi = UInt64(bmag)
309+
rhi = UInt64(rmag)
270310
} else {
271-
// TODO: this is untested currently.
272-
let qmag = other.magnitude
273-
let shift = qmag._msb - 1
274-
qhi = UInt64(truncatingIfNeeded: qmag >> shift)
275-
rhi = UInt64(truncatingIfNeeded: r.magnitude >> shift)
276-
}
277-
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< qhi))
278-
if car || sum >= qhi {
279-
if (other > 0) != (r > 0) { return (q-1, r+other) }
280-
return (q+1, r-other)
311+
let shift = bmag._msb - 63
312+
bhi = UInt64(truncatingIfNeeded: bmag >> shift)
313+
rhi = UInt64(truncatingIfNeeded: rmag >> shift)
281314
}
282-
return (q, r)
315+
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< bhi))
316+
if sum < bhi && !car { return (q, r) }
317+
283318
case .requireExact:
284319
preconditionFailure("Division was not exact.")
285320
}
321+
322+
// Fallthrough behavior is to round q away from zero and adjust r to
323+
// match.
324+
return other.signum() == r.signum() ? (q+1, r-other) : (q-1, r+other)
286325
}
287326
}
288327

0 commit comments

Comments
 (0)