Skip to content

Commit fa3744e

Browse files
committed
[SE-0368] StaticBigInt: add the Swift wrapper type
1 parent 8f9cf7b commit fa3744e

File tree

4 files changed

+371
-2
lines changed

4 files changed

+371
-2
lines changed

stdlib/public/core/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# This source file is part of the Swift.org open source project
44
#
5-
# Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
5+
# Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
66
# Licensed under Apache License v2.0 with Runtime Library Exception
77
#
88
# See https://swift.org/LICENSE.txt for license information
@@ -226,6 +226,7 @@ set(SWIFTLIB_SOURCES
226226
PlaygroundDisplay.swift
227227
CommandLine.swift
228228
SliceBuffer.swift
229+
StaticBigInt.swift
229230
UnfoldSequence.swift
230231
VarArgs.swift
231232
Zip.swift

stdlib/public/core/GroupInfo.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@
167167
"Integers": [
168168
"Integers.swift",
169169
"IntegerTypes.swift",
170-
"IntegerParsing.swift"],
170+
"IntegerParsing.swift",
171+
"StaticBigInt.swift"],
171172
"Floating": [
172173
"FloatingPoint.swift",
173174
"FloatingPointParsing.swift",

stdlib/public/core/StaticBigInt.swift

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 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+
/// An immutable arbitrary-precision signed integer.
14+
///
15+
/// `StaticBigInt` is primarily intended to be used as the associated type of an
16+
/// `ExpressibleByIntegerLiteral` conformance.
17+
@available(SwiftStdlib 5.8, *)
18+
@frozen
19+
public struct StaticBigInt:
20+
_ExpressibleByBuiltinIntegerLiteral,
21+
ExpressibleByIntegerLiteral,
22+
Sendable
23+
{
24+
@available(SwiftStdlib 5.8, *)
25+
@usableFromInline
26+
internal let _value: Builtin.IntLiteral
27+
28+
@available(SwiftStdlib 5.8, *)
29+
@inlinable
30+
public init(_builtinIntegerLiteral value: Builtin.IntLiteral) {
31+
_value = value
32+
}
33+
34+
/// Returns the given value unchanged.
35+
@_alwaysEmitIntoClient
36+
public static prefix func + (_ rhs: Self) -> Self {
37+
rhs
38+
}
39+
}
40+
41+
//===----------------------------------------------------------------------===//
42+
// MARK: - Binary Representation
43+
//===----------------------------------------------------------------------===//
44+
45+
@available(SwiftStdlib 5.8, *)
46+
extension StaticBigInt {
47+
48+
/// Returns `-1`, `0`, or `+1` to indicate whether this value is less than,
49+
/// equal to, or greater than zero.
50+
///
51+
/// The return type is `Int` rather than `Self`.
52+
@available(SwiftStdlib 5.8, *)
53+
@inlinable
54+
public func signum() -> Int {
55+
_isNegative ? -1 : (bitWidth == 1) ? 0 : +1
56+
}
57+
58+
@available(SwiftStdlib 5.8, *)
59+
@inlinable
60+
internal var _isNegative: Bool {
61+
Bool(Builtin.isNegative_IntLiteral(_value))
62+
}
63+
64+
/// Returns the minimal number of bits in this value's binary representation,
65+
/// including the sign bit, and excluding the sign extension.
66+
///
67+
/// The following examples show the least significant byte of each value's
68+
/// binary representation, separated into excluded and included bits. Negative
69+
/// values are in two's complement.
70+
///
71+
/// * `-4` (`0b11111_100`) is 3 bits.
72+
/// * `-3` (`0b11111_101`) is 3 bits.
73+
/// * `-2` (`0b111111_10`) is 2 bits.
74+
/// * `-1` (`0b1111111_1`) is 1 bit.
75+
/// * `+0` (`0b0000000_0`) is 1 bit.
76+
/// * `+1` (`0b000000_01`) is 2 bits.
77+
/// * `+2` (`0b00000_010`) is 3 bits.
78+
/// * `+3` (`0b00000_011`) is 3 bits.
79+
@available(SwiftStdlib 5.8, *)
80+
@inlinable
81+
public var bitWidth: Int {
82+
Int(Builtin.bitWidth_IntLiteral(_value))
83+
}
84+
85+
/// Returns a 32-bit or 64-bit word of this value's binary representation.
86+
///
87+
/// The words are ordered from least significant to most significant, with
88+
/// an infinite sign extension. Negative values are in two's complement.
89+
///
90+
/// let negative: StaticBigInt = -0x0011223344556677_8899AABBCCDDEEFF
91+
/// negative.signum() //-> -1
92+
/// negative.bitWidth //-> 118
93+
/// negative[0] //-> 0x7766554433221101
94+
/// negative[1] //-> 0xFFEEDDCCBBAA9988
95+
/// negative[2] //-> 0xFFFFFFFFFFFFFFFF
96+
///
97+
/// let positive: StaticBigInt = +0x0011223344556677_8899AABBCCDDEEFF
98+
/// positive.signum() //-> +1
99+
/// positive.bitWidth //-> 118
100+
/// positive[0] //-> 0x8899AABBCCDDEEFF
101+
/// positive[1] //-> 0x0011223344556677
102+
/// positive[2] //-> 0x0000000000000000
103+
///
104+
/// - Parameter wordIndex: A nonnegative zero-based offset.
105+
@available(SwiftStdlib 5.8, *)
106+
@inlinable
107+
public subscript(_ wordIndex: Int) -> UInt {
108+
_precondition(wordIndex >= 0, "Negative word index")
109+
let bitIndex = wordIndex.multipliedReportingOverflow(by: UInt.bitWidth)
110+
guard !bitIndex.overflow, bitIndex.partialValue < bitWidth else {
111+
return _isNegative ? ~0 : 0
112+
}
113+
return UInt(
114+
Builtin.wordAtIndex_IntLiteral(_value, wordIndex._builtinWordValue)
115+
)
116+
}
117+
}
118+
119+
//===----------------------------------------------------------------------===//
120+
// MARK: - Textual Representation
121+
//===----------------------------------------------------------------------===//
122+
123+
@available(SwiftStdlib 5.8, *)
124+
extension StaticBigInt: CustomDebugStringConvertible {
125+
126+
@available(SwiftStdlib 5.8, *)
127+
public var debugDescription: String {
128+
let isNegative = _isNegative
129+
let indicator = isNegative ? "-0x" : "+0x"
130+
131+
// Equivalent to `ceil(bitWidthExcludingSignBit / fourBitsPerHexDigit)`.
132+
// Underestimated for `-(16 ** y)` values (e.g. "-0x1", "-0x10", "-0x100").
133+
let capacity = indicator.utf8.count + (((bitWidth - 1) + 3) / 4)
134+
var result = String(unsafeUninitializedCapacity: capacity) { utf8 in
135+
136+
// Pre-initialize with zeros, ignoring extra capacity.
137+
var utf8 = UnsafeMutableBufferPointer(rebasing: utf8.prefix(capacity))
138+
utf8.initialize(repeating: UInt8(ascii: "0"))
139+
140+
// Use a 32-bit element type, to generate small hexadecimal strings.
141+
typealias Element = UInt32
142+
let hexDigitsPerElement = Element.bitWidth / 4
143+
_internalInvariant(hexDigitsPerElement <= _SmallString.capacity)
144+
_internalInvariant(UInt.bitWidth.isMultiple(of: Element.bitWidth))
145+
146+
// Lazily compute the magnitude, starting with the least significant bits.
147+
var overflow = isNegative
148+
for bitIndex in stride(from: 0, to: bitWidth, by: Element.bitWidth) {
149+
let wordIndex = bitIndex >> UInt.bitWidth.trailingZeroBitCount
150+
var element = Element(_truncatingBits: self[wordIndex] &>> bitIndex)
151+
if isNegative {
152+
element = ~element
153+
if overflow {
154+
(element, overflow) = element.addingReportingOverflow(1)
155+
}
156+
}
157+
158+
// Overwrite trailing zeros with hexadecimal digits.
159+
let hexDigits = String(element, radix: 16, uppercase: true)
160+
_ = UnsafeMutableBufferPointer(
161+
rebasing: utf8.suffix(hexDigits.utf8.count)
162+
).initialize(from: hexDigits.utf8)
163+
utf8 = UnsafeMutableBufferPointer(
164+
rebasing: utf8.dropLast(hexDigitsPerElement)
165+
)
166+
}
167+
return capacity
168+
}
169+
170+
// Overwrite leading zeros with the "±0x" indicator.
171+
if let upToIndex = result.firstIndex(where: { $0 != "0" }) {
172+
result.replaceSubrange(..<upToIndex, with: indicator)
173+
} else {
174+
result = "+0x0"
175+
}
176+
return result
177+
}
178+
}
179+
180+
#if SWIFT_ENABLE_REFLECTION
181+
@available(SwiftStdlib 5.8, *)
182+
extension StaticBigInt: CustomReflectable {
183+
184+
@available(SwiftStdlib 5.8, *)
185+
public var customMirror: Mirror {
186+
Mirror(self, unlabeledChildren: EmptyCollection<Void>())
187+
}
188+
}
189+
#endif

test/stdlib/StaticBigInt.swift

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022 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+
// RUN: %target-run-simple-swift(-parse-as-library)
14+
// REQUIRES: executable_test
15+
// REQUIRES: PTRSIZE=64
16+
// REQUIRES: reflection
17+
// END.
18+
//
19+
//===----------------------------------------------------------------------===//
20+
21+
import StdlibUnittest
22+
23+
@available(SwiftStdlib 5.8, *)
24+
@main
25+
final class StaticBigIntTests {
26+
27+
@available(SwiftStdlib 5.8, *)
28+
static func main() {
29+
let testCase = StaticBigIntTests()
30+
let testSuite = TestSuite("StaticBigIntTests")
31+
testSuite.test("BinaryRepresentation", testCase.testBinaryRepresentation)
32+
testSuite.test("TextualRepresentation", testCase.testTextualRepresentation)
33+
testSuite.test("WrapperAssociatedType", testCase.testWrapperAssociatedType)
34+
runAllTests()
35+
}
36+
}
37+
38+
//===----------------------------------------------------------------------===//
39+
// MARK: - Binary Representation
40+
//===----------------------------------------------------------------------===//
41+
42+
@available(SwiftStdlib 5.8, *)
43+
extension StaticBigIntTests {
44+
45+
@available(SwiftStdlib 5.8, *)
46+
func testBinaryRepresentation() {
47+
typealias Expected = (signum: Int, bitWidth: Int, words: [UInt])
48+
let m = UInt(bitPattern: .min)
49+
let keyValuePairs: KeyValuePairs<StaticBigInt, Expected> = [
50+
-0x80000000000000000000000000000002: (-1, 129, [~1, ~m, ~0]),
51+
-0x80000000000000000000000000000001: (-1, 129, [~0, ~m, ~0]),
52+
-0x80000000000000000000000000000000: (-1, 128, [ 0, m, ~0]),
53+
-0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF: (-1, 128, [ 1, m, ~0]),
54+
-0x4: (-1, 3, [~3, ~0, ~0]),
55+
-0x3: (-1, 3, [~2, ~0, ~0]),
56+
-0x2: (-1, 2, [~1, ~0, ~0]),
57+
-0x1: (-1, 1, [~0, ~0, ~0]),
58+
+0x0: ( 0, 1, [ 0, 0, 0]),
59+
+0x1: (+1, 2, [ 1, 0, 0]),
60+
+0x2: (+1, 3, [ 2, 0, 0]),
61+
+0x3: (+1, 3, [ 3, 0, 0]),
62+
+0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE: (+1, 128, [~1, ~m, 0]),
63+
+0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF: (+1, 128, [~0, ~m, 0]),
64+
+0x80000000000000000000000000000000: (+1, 129, [ 0, m, 0]),
65+
+0x80000000000000000000000000000001: (+1, 129, [ 1, m, 0]),
66+
]
67+
for (actual, expected) in keyValuePairs {
68+
expectEqual(expected.signum, actual.signum())
69+
expectEqual(expected.bitWidth, actual.bitWidth)
70+
expectEqual(expected.words[0], actual[0])
71+
expectEqual(expected.words[1], actual[1])
72+
expectEqual(expected.words[2], actual[2])
73+
}
74+
}
75+
}
76+
77+
//===----------------------------------------------------------------------===//
78+
// MARK: - Textual Representation
79+
//===----------------------------------------------------------------------===//
80+
81+
@available(SwiftStdlib 5.8, *)
82+
extension StaticBigIntTests {
83+
84+
@available(SwiftStdlib 5.8, *)
85+
func testTextualRepresentation() {
86+
let keyValuePairs: KeyValuePairs<StaticBigInt, String> = [
87+
-0x80000000000000000000000000000002: "-0x80000000000000000000000000000002",
88+
-0x80000000000000000000000000000001: "-0x80000000000000000000000000000001",
89+
-0x80000000000000000000000000000000: "-0x80000000000000000000000000000000",
90+
-0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF: "-0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
91+
-0x123456789ABCDE: "-0x123456789ABCDE",
92+
-0x123456789ABCD: "-0x123456789ABCD",
93+
-0x123456789ABC: "-0x123456789ABC",
94+
-0x1000: "-0x1000",
95+
-0x100: "-0x100",
96+
-0x10: "-0x10",
97+
-0x4: "-0x4",
98+
-0x3: "-0x3",
99+
-0x2: "-0x2",
100+
-0x1: "-0x1",
101+
+0x0: "+0x0",
102+
+0x1: "+0x1",
103+
+0x2: "+0x2",
104+
+0x3: "+0x3",
105+
+0x10: "+0x10",
106+
+0x100: "+0x100",
107+
+0x1000: "+0x1000",
108+
+0x123456789ABC: "+0x123456789ABC",
109+
+0x123456789ABCD: "+0x123456789ABCD",
110+
+0x123456789ABCDE: "+0x123456789ABCDE",
111+
+0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE: "+0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE",
112+
+0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF: "+0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
113+
+0x80000000000000000000000000000000: "+0x80000000000000000000000000000000",
114+
+0x80000000000000000000000000000001: "+0x80000000000000000000000000000001",
115+
]
116+
for (actual, expected) in keyValuePairs {
117+
expectEqual(expected, String(reflecting: actual))
118+
}
119+
}
120+
}
121+
122+
//===----------------------------------------------------------------------===//
123+
// MARK: - Wrapper Associated Type
124+
//===----------------------------------------------------------------------===//
125+
126+
@available(SwiftStdlib 5.8, *)
127+
extension StaticBigIntTests {
128+
129+
@available(SwiftStdlib 5.8, *)
130+
struct Wrapper: ExpressibleByIntegerLiteral {
131+
132+
@available(SwiftStdlib 5.8, *)
133+
let actual: StaticBigInt
134+
135+
@available(SwiftStdlib 5.8, *)
136+
init(integerLiteral actual: StaticBigInt) {
137+
self.actual = actual
138+
}
139+
}
140+
141+
@available(SwiftStdlib 5.8, *)
142+
func testWrapperAssociatedType() {
143+
do {
144+
let negative = Wrapper(-0x0011223344556677_8899AABBCCDDEEFF)
145+
expectEqual( -1, negative.actual.signum())
146+
expectEqual(118, negative.actual.bitWidth)
147+
expectEqual(0x7766554433221101, negative.actual[0])
148+
expectEqual(0xFFEEDDCCBBAA9988, negative.actual[1])
149+
expectEqual(0xFFFFFFFFFFFFFFFF, negative.actual[2])
150+
expectEqual(0xFFFFFFFFFFFFFFFF, negative.actual[.max])
151+
}
152+
do {
153+
let positive = Wrapper(0x0011223344556677_8899AABBCCDDEEFF)
154+
expectEqual( +1, positive.actual.signum())
155+
expectEqual(118, positive.actual.bitWidth)
156+
expectEqual(0x8899AABBCCDDEEFF, positive.actual[0])
157+
expectEqual(0x0011223344556677, positive.actual[1])
158+
expectEqual(0x0000000000000000, positive.actual[2])
159+
expectEqual(0x0000000000000000, positive.actual[.max])
160+
}
161+
do {
162+
let fibonacciSequence = Wrapper(
163+
0xA94FAD42221F2702_68A3DD8E61ECCFBD_40ABCFB3C0325745_27F80DDAA1BA7878_18B3C1D91E77DECD_0F444C01834299AB_096F75D79B354522_05D4D629E80D5489_039A9FADB327F099_023A367C34E563F0_016069317E428CA9_00D9CD4AB6A2D747_00869BE6C79FB562_00533163EF0321E5_00336A82D89C937D_001FC6E116668E68_0013A3A1C2360515_000C233F54308953_000780626E057BC2_0004A2DCE62B0D91_0002DD8587DA6E31_0001C5575E509F60_0001182E2989CED1_0000AD2934C6D08F_00006B04F4C2FE42_000042244003D24D_000028E0B4BF2BF5_000019438B44A658_00000F9D297A859D_000009A661CA20BB_000005F6C7B064E2_000003AF9A19BBD9_000002472D96A909_000001686C8312D0_000000DEC1139639_00000089AB6F7C97_0000005515A419A2_0000003495CB62F5_000000207FD8B6AD_0000001415F2AC48_0000000C69E60A65_00000007AC0CA1E3_00000004BDD96882_00000002EE333961_00000001CFA62F21_000000011E8D0A40_00000000B11924E1_000000006D73E55F_0000000043A53F82_0000000029CEA5DD_0000000019D699A5_000000000FF80C38_0000000009DE8D6D_0000000006197ECB_0000000003C50EA2_0000000002547029_0000000001709E79_0000000000E3D1B0_00000000008CCCC9_00000000005704E7_000000000035C7E2_0000000000213D05_0000000000148ADD_00000000000CB228_000000000007D8B5_000000000004D973_000000000002FF42_000000000001DA31_0000000000012511_000000000000B520_0000000000006FF1_000000000000452F_0000000000002AC2_0000000000001A6D_0000000000001055_0000000000000A18_000000000000063D_00000000000003DB_0000000000000262_0000000000000179_00000000000000E9_0000000000000090_0000000000000059_0000000000000037_0000000000000022_0000000000000015_000000000000000D_0000000000000008_0000000000000005_0000000000000003_0000000000000002_0000000000000001_0000000000000001_0000000000000000
164+
)
165+
expectEqual(
166+
1 + (94 * UInt.bitWidth), //-> 6017 bits.
167+
fibonacciSequence.actual.bitWidth
168+
)
169+
for wordIndex in 2..<94 {
170+
expectEqual(
171+
fibonacciSequence.actual[wordIndex],
172+
fibonacciSequence.actual[wordIndex - 1] +
173+
fibonacciSequence.actual[wordIndex - 2]
174+
)
175+
}
176+
}
177+
}
178+
}

0 commit comments

Comments
 (0)