Files
swift-mirror/validation-test/stdlib/IntegerDivision.swift
Stephen Canon 7a0c4b5e4a Add default implementations for FixedWidthInteger.dividingFullWidth (#70823)
These are provided for FixedWidthInteger & UnsignedInteger (the base implementation, following Knuth's Algorithm D) and SignedInteger (converting to magnitudes and calling the former). Previously no default implementations were available, requiring every type to implement these operations.

These defaults will not be optimal for large fixed-width integers, so types vending Int512 or similar integers should still provide their own implementations, but they are unconditionally available as a fallback, which simplifies the process of writing such types, and work well enough as a fallback for modest fixed-width integer types like Int64 or 32b or smaller platforms or Int128 on 64b platforms.

Additionally rework the concrete implementations to guarantee that we always trap when the quotient is not representable, and to improve performance for 64b integers on arm64_32, and added some new test coverage for these operations.
2024-01-12 15:25:01 -05:00

142 lines
4.5 KiB
Swift

//===--- IntegerDivision.swift.gyb ----------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===---------------------------------------------------------------------===//
// RUN: %target-run-simple-swift
// REQUIRES: executable_test
// REQUIRES: optimized_stdlib
import StdlibUnittest
var suite = TestSuite("Integer Division")
suite.test("Int8 division inbounds") {
for b in -128 ... 127 {
if b == 0 { continue }
let lb: Int = (b < 0 ? 128 : -129) * b + 1
let ub: Int = (b < 0 ? -129 : 128) * b - 1
for a in lb ... ub {
let high = Int8(a >> 8)
let low = UInt8(a & 0xff)
let (q, r) = Int8(b).dividingFullWidth(
(high: high, low: low)
)
expectTrue(r.magnitude < b.magnitude)
if r != 0 { expectEqual(r < 0, a < 0) }
expectTrue(r == a - b*Int(q))
}
}
}
suite.test("UInt8 division inbounds") {
for b in 0 ... 255 {
if b == 0 { continue }
for a in 0 ..< (b << 8) {
let high = UInt8(a >> 8)
let low = UInt8(a & 0xff)
let (q, r) = UInt8(b).dividingFullWidth(
(high: high, low: low)
)
expectTrue(r < b)
expectTrue(r == a - b*Int(q))
}
}
}
// Dead-simple deterministic random source to ensure that we always test
// the same "random" values.
struct WyRand: RandomNumberGenerator {
var state: UInt64
mutating func next() -> UInt64 {
state &+= 0xa076_1d64_78bd_642f
let p = state.multipliedFullWidth(by: state ^ 0xe703_7ed1_a0b4_28db)
return p.high ^ p.low
}
}
suite.test("Int32 division inbounds") {
let step: Int32 = 0x100_0000
var g = WyRand(state: 0)
for bhi in (-128 as Int32) ... 127 {
let b = bhi << 24 | Int32.random(in: 0 ..< step, using: &g)
for qhi in (-128 as Int32) ... 127 {
let q = qhi << 24 | Int32.random(in: 0 ..< step, using: &g)
let rmag = UInt32.random(in: 0 ..< b.magnitude, using: &g)
let p = Int64(b)*Int64(q)
let r = p < 0 ? -Int32(rmag) : Int32(rmag)
let a = p + Int64(r)
let high = Int32(a >> 32)
let low = UInt32(a & 0xffff_ffff)
let observed = b.dividingFullWidth((high: high, low: low))
expectEqual(observed.quotient, q)
expectEqual(observed.remainder, r)
}
}
}
suite.test("UInt32 division inbounds") {
let step: UInt32 = 0x100_0000
var g = WyRand(state: 0)
for bhi in UInt32.zero ... 255 {
let b = bhi << 24 | UInt32.random(in: 0 ..< step, using: &g)
for qhi in UInt32.zero ... 255 {
let q = qhi << 24 | UInt32.random(in: 0 ..< step, using: &g)
let r = UInt32.random(in: 0 ..< b, using: &g)
let a = UInt64(b)*UInt64(q) + UInt64(r)
let high = UInt32(a >> 32)
let low = UInt32(a & 0xffff_ffff)
let observed = b.dividingFullWidth((high: high, low: low))
expectEqual(observed.quotient, q)
expectEqual(observed.remainder, r)
}
}
}
suite.test("Int64 division inbounds") {
let step: Int64 = 0x100_0000_0000_0000
var g = WyRand(state: 0)
for bhi in (-128 as Int64) ... 127 {
let b = bhi << 56 | Int64.random(in: 0 ..< step, using: &g)
for qhi in (-128 as Int64) ... 127 {
let q = qhi << 56 | Int64.random(in: 0 ..< step, using: &g)
let rmag = UInt64.random(in: 0 ..< b.magnitude, using: &g)
let p = b.multipliedFullWidth(by: q)
let r = p.high < 0 ? -Int64(rmag) : Int64(rmag)
let (low, c) =
p.low.addingReportingOverflow(UInt64(truncatingIfNeeded: r))
let high = p.high &+ (r >> 63) &+ (c ? 1 : 0)
let observed = b.dividingFullWidth((high: high, low: low))
expectEqual(observed.quotient, q)
expectEqual(observed.remainder, r)
}
}
}
suite.test("UInt64 division inbounds") {
let step: UInt64 = 0x100_0000_0000_0000
var g = WyRand(state: 0)
for bhi in UInt64.zero ... 255 {
let b = bhi << 56 | UInt64.random(in: 0 ..< step, using: &g)
for qhi in UInt64.zero ... 255 {
let q = qhi << 56 | UInt64.random(in: 0 ..< step, using: &g)
let r = UInt64.random(in: 0 ..< b, using: &g)
let p = b.multipliedFullWidth(by: q)
let (low, c) = p.low.addingReportingOverflow(r)
let high = p.high &+ (c ? 1 : 0)
let observed = b.dividingFullWidth((high: high, low: low))
expectEqual(observed.quotient, q)
expectEqual(observed.remainder, r)
}
}
}
runAllTests()