Skip to content

Commit dbffff1

Browse files
committed
Add some basic documentation.
1 parent 7953ef3 commit dbffff1

File tree

3 files changed

+100
-6
lines changed

3 files changed

+100
-6
lines changed

Sources/IntegerUtilities/README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ _Note: This module is only present on `main`, and has not yet been stabilized an
44

55
## Utilities defined for `BinaryInteger`
66

7-
The following API are defined for all integer types conforming to `BinaryInteger`:
7+
The following API are defined for all integer types:
88

99
- The `gcd(_:_:)` free function implements the _Greatest Common Divisor_ operation.
1010

@@ -36,6 +36,32 @@ The following API are defined for signed integer types:
3636

3737
- The `rotated(right:)` and `rotated(left:)` methods implement _bitwise rotation_ for signed and unsigned integer types.
3838
The count parameter may be any `BinaryInteger` type.
39+
40+
### Saturating Arithmetic
41+
42+
The following saturating operations are defined as methods on
43+
`FixedWidthInteger` types:
44+
45+
- `addingWithSaturation(_:)`
46+
- `subtractingWithSaturation(_:)`
47+
- `negatedWithSaturation(_:)`
48+
- `multipliedWithSaturation(by:)`
49+
50+
These implement _saturating arithmetic_. These are an alternative to the
51+
usual `+`, `-`, and `*` operators, which trap if the result cannot be
52+
represented in the argument type, and `&+`, `&-`, and `&*` which wrap
53+
out-of-range results modulo 2ⁿ for some n. Instead these methods clamp
54+
the result to the representable range of the type:
55+
```
56+
let x: Int8 = 84
57+
let y: Int8 = 100
58+
let a = x + y // traps due to overflow
59+
let b = x &+ y // wraps to -72
60+
let c = x.addingWithSaturation(y) // saturates to 127
61+
```
62+
63+
There is one other method, `shiftedWithSaturation(leftBy:rounding:)`,
64+
which performs a bitwise shift with rounding and saturation.
3965

4066
## Types
4167

Sources/IntegerUtilities/SaturatingArithmetic.swift

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,74 @@ extension FixedWidthInteger {
1313
@_transparent @usableFromInline
1414
var signExtension: Self { self &>> -1 }
1515

16+
/// Saturating integer addition
17+
///
18+
/// `self + other` clamped to the representable range of the type. e.g.:
19+
/// ```
20+
/// let a: Int8 = 84
21+
/// let b: Int8 = 100
22+
/// // 84 + 100 = 184 is not representable as
23+
/// // Int8, so `c` is clamped to Int8.max (127).
24+
/// let c = a.addingWithSaturation(b)
25+
/// ```
26+
///
27+
/// Anytime the "normal addition" `self + other` does not trap,
28+
/// `addingWithSaturation` produces the same result.
1629
@inlinable
1730
public func addingWithSaturation(_ other: Self) -> Self {
1831
let (wrapped, overflow) = addingReportingOverflow(other)
1932
if !overflow { return wrapped }
2033
return Self.max &- signExtension
2134
}
2235

36+
/// Saturating integer subtraction
37+
///
38+
/// `self - other` clamped to the representable range of the type. e.g.:
39+
/// ```
40+
/// let a: UInt = 37
41+
/// let b: UInt = 42
42+
/// // 37 - 42 = -5, which is not representable as
43+
/// // UInt, so `c` is clamped to UInt.min (zero).
44+
/// let c = a.subtractingWithSaturation(b)
45+
/// ```
46+
///
47+
/// Note that `a.addingWithSaturation(-b)` is not always equivalent to
48+
/// `a.subtractingWithSaturation(b)`, because `-b` is not representable
49+
/// if `b` is the minimum value of a signed type.
50+
///
51+
/// Anytime the "normal subtraction" `self - other` does not trap,
52+
/// `subtractingWithSaturation` produces the same result.
2353
@inlinable
2454
public func subtractingWithSaturation(_ other: Self) -> Self {
2555
let (wrapped, overflow) = subtractingReportingOverflow(other)
2656
if !overflow { return wrapped }
2757
return Self.max &- signExtension
2858
}
2959

60+
/// Saturating integer negation
61+
///
62+
/// For unsigned types, the result is always zero. This is not very
63+
/// interesting, but may occasionally be useful in generic contexts.
64+
/// For signed types, the result is `-self` unless `self` is `.min`,
65+
/// in which case the result is `.max`.
66+
@inlinable
67+
public func negatedWithSaturation() -> Self {
68+
Self.zero.subtractingWithSaturation(self)
69+
}
70+
71+
/// Saturating integer multiplication
72+
///
73+
/// `self * other` clamped to the representable range of the type. e.g.:
74+
/// ```
75+
/// let a: Int8 = -16
76+
/// let b: Int8 = -8
77+
/// // -16 * -8 = 128 is not representable as
78+
/// // Int8, so `c` is clamped to Int8.max (127).
79+
/// let c = a.multipliedWithSaturation(by: b)
80+
/// ```
81+
///
82+
/// Anytime the "normal multiplication" `self * other` does not trap,
83+
/// `multipliedWithSaturation` produces the same result.
3084
@inlinable
3185
public func multipliedWithSaturation(by other: Self) -> Self {
3286
let (high, low) = multipliedFullWidth(by: other)
@@ -35,15 +89,29 @@ extension FixedWidthInteger {
3589
return Self.max &- high.signExtension
3690
}
3791

92+
/// Bitwise left with rounding and saturation.
93+
///
94+
/// `self` multiplied by the rational number 2^(`count`), saturated to the
95+
/// range `Self.min ... Self.max`, and rounded according to `rule`.
96+
///
97+
/// See `shifted(rightBy:rounding:)` for more discussion of rounding
98+
/// shifts with examples.
99+
///
100+
/// - Parameters:
101+
/// - leftBy count: the number of bits to shift by. If positive, this is a left-shift,
102+
/// and if negative a right shift.
103+
/// - rounding rule: the direction in which to round if `count` is negative.
38104
@inlinable
39105
public func shiftedWithSaturation<Count: BinaryInteger>(
40106
leftBy count: Count, rounding rule: RoundingRule = .down
41107
) -> Self {
42108
// If count is zero or negative, negate it and do a right
43109
// shift without saturation instead, as that's easier.
44110
guard count > 0 else {
45-
// TODO: fixup case where 0 - count overflows
46-
return shifted(rightBy: 0 - count, rounding: rule)
111+
return shifted(
112+
rightBy: Self(clamping: count).negatedWithSaturation(),
113+
rounding: rule
114+
)
47115
}
48116
guard count < Self.bitWidth else {
49117
// If count is bitWidth or greater, we always overflow

Sources/IntegerUtilities/ShiftWithRounding.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ extension BinaryInteger {
6262
// rounding, and then shifting the remaining bitWidth - 1 bits with
6363
// the desired rounding mode.
6464
let count = count - Count(bitWidth - 1)
65-
var floor = self >> count
65+
let floor = self >> count
6666
let lost = self - (floor << count)
67-
if lost != 0 { floor |= 1 } // insert sticky bit
68-
return floor.shifted(rightBy: bitWidth - 1, rounding: rule)
67+
let sticky = floor | (lost == 0 ? 0 : 1)
68+
return sticky.shifted(rightBy: bitWidth - 1, rounding: rule)
6969
}
7070
// Now we are in the happy case: 0 < count < bitWidth, which makes all
7171
// the math to handle rounding simpler.

0 commit comments

Comments
 (0)