Skip to content

Commit f00824e

Browse files
committed
More tests for saturating arithmetic -- exhaustive sampling of [U]Int8 arguments.
1 parent 4779aef commit f00824e

File tree

4 files changed

+182
-54
lines changed

4 files changed

+182
-54
lines changed

Sources/IntegerUtilities/SaturatingArithmetic.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ extension FixedWidthInteger {
5454
public func subtractingWithSaturation(_ other: Self) -> Self {
5555
let (wrapped, overflow) = subtractingReportingOverflow(other)
5656
if !overflow { return wrapped }
57-
return Self.max &- sextOrZext
57+
return Self.isSigned ? Self.max &- sextOrZext : 0
5858
}
5959

6060
/// Saturating integer negation
@@ -144,7 +144,6 @@ extension FixedWidthInteger {
144144
let valueBits = Self.bitWidth &- (Self.isSigned ? 1 : 0)
145145
let wrapped = self &<< count
146146
let complement = valueBits &- Int(count)
147-
if self &>> complement == sextOrZext { return wrapped }
148-
return clamped
147+
return self &>> complement == sextOrZext ? wrapped : clamped
149148
}
150149
}

Tests/IntegerUtilitiesTests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ add_library(IntegerUtilitiesTests
1212
DoubleWidthTests.swift
1313
GCDTests.swift
1414
RotateTests.swift
15+
SaturatingArithmeticTests.swift
1516
ShiftTests.swift)
1617
target_compile_options(IntegerUtilitiesTests PRIVATE
1718
-enable-testing)
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//===--- SaturatingArithmeticTests.swift ----------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import IntegerUtilities
14+
import XCTest
15+
import _TestSupport
16+
17+
final class IntegerUtilitiesSaturatingTests: XCTestCase {
18+
19+
func testSaturatingAddSigned() {
20+
for a in Int8.min ... Int8.max {
21+
for b in Int8.min ... Int8.max {
22+
let expected = Int8(clamping: Int16(a) + Int16(b))
23+
let observed = a.addingWithSaturation(b)
24+
if expected != observed {
25+
print("Error found in (\(a)).addingWithSaturation(\(b)).")
26+
print("Expected: \(String(expected, radix: 16))")
27+
print("Observed: \(String(observed, radix: 16))")
28+
XCTFail()
29+
return
30+
}
31+
}
32+
}
33+
}
34+
35+
func testSaturatingSubtractSigned() {
36+
for a in Int8.min ... Int8.max {
37+
for b in Int8.min ... Int8.max {
38+
let expected = Int8(clamping: Int16(a) - Int16(b))
39+
let observed = a.subtractingWithSaturation(b)
40+
if expected != observed {
41+
print("Error found in (\(a)).subtractingWithSaturation(\(b)).")
42+
print("Expected: \(String(expected, radix: 16))")
43+
print("Observed: \(String(observed, radix: 16))")
44+
XCTFail()
45+
return
46+
}
47+
}
48+
}
49+
}
50+
51+
func testSaturatingNegation() {
52+
for a in Int8.min ... Int8.max {
53+
let expected = Int8(clamping: 0 - Int16(a))
54+
let observed = a.negatedWithSaturation()
55+
if expected != observed {
56+
print("Error found in (\(a)).negatedWithSaturation().")
57+
print("Expected: \(String(expected, radix: 16))")
58+
print("Observed: \(String(observed, radix: 16))")
59+
XCTFail()
60+
return
61+
}
62+
}
63+
}
64+
65+
func testSaturatingMultiplicationSigned() {
66+
for a in Int8.min ... Int8.max {
67+
for b in Int8.min ... Int8.max {
68+
let expected = Int8(clamping: Int16(a) * Int16(b))
69+
let observed = a.multipliedWithSaturation(by: b)
70+
if expected != observed {
71+
print("Error found in (\(a)).multipliedWithSaturation(by: \(b)).")
72+
print("Expected: \(String(expected, radix: 16))")
73+
print("Observed: \(String(observed, radix: 16))")
74+
XCTFail()
75+
return
76+
}
77+
}
78+
}
79+
}
80+
81+
func testSaturatingAddUnsigned() {
82+
for a in UInt8.min ... UInt8.max {
83+
for b in UInt8.min ... UInt8.max {
84+
let expected = UInt8(clamping: UInt16(a) + UInt16(b))
85+
let observed = a.addingWithSaturation(b)
86+
if expected != observed {
87+
print("Error found in (\(a)).addingWithSaturation(\(b)).")
88+
print("Expected: \(String(expected, radix: 16))")
89+
print("Observed: \(String(observed, radix: 16))")
90+
XCTFail()
91+
return
92+
}
93+
}
94+
}
95+
}
96+
97+
func testSaturatingSubtractUnsigned() {
98+
for a in UInt8.min ... UInt8.max {
99+
for b in UInt8.min ... UInt8.max {
100+
let expected = UInt8(clamping: Int16(a) - Int16(b))
101+
let observed = a.subtractingWithSaturation(b)
102+
if expected != observed {
103+
print("Error found in (\(a)).subtractingWithSaturation(\(b)).")
104+
print("Expected: \(String(expected, radix: 16))")
105+
print("Observed: \(String(observed, radix: 16))")
106+
XCTFail()
107+
return
108+
}
109+
}
110+
}
111+
}
112+
113+
func testSaturatingMultiplicationUnsigned() {
114+
for a in UInt8.min ... UInt8.max {
115+
for b in UInt8.min ... UInt8.max {
116+
let expected = UInt8(clamping: UInt16(a) * UInt16(b))
117+
let observed = a.multipliedWithSaturation(by: b)
118+
if expected != observed {
119+
print("Error found in (\(a)).multipliedWithSaturation(by: \(b)).")
120+
print("Expected: \(String(expected, radix: 16))")
121+
print("Observed: \(String(observed, radix: 16))")
122+
XCTFail()
123+
}
124+
}
125+
}
126+
}
127+
128+
func testSaturatingShift<T, C>(
129+
_ value: T, _ count: C, rounding rule: RoundingRule
130+
) where T: FixedWidthInteger, C: FixedWidthInteger {
131+
let observed = value.shiftedWithSaturation(leftBy: count, rounding: rule)
132+
var expected: T = 0
133+
if count <= 0 {
134+
expected = value.shifted(rightBy: -Int64(count), rounding: rule)
135+
} else {
136+
let multiplier: T = 1 << count
137+
if multiplier <= 0 {
138+
expected = value == 0 ? 0 :
139+
value < 0 ? .min : .max
140+
} else {
141+
expected = value.multipliedWithSaturation(by: multiplier)
142+
}
143+
}
144+
if observed != expected {
145+
print("Error found in \(T.self).shiftedWithSaturation(leftBy: \(count), rounding: \(rule)).")
146+
print(" Value: \(String(value, radix: 16))")
147+
print("Expected: \(String(expected, radix: 16))")
148+
print("Observed: \(String(observed, radix: 16))")
149+
XCTFail()
150+
return
151+
}
152+
}
153+
154+
func testSaturatingShift<T: FixedWidthInteger>(
155+
_ type: T.Type, rounding rule: RoundingRule
156+
) {
157+
for count in Int8.min ... .max {
158+
testSaturatingShift(0, count, rounding: rule)
159+
for bits in 0 ..< T.bitWidth {
160+
let msb: T.Magnitude = 1 << bits
161+
let value = T(truncatingIfNeeded: msb) | .random(in: 0 ... T(msb-1))
162+
testSaturatingShift(value, count, rounding: rule)
163+
testSaturatingShift(0 &- value, count, rounding: rule)
164+
}
165+
}
166+
}
167+
168+
func testSaturatingShifts() {
169+
testSaturatingShift(Int8.self, rounding: .toOdd)
170+
testSaturatingShift(UInt8.self, rounding: .toOdd)
171+
testSaturatingShift(Int.self, rounding: .toOdd)
172+
testSaturatingShift(UInt.self, rounding: .toOdd)
173+
}
174+
175+
func testEdgeCaseForNegativeCount() {
176+
XCTAssertEqual(1.shiftedWithSaturation(leftBy: Int.min), 0)
177+
}
178+
179+
}

Tests/IntegerUtilitiesTests/ShiftTests.swift

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -194,55 +194,4 @@ final class IntegerUtilitiesShiftTests: XCTestCase {
194194
testStochasticAverage(DoubleWidth<Int64>.random(in: .min ... .max))
195195
testStochasticAverage(DoubleWidth<UInt64>.random(in: .min ... .max))
196196
}
197-
198-
func testSaturatingShift<T, C>(
199-
_ value: T, _ count: C, rounding rule: RoundingRule
200-
) where T: FixedWidthInteger, C: FixedWidthInteger {
201-
let observed = value.shiftedWithSaturation(leftBy: count, rounding: rule)
202-
var expected: T = 0
203-
if count <= 0 {
204-
expected = value.shifted(rightBy: -Int64(count), rounding: rule)
205-
} else {
206-
let multiplier: T = 1 << count
207-
if multiplier <= 0 {
208-
expected = value == 0 ? 0 :
209-
value < 0 ? .min : .max
210-
} else {
211-
expected = value.multipliedWithSaturation(by: multiplier)
212-
}
213-
}
214-
if observed != expected {
215-
print("Error found in \(T.self).shiftedWithSaturation(leftBy: \(count), rounding: \(rule)).")
216-
print(" Value: \(String(value, radix: 16))")
217-
print("Expected: \(String(expected, radix: 16))")
218-
print("Observed: \(String(observed, radix: 16))")
219-
XCTFail()
220-
return
221-
}
222-
}
223-
224-
func testSaturatingShift<T: FixedWidthInteger>(
225-
_ type: T.Type, rounding rule: RoundingRule
226-
) {
227-
for count in Int8.min ... .max {
228-
testSaturatingShift(0, count, rounding: rule)
229-
for bits in 0 ..< T.bitWidth {
230-
let msb: T.Magnitude = 1 << bits
231-
let value = T(truncatingIfNeeded: msb) | .random(in: 0 ... T(msb-1))
232-
testSaturatingShift(value, count, rounding: rule)
233-
testSaturatingShift(0 &- value, count, rounding: rule)
234-
}
235-
}
236-
}
237-
238-
func testSaturatingShifts() {
239-
testSaturatingShift(Int8.self, rounding: .toOdd)
240-
testSaturatingShift(UInt8.self, rounding: .toOdd)
241-
testSaturatingShift(Int.self, rounding: .toOdd)
242-
testSaturatingShift(UInt.self, rounding: .toOdd)
243-
}
244-
245-
func testEdgeCaseForNegativeCount() {
246-
XCTAssertEqual(1.shiftedWithSaturation(leftBy: Int.min), 0)
247-
}
248197
}

0 commit comments

Comments
 (0)