11
11
12
12
extension FixedWidthInteger {
13
13
@_transparent @usableFromInline
14
- var signExtension : Self { self & >> - 1 }
14
+ var sextOrZext : Self { self >> Self . bitWidth }
15
15
16
16
/// Saturating integer addition
17
17
///
@@ -30,7 +30,7 @@ extension FixedWidthInteger {
30
30
public func addingWithSaturation( _ other: Self ) -> Self {
31
31
let ( wrapped, overflow) = addingReportingOverflow ( other)
32
32
if !overflow { return wrapped }
33
- return Self . max &- signExtension
33
+ return Self . max &- sextOrZext
34
34
}
35
35
36
36
/// Saturating integer subtraction
@@ -54,7 +54,7 @@ extension FixedWidthInteger {
54
54
public func subtractingWithSaturation( _ other: Self ) -> Self {
55
55
let ( wrapped, overflow) = subtractingReportingOverflow ( other)
56
56
if !overflow { return wrapped }
57
- return Self . max &- signExtension
57
+ return Self . max &- sextOrZext
58
58
}
59
59
60
60
/// Saturating integer negation
@@ -85,8 +85,8 @@ extension FixedWidthInteger {
85
85
public func multipliedWithSaturation( by other: Self ) -> Self {
86
86
let ( high, low) = multipliedFullWidth ( by: other)
87
87
let wrapped = Self ( truncatingIfNeeded: low)
88
- if high == wrapped. signExtension { return wrapped }
89
- return Self . max &- high. signExtension
88
+ if high == wrapped. sextOrZext { return wrapped }
89
+ return Self . max &- high. sextOrZext
90
90
}
91
91
92
92
/// Bitwise left with rounding and saturation.
@@ -106,23 +106,45 @@ extension FixedWidthInteger {
106
106
leftBy count: Count , rounding rule: RoundingRule = . down
107
107
) -> Self {
108
108
// If count is zero or negative, negate it and do a right
109
- // shift without saturation instead, as that's easier.
109
+ // shift without saturation instead, since we already have
110
+ // that implemented.
110
111
guard count > 0 else {
112
+ // negating count is tricky, because count's type can be
113
+ // an arbitrary BinaryInteger; in particular, it could be
114
+ // .min of a signed type, so that its negation cannot be
115
+ // represented in the same type. Fortunately, Int64 is
116
+ // always big enough to represent arbitrary shifts of
117
+ // arbitrary types, so we can use that as an intermediate
118
+ // type, and then we can use negatedWithSaturation() to
119
+ // handle the .min case.
120
+ let int64Count = Int64 ( clamping: count)
111
121
return shifted (
112
- rightBy: Self ( clamping : count ) . negatedWithSaturation ( ) ,
122
+ rightBy: int64Count . negatedWithSaturation ( ) ,
113
123
rounding: rule
114
124
)
115
125
}
126
+ let clamped = Self . max &- sextOrZext
116
127
guard count < Self . bitWidth else {
117
128
// If count is bitWidth or greater, we always overflow
118
129
// unless self is zero.
119
- return self == 0 ? 0 : Self . max &- signExtension
130
+ return self == 0 ? 0 : clamped
120
131
}
121
132
// Now we have 0 < count < bitWidth, so we can use a nice
122
- // straightforward implementation; the shift overflows if
123
- // the complementary shift doesn't match sign extension.
124
- let wrapped = self << count
125
- if self &>> ~ count == signExtension { return wrapped }
126
- return Self . max &- signExtension
133
+ // straightforward implementation; a shift overflows if
134
+ // the complementary shift doesn't match sign-or-zero
135
+ // extension. E.g.:
136
+ //
137
+ // - signed 0b0010_1111 << 2 overflows, because
138
+ // 0b0010_1111 >> 5 is 0b0000_0001, which does not
139
+ // equal 0b0000_0000
140
+ //
141
+ // - unsigned 0b0010_1111 << 2 does not overflow,
142
+ // because 0b0010_0000 >> 6 is 0b0000_0000, which
143
+ // does equal 0b0000_0000.
144
+ let valueBits = Self . bitWidth &- ( Self . isSigned ? 1 : 0 )
145
+ let wrapped = self &<< count
146
+ let complement = valueBits &- Int ( count)
147
+ if self &>> complement == sextOrZext { return wrapped }
148
+ return clamped
127
149
}
128
150
}
0 commit comments