Skip to content

Commit e25b6dd

Browse files
committed
[SE-0368] StaticBigInt: add the Swift wrapper type
1 parent 94bce64 commit e25b6dd

File tree

4 files changed

+482
-2
lines changed

4 files changed

+482
-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: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
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.
69+
///
70+
/// * `-4` (`0b11111_100`) is 3 bits.
71+
/// * `-3` (`0b11111_101`) is 3 bits.
72+
/// * `-2` (`0b111111_10`) is 2 bits.
73+
/// * `-1` (`0b1111111_1`) is 1 bit.
74+
/// * `+0` (`0b0000000_0`) is 1 bit.
75+
/// * `+1` (`0b000000_01`) is 2 bits.
76+
/// * `+2` (`0b00000_010`) is 3 bits.
77+
/// * `+3` (`0b00000_011`) is 3 bits.
78+
@available(SwiftStdlib 5.8, *)
79+
@inlinable
80+
public var bitWidth: Int {
81+
Int(Builtin.bitWidth_IntLiteral(_value))
82+
}
83+
84+
/// Returns an element of this value's binary representation.
85+
///
86+
/// The elements are ordered from least significant to most significant, with
87+
/// an infinite sign extension. Negative values are in two's complement.
88+
///
89+
/// let value: StaticBigInt = -0x8000000000000000_0000000000000000
90+
/// value.bitWidth //-> 128
91+
/// value[0] as UInt64 //-> 0x0000000000000000
92+
/// value[1] as UInt64 //-> 0x8000000000000000
93+
/// value[2] as UInt64 //-> 0xFFFFFFFFFFFFFFFF
94+
///
95+
/// let value: StaticBigInt = -1
96+
/// value.bitWidth //-> 1
97+
/// value[0] as UInt64 //-> 0xFFFFFFFFFFFFFFFF
98+
/// value[1] as UInt64 //-> 0xFFFFFFFFFFFFFFFF
99+
/// value[2] as UInt64 //-> 0xFFFFFFFFFFFFFFFF
100+
///
101+
/// let value: StaticBigInt = +0
102+
/// value.bitWidth //-> 1
103+
/// value[0] as UInt64 //-> 0x0000000000000000
104+
/// value[1] as UInt64 //-> 0x0000000000000000
105+
/// value[2] as UInt64 //-> 0x0000000000000000
106+
///
107+
/// let value: StaticBigInt = +0x7FFFFFFFFFFFFFFF_FFFFFFFFFFFFFFFF
108+
/// value.bitWidth //-> 128
109+
/// value[0] as UInt64 //-> 0xFFFFFFFFFFFFFFFF
110+
/// value[1] as UInt64 //-> 0x7FFFFFFFFFFFFFFF
111+
/// value[2] as UInt64 //-> 0x0000000000000000
112+
///
113+
/// - Parameter index: A nonnegative zero-based index.
114+
/// - Returns: A fixed-width unsigned integer. Word-sized types are preferred.
115+
@available(SwiftStdlib 5.8, *)
116+
@inlinable
117+
public subscript<Element>(_ index: Int) -> Element
118+
where
119+
Element: _ExpressibleByBuiltinIntegerLiteral,
120+
Element: FixedWidthInteger,
121+
Element: UnsignedInteger
122+
{
123+
_precondition(index >= 0, "index out of range")
124+
guard index * Element.bitWidth < bitWidth else {
125+
return _isNegative ? ~0 : 0
126+
}
127+
guard Element.bitWidth == UInt.bitWidth else {
128+
return self[_slowElementAt: index]
129+
}
130+
return Element(_truncatingBits: UInt(
131+
Builtin.wordAtIndex_IntLiteral(_value, index._builtinWordValue)
132+
))
133+
}
134+
135+
@available(SwiftStdlib 5.8, *)
136+
@usableFromInline
137+
internal subscript<Element>(_slowElementAt index: Int) -> Element
138+
where
139+
Element: _ExpressibleByBuiltinIntegerLiteral,
140+
Element: FixedWidthInteger,
141+
Element: UnsignedInteger
142+
{
143+
_internalInvariant(index >= 0, "index out of range")
144+
let bitIndex = index * Element.bitWidth
145+
var wordIndex = bitIndex >> UInt.bitWidth.trailingZeroBitCount
146+
if UInt.bitWidth.isMultiple(of: Element.bitWidth) {
147+
// Create a single-word element, using a *masking* shift.
148+
let word: UInt = self[wordIndex]
149+
return Element(_truncatingBits: word &>> bitIndex)
150+
} else if Element.bitWidth.isMultiple(of: UInt.bitWidth) {
151+
// Create a multi-word element.
152+
var result = Element.zero
153+
for wordOffset in 0..<(Element.bitWidth / UInt.bitWidth) {
154+
let word: UInt = self[wordIndex]
155+
result |= Element(_truncatingBits: word) << (wordOffset * UInt.bitWidth)
156+
wordIndex += 1
157+
}
158+
return result
159+
} else {
160+
_preconditionFailure("element type not supported")
161+
}
162+
}
163+
}
164+
165+
//===----------------------------------------------------------------------===//
166+
// MARK: - Textual Representation
167+
//===----------------------------------------------------------------------===//
168+
169+
@available(SwiftStdlib 5.8, *)
170+
extension StaticBigInt: CustomDebugStringConvertible {
171+
172+
@available(SwiftStdlib 5.8, *)
173+
public var debugDescription: String {
174+
let isNegative = _isNegative
175+
let prefix = isNegative ? "-0x" : "+0x"
176+
177+
// Equivalent to `ceil(bitWidthExcludingSignBit / fourBitsPerHexDigit)`.
178+
// Underestimated for `-(16 ** y)` values (e.g. "-0x1", "-0x10", "-0x100").
179+
let capacity = prefix.utf8.count + (((bitWidth - 1) + 3) / 4)
180+
var result = String(unsafeUninitializedCapacity: capacity) { buffer in
181+
182+
// Ignore extra capacity by rebasing, then pre-initialize with zeros.
183+
var buffer = UnsafeMutableBufferPointer(rebasing: buffer.prefix(capacity))
184+
buffer.initialize(repeating: UInt8(ascii: "0"))
185+
186+
// Use a 32-bit element type, to generate small hexadecimal strings.
187+
typealias Element = UInt32
188+
let hexDigitsPerElement = Element.bitWidth / 4
189+
_internalInvariant(hexDigitsPerElement <= _SmallString.capacity)
190+
191+
// Lazily compute the magnitude, starting with the least significant bits.
192+
var overflow = isNegative
193+
for bitIndex in stride(from: 0, to: bitWidth, by: Element.bitWidth) {
194+
let elementIndex = bitIndex >> Element.bitWidth.trailingZeroBitCount
195+
var element: Element = self[elementIndex]
196+
if isNegative {
197+
element = ~element
198+
if overflow {
199+
(element, overflow) = element.addingReportingOverflow(1)
200+
}
201+
}
202+
203+
// Overwrite trailing zeros with hexadecimal digits.
204+
// FIXME: <https://forums.swift.org/t/better-buffer-slices/55689>
205+
let hexDigits = String(element, radix: 16, uppercase: true)
206+
_ = UnsafeMutableBufferPointer(
207+
rebasing: buffer.suffix(hexDigits.utf8.count)
208+
).initialize(from: hexDigits.utf8)
209+
buffer = UnsafeMutableBufferPointer(
210+
rebasing: buffer.dropLast(hexDigitsPerElement)
211+
)
212+
}
213+
return capacity
214+
}
215+
216+
// Overwrite leading zeros with the "±0x" indicator.
217+
if let upToIndex = result.firstIndex(where: { $0 != "0" }) {
218+
result.replaceSubrange(..<upToIndex, with: prefix)
219+
} else {
220+
result = "+0x0"
221+
}
222+
return result
223+
}
224+
}
225+
226+
#if SWIFT_ENABLE_REFLECTION
227+
@available(SwiftStdlib 5.8, *)
228+
extension StaticBigInt: CustomReflectable {
229+
230+
@available(SwiftStdlib 5.8, *)
231+
public var customMirror: Mirror {
232+
Mirror(self, unlabeledChildren: EmptyCollection<Void>())
233+
}
234+
}
235+
#endif

0 commit comments

Comments
 (0)