//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2020 - 2023 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 // //===----------------------------------------------------------------------===// #if canImport(Darwin) import Darwin #elseif canImport(Glibc) import Glibc #endif // MARK: - BinaryInteger + Numeric string representation extension BinaryInteger { /// Formats `self` in "Numeric string" format (https://speleotrove.com/decimal/daconvs.html) /// which is the required input form for certain ICU functions (e.g. `unum_formatDecimal`). /// /// This produces output that (at time of writing) looks identical to the `description` for /// many `BinaryInteger` types, such as the built-in integer types. However, the format of /// `description` is not specifically defined by `BinaryInteger` (or anywhere else, really), /// and as such cannot be relied upon. Thus this purpose-built method, instead. /// package var numericStringRepresentation: String { numericStringRepresentationForBinaryInteger(words: self.words, isSigned: Self.isSigned) } } /// Formats `words` in "Numeric string" format (https://speleotrove.com/decimal/daconvs.html) /// which is the required input form for certain ICU functions (e.g. `unum_formatDecimal`). /// /// - Parameters: /// - words: The binary integer's words (least-significant word first). /// - isSigned: The binary integer's signedness. If true, `words` must be in two's complement form. /// internal func numericStringRepresentationForBinaryInteger(words: some Collection, isSigned: Bool) -> String { // Copies the words and then passes them to a non-generic, mutating, word-based algorithm. withUnsafeTemporaryAllocation(of: UInt.self, capacity: words.count) { let initializedEndIndex = $0.initialize(fromContentsOf: words) let initialized = UnsafeMutableBufferPointer(rebasing: $0[.., isSigned: Bool) -> String { // We reinterpret the words as an unsigned binary integer. var magnitude = /* consume */ words // Note that negative values are in two's complement form. let isLessThanZero = isSigned && Int(bitPattern: magnitude.last ?? .zero) < .zero // The **unsigned** magnitude is formed when the words represent a negative value. if isLessThanZero { formTwosComplementForBinaryInteger(words: magnitude) } let capacity = maxDecimalDigitCountForUnsignedInteger(bitWidth: magnitude.count * UInt.bitWidth) + (isLessThanZero ? 1 : 0) return withUnsafeTemporaryAllocation(of: UInt8.self, capacity: capacity) { // We rebase $0 because capacity <= $0.count. let ascii = UnsafeMutableBufferPointer(start: $0.baseAddress, count: capacity) // Set initial ASCII zeros (see later steps). ascii.initialize(repeating: UInt8(ascii: "0")) // Deferred deinitialization of initialized memory. defer { ascii.deinitialize() } // We get decimal digits in chunks as we divide the magnitude by pow(10,radix.exponent). // We then extract the decimal digits from each chunk by repeatedly dividing them by 10. let radix: (exponent: Int, power: UInt) = maxDecimalExponentAndPowerForUnsignedIntegerWord() var chunkIndex = ascii.endIndex // The index of the current iteration's chunk. var writeIndex = ascii.endIndex // The index of the last character we encoded. dividing: while true { // Mutating division prevents unnecessary big integer allocations. var chunk = formQuotientWithRemainderForUnsignedInteger(words: magnitude, dividingBy: radix.power) // We trim the magnitude's most significant zeros for flexible-width performance and to end the loop. magnitude = .init(rebasing: magnitude[.. ascii.startIndex, "the buffer must accommodate the magnitude's decimal digits") ascii.formIndex(before: &writeIndex) ascii[writeIndex] = UInt8(ascii: "0") &+ UInt8(truncatingIfNeeded: digit) } while chunk != .zero // We break the loop when every decimal digit has been encoded. if magnitude.isEmpty { break } // The resulting index is always in bounds because we form it after checking if there are digits left. chunkIndex = ascii.index(chunkIndex, offsetBy: -radix.exponent) // Set the next iterations's index in case this one ended in zeros. Note that zeros are pre-initialized. writeIndex = chunkIndex } // Add a minus sign to negative values. if isLessThanZero { precondition(writeIndex > ascii.startIndex, "must add 1 to the buffer's capacity for integers less than zero") ascii.formIndex(before: &writeIndex) ascii[writeIndex] = UInt8(ascii: "-") } // We copy the sequence from the last character we encoded. let result = UnsafeBufferPointer(rebasing: ascii[writeIndex...]) return String(unsafeUninitializedCapacity: result.count) { _ = $0.initialize(fromContentsOf: result); return result.count } } } /// Returns an upper bound for the [number of decimal digits][algorithm] needed /// to represent an unsigned integer with the given `bitWidth`. /// /// [algorithm]: https://www.exploringbinary.com/number-of-decimal-digits-in-a-binary-integer /// /// - Parameter bitWidth: An unsigned binary integer's bit width. It must be non-negative. /// /// - Returns: Some integer greater than or equal to `1`. /// private func maxDecimalDigitCountForUnsignedInteger(bitWidth: Int) -> Int { // - Int.init(some BinaryFloatingPoint) rounds to zero. // - Double.init(exactly:) and UInt.init(_:) for correctness. // - log10(2.0) is: 1.0021010002000002002101⌈01...⌉ * 2^(-2). // - It's an upper bound, so Double/nextUp for peace of mind. return Int(Double(exactly: UInt(bitWidth))! * log10(2.0).nextUp) + 1 } /// Returns the largest `exponent` and `power` in `pow(10, exponent) <= UInt.max + 1`. /// /// The `exponent` is also the maximum number of decimal digits needed to represent a binary integer /// in the range of `0 ..< power`. Another method is used to estimate the total number of digits, however. /// This is so that binary integers can be rabased and encoded in the same loop. /// /// ``` /// 32-bit: (exponent: 9, power: 1000000000) /// 64-bit: (exponent: 19, power: 10000000000000000000) /// ``` /// /// - Note: The optimizer should inline this as a constant. /// /// - Note: Dividing an integer by `power` yields the first `exponent` number of decimal digits in the /// remainder. The quotient is the integer with its first `exponent` number of decimal digits removed. /// private func maxDecimalExponentAndPowerForUnsignedIntegerWord() -> (exponent: Int, power: UInt) { var exponent: Int = 1, power: UInt = 10 while true { let next = power.multipliedReportingOverflow(by: 10) if next.overflow { break } exponent += 1 power = next.partialValue } return (exponent: exponent, power: power) } /// Forms the `two's complement` of a binary integer. /// /// - Parameter words: A binary integer's mutable words. /// private func formTwosComplementForBinaryInteger(words: UnsafeMutableBufferPointer) { var carry = true for index in words.indices { (words[index], carry) = (~words[index]).addingReportingOverflow(carry ? 1 : 0) } } /// Forms the `quotient` of dividing the `dividend` by the `divisor`, then returns the `remainder`. /// /// - Parameters: /// - dividend: An unsigned binary integer's words. It becomes the `quotient` once this function returns. /// - divisor: An unsigned binary integer's only word. /// /// - Returns: The `remainder`, which is a value in the range of `0 ..< divisor`. /// private func formQuotientWithRemainderForUnsignedInteger(words dividend: UnsafeMutableBufferPointer, dividingBy divisor: UInt) -> UInt { var remainder = UInt.zero for index in dividend.indices.reversed() { (dividend[index], remainder) = divisor.dividingFullWidth((high: remainder, low: dividend[index])) } return remainder }