Skip to content

Commit 53afff1

Browse files
Merge pull request #295 from stephentyrone/double-width-improvements
Improvements and bugfixes for DoubleWidth
2 parents e7af7df + 1043e9e commit 53afff1

File tree

2 files changed

+52
-22
lines changed

2 files changed

+52
-22
lines changed

Sources/_TestSupport/DoubleWidth.swift

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift Numerics open source project
44
//
5-
// Copyright (c) 2017-2021 Apple Inc. and the Swift Numerics project authors
5+
// Copyright (c) 2017-2024 Apple Inc. and the Swift Numerics project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -281,17 +281,11 @@ extension DoubleWidth : FixedWidthInteger {
281281
by rhs: DoubleWidth
282282
) -> (partialValue: DoubleWidth, overflow: Bool) {
283283
let (carry, product) = multipliedFullWidth(by: rhs)
284-
let result = DoubleWidth(truncatingIfNeeded: product)
285-
286-
let isNegative = DoubleWidth.isSigned &&
287-
(self < (0 as DoubleWidth)) != (rhs < (0 as DoubleWidth))
288-
let didCarry = isNegative
289-
? carry != ~(0 as DoubleWidth)
290-
: carry != (0 as DoubleWidth)
291-
let hadPositiveOverflow =
292-
DoubleWidth.isSigned && !isNegative && product.leadingZeroBitCount == 0
293-
294-
return (result, didCarry || hadPositiveOverflow)
284+
let partialValue = DoubleWidth(truncatingIfNeeded: product)
285+
// Overflow has occured if carry is not just the sign-extension of
286+
// partialValue (which is zero when Base is unsigned).
287+
let overflow = carry != (partialValue >> DoubleWidth.bitWidth)
288+
return (partialValue, overflow)
295289
}
296290

297291
public func quotientAndRemainder(
@@ -331,7 +325,11 @@ extension DoubleWidth : FixedWidthInteger {
331325
if DoubleWidth.isSigned && other == -1 && self == .min { return (0, true) }
332326
return (quotientAndRemainder(dividingBy: other).remainder, false)
333327
}
334-
328+
329+
// When using a pre-Swift 6.0 runtime, `&*` is not a protocol requirement of
330+
// FixedWidthInteger, which results in the default implementation of this
331+
// operation ending up recursively calling itself forever. In order to avoid
332+
// this, we keep the concrete implementation around.
335333
public func multipliedFullWidth(
336334
by other: DoubleWidth
337335
) -> (high: DoubleWidth, low: DoubleWidth.Magnitude) {
@@ -453,18 +451,21 @@ extension DoubleWidth : FixedWidthInteger {
453451

454452
/// Returns this value "masked" by its bit width.
455453
///
456-
/// "Masking" notionally involves repeatedly incrementing or decrementing this
457-
/// value by `self.bitWidth` until the result is contained in the range
458-
/// `0..<self.bitWidth`.
454+
/// "Masking" notionally involves repeatedly incrementing or decrementing
455+
/// this value by `self.bitWidth` until the result is contained in the
456+
/// range `0..<self.bitWidth`.
459457
internal func _masked() -> DoubleWidth {
460-
// FIXME(integers): test types with bit widths that aren't powers of 2
458+
let bits = DoubleWidth(DoubleWidth.bitWidth)
461459
if DoubleWidth.bitWidth.nonzeroBitCount == 1 {
462-
return self & DoubleWidth(DoubleWidth.bitWidth &- 1)
460+
return self & (bits &- 1)
463461
}
464-
if DoubleWidth.isSigned && self._storage.high < (0 as High) {
465-
return self % DoubleWidth(DoubleWidth.bitWidth) + self
466-
}
467-
return self % DoubleWidth(DoubleWidth.bitWidth)
462+
let reduced = self % bits
463+
// bitWidth is always positive, but the value being reduced might have
464+
// been negative, in which case reduced will also be negative. We need
465+
// the representative in [0, bitWidth), so conditionally add the count
466+
// to get the positive residue.
467+
if Base.isSigned && reduced < 0 { return reduced &+ bits }
468+
return reduced
468469
}
469470

470471
public static func &<<=(lhs: inout DoubleWidth, rhs: DoubleWidth) {
@@ -590,6 +591,31 @@ extension DoubleWidth : FixedWidthInteger {
590591
precondition(!overflow, "Overflow in %=")
591592
lhs = result
592593
}
594+
595+
public static func &+(
596+
lhs: DoubleWidth, rhs: DoubleWidth
597+
) -> DoubleWidth {
598+
let (low, carry) = lhs.low.addingReportingOverflow(rhs.low)
599+
let high = lhs.high &+ rhs.high &+ (carry ? 1 : 0)
600+
return DoubleWidth(high, low)
601+
}
602+
603+
public static func &-(
604+
lhs: DoubleWidth, rhs: DoubleWidth
605+
) -> DoubleWidth {
606+
let (low, borrow) = lhs.low.subtractingReportingOverflow(rhs.low)
607+
let high = lhs.high &- rhs.high &- (borrow ? 1 : 0)
608+
return DoubleWidth(high, low)
609+
}
610+
611+
public static func &*(
612+
lhs: DoubleWidth, rhs: DoubleWidth
613+
) -> DoubleWidth {
614+
let p00 = lhs.low.multipliedFullWidth(by: rhs.low)
615+
let p10 = lhs.high &* Base(truncatingIfNeeded: rhs.low)
616+
let p01 = Base(truncatingIfNeeded: lhs.low) &* rhs.high
617+
return DoubleWidth(p10 &+ p01 &+ Base(truncatingIfNeeded: p00.high), p00.low)
618+
}
593619

594620
public init(_truncatingBits bits: UInt) {
595621
_storage.low = Low(_truncatingBits: bits)

Tests/IntegerUtilitiesTests/DoubleWidthTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,4 +522,8 @@ final class DoubleWidthTests: XCTestCase {
522522
checkUnsignedIntegerConformance(0 as _UInt128)
523523
checkUnsignedIntegerConformance(0 as _UInt1024)
524524
}
525+
526+
func testMultiplyOverflow() {
527+
XCTAssertFalse(_Int128(-1).multipliedReportingOverflow(by: 0).overflow)
528+
}
525529
}

0 commit comments

Comments
 (0)