Johannes Weiss 601e847faa
always @preconcurrency import Glibc (#1175)
Co-authored-by: Johannes Weiss <johannes@jweiss.io>
2025-03-04 09:25:51 -08:00

461 lines
15 KiB
Swift

//===----------------------------------------------------------------------===//
//
// 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)
@preconcurrency import Glibc
#elseif canImport(ucrt)
import ucrt
#endif
#if !FOUNDATION_FRAMEWORK
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
public struct Decimal: Sendable {
@_spi(SwiftCorelibsFoundation)
public typealias Mantissa = (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)
internal struct Storage: Sendable {
var exponent: Int8
// Layout:
// | 0 1 2 3 | 4 | 5 | 6 7 |
// | -> _length | | | | | ->_reserved
// | | | | |-> _isCompact
// | | |-> _isNegative
var lengthFlagsAndReserved: UInt8
// 18 bits long
var reserved: UInt16
var mantissa: Mantissa
}
internal var storage: Storage
// Int8
internal var _exponent: Int32 {
get {
return Int32(self.storage.exponent)
}
set {
self.storage.exponent = Int8(newValue)
}
}
// 4 bits
internal var _length: UInt32 {
get {
return UInt32(self.storage.lengthFlagsAndReserved >> 4)
}
set {
let newLength = (UInt8(truncatingIfNeeded: newValue) & 0x0F) << 4
self.storage.lengthFlagsAndReserved &= 0x0F // clear the length
self.storage.lengthFlagsAndReserved |= newLength // set the new length
}
}
// Bool
internal var _isNegative: UInt32 {
get {
return UInt32((self.storage.lengthFlagsAndReserved >> 3) & 0x01)
}
set {
if (newValue & 0x1) != 0 {
self.storage.lengthFlagsAndReserved |= 0b00001000
} else {
self.storage.lengthFlagsAndReserved &= 0b11110111
}
}
}
// Bool
internal var _isCompact: UInt32 {
get {
return UInt32((self.storage.lengthFlagsAndReserved >> 2) & 0x01)
}
set {
if (newValue & 0x1) != 0 {
self.storage.lengthFlagsAndReserved |= 0b00000100
} else {
self.storage.lengthFlagsAndReserved &= 0b11111011
}
}
}
// Only 18 bits
internal var _reserved: UInt32 {
get {
return (UInt32(self.storage.lengthFlagsAndReserved & 0x03) << 16) | UInt32(self.storage.reserved)
}
set {
// Bottom 16 bits
self.storage.reserved = UInt16(newValue & 0xFFFF)
self.storage.lengthFlagsAndReserved &= 0xFC
self.storage.lengthFlagsAndReserved |= UInt8(newValue >> 16) & 0xFF
}
}
internal var _mantissa: Mantissa {
get {
return self.storage.mantissa
}
set {
self.storage.mantissa = newValue
}
}
internal var _lengthFlagsAndReserved: UInt8 {
get {
return self.storage.lengthFlagsAndReserved
}
set {
self.storage.lengthFlagsAndReserved = newValue
}
}
@_spi(SwiftCorelibsFoundation)
public init(
_exponent: Int32 = 0,
_length: UInt32,
_isNegative: UInt32 = 0,
_isCompact: UInt32,
_reserved: UInt32 = 0,
_mantissa: Mantissa
) {
let length: UInt8 = (UInt8(truncatingIfNeeded: _length) & 0xF) << 4
let isNegative: UInt8 = UInt8(truncatingIfNeeded: _isNegative & 0x1) == 0 ? 0 : 0b00001000
let isCompact: UInt8 = UInt8(truncatingIfNeeded: _isCompact & 0x1) == 0 ? 0 : 0b00000100
let reservedLeft: UInt8 = UInt8(truncatingIfNeeded: (_reserved & 0x3FFFF) >> 16)
self.storage = .init(
exponent: Int8(truncatingIfNeeded: _exponent),
lengthFlagsAndReserved: length | isNegative | isCompact | reservedLeft,
reserved: UInt16(truncatingIfNeeded: _reserved & 0xFFFF),
mantissa: _mantissa
)
}
@_spi(SwiftCorelibsFoundation)
public init(mantissa: UInt64, exponent: Int16, isNegative: Bool) {
var d = Decimal(mantissa)
d._exponent += Int32(exponent)
d._isNegative = isNegative ? 1 : 0
self = d
}
public init() {
self.storage = .init(
exponent: 0,
lengthFlagsAndReserved: 0,
reserved: 0,
mantissa: (0, 0, 0, 0, 0, 0, 0, 0)
)
}
}
extension Decimal {
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
public enum RoundingMode: UInt, Sendable {
case plain
case down
case up
case bankers
}
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
public enum CalculationError: UInt, Sendable {
case noError
case lossOfPrecision
case overflow
case underflow
case divideByZero
}
}
#endif // !FOUNDATION_FRAMEWORK
// MARK: - String
extension Decimal {
#if FOUNDATION_FRAMEWORK
#else
@_spi(SwiftCorelibsFoundation)
public func toString(with locale: Locale? = nil) -> String {
let separator: String
if let locale = locale,
let localizedSeparator = locale.decimalSeparator {
separator = localizedSeparator
} else {
separator = "."
}
return _toString(withDecimalSeparator: separator)
}
@_spi(SwiftCorelibsFoundation)
public static func decimal(
from stringView: String.UTF8View,
decimalSeparator: String.UTF8View,
matchEntireString: Bool
) -> (result: Decimal?, processedLength: Int) {
_decimal(from: stringView, decimalSeparator: decimalSeparator, matchEntireString: matchEntireString).asOptional
}
#endif
internal func _toString(withDecimalSeparator separator: String) -> String {
if self.isNaN {
return "NaN"
}
if self._length == 0 {
return "0"
}
var buffer = ""
var copy = self
while copy._exponent > 0 {
buffer += "0"
copy._exponent -= 1
}
if copy._exponent == 0 {
copy._exponent = 1
}
while copy._length != 0 {
if copy._exponent == 0 {
buffer.append(separator)
}
copy._exponent += 1
// _divide only throws `.divideByZero` which we are obviously
// not doing here, hence try!
let (result, remainder) = try! copy._divide(by: 10)
copy = result
let zero = Unicode.Scalar("0")
buffer.append(String(Unicode.Scalar(zero.value + UInt32(remainder))!))
}
if copy._exponent <= 0 {
while copy._exponent != 0 {
buffer.append("0")
copy._exponent += 1
}
buffer.append(separator)
buffer.append("0")
}
if copy._isNegative != 0 {
buffer.append("-")
}
return String(buffer.reversed())
}
internal enum DecimalParseResult {
case success(Decimal, processedLength: Int)
case parseFailure
case overlargeValue
var asOptional: (result: Decimal?, processedLength: Int) {
switch self {
case let .success(decimal, processedLength): (decimal, processedLength: processedLength)
default: (nil, processedLength: 0)
}
}
}
@_specialize(where UTF8Collection == String.UTF8View)
@_specialize(where UTF8Collection == BufferView<UInt8>)
internal static func _decimal<UTF8Collection: Collection>(
from utf8View: UTF8Collection,
decimalSeparator: String.UTF8View = ".".utf8,
matchEntireString: Bool
) -> DecimalParseResult where UTF8Collection.Element == UTF8.CodeUnit {
func multiplyBy10AndAdd(
_ decimal: Decimal,
number: UInt16
) throws -> Decimal {
do {
var result = try decimal._multiply(byShort: 10)
result = try result._add(number)
return result
} catch {
throw _CalculationError.overflow
}
}
func skipWhiteSpaces(from index: UTF8Collection.Index) -> UTF8Collection.Index {
var i = index
while i != utf8View.endIndex &&
Character(utf8Scalar: utf8View[i]).isWhitespace {
utf8View.formIndex(after: &i)
}
return i
}
func stringViewContainsDecimalSeparator(at index: UTF8Collection.Index) -> Bool {
for indexOffset in 0 ..< decimalSeparator.count {
let stringIndex = utf8View.index(index, offsetBy: indexOffset)
let decimalIndex = decimalSeparator.index(
decimalSeparator.startIndex,
offsetBy: indexOffset
)
if utf8View[stringIndex] != decimalSeparator[decimalIndex] {
return false
}
}
return true
}
var result = Decimal()
var index = utf8View.startIndex
index = skipWhiteSpaces(from: index)
// Get the sign
if index != utf8View.endIndex &&
(utf8View[index] == UInt8._plus ||
utf8View[index] == UInt8._minus) {
result._isNegative = (utf8View[index] == UInt8._minus) ? 1 : 0
// Advance over the sign
utf8View.formIndex(after: &index)
}
// Build mantissa
var tooBigToFit = false
while index != utf8View.endIndex,
let digitValue = utf8View[index].digitValue {
defer {
utf8View.formIndex(after: &index)
}
// Multiply the value by 10 and add the current digit
func incrementExponent(_ decimal: inout Decimal) {
// Before incrementing the exponent, we need to check
// if it's still possible to increment.
if decimal._exponent == Int8.max {
decimal = .nan
return
}
decimal._exponent += 1
}
if tooBigToFit {
incrementExponent(&result)
if result.isNaN {
return .overlargeValue
}
continue
}
guard let product = try? result._multiplyBy10AndAdd(number: UInt16(digitValue)
) else {
tooBigToFit = true
incrementExponent(&result)
if result.isNaN {
return .overlargeValue
}
continue
}
result = product
}
// Get the decimal point
if index < utf8View.endIndex && stringViewContainsDecimalSeparator(at: index) {
utf8View.formIndex(&index, offsetBy: decimalSeparator.count)
// Continue to build the mantissa
while index != utf8View.endIndex,
let digitValue = utf8View[index].digitValue {
defer {
utf8View.formIndex(after: &index)
}
guard !tooBigToFit else {
continue
}
guard let product = try? result._multiplyBy10AndAdd(number: UInt16(digitValue)
) else {
tooBigToFit = true
continue
}
result = product
// Before decrementing the exponent, we need to check
// if it's still possible to decrement.
if result._exponent == Int8.min {
return .overlargeValue
}
result._exponent -= 1
}
}
// Get the exponent if any
if index < utf8View.endIndex && (utf8View[index] == UInt8._E || utf8View[index] == UInt8._e) {
utf8View.formIndex(after: &index)
// If there is no content after e, the string is invalid
guard index != utf8View.endIndex else {
// Normally we should return .parseFailure
// However, NSDecimal historically parses any
// - Invalid strings starting with `e` as 0
// - "en" -> 0
// - "e" -> 0
// - Strings ending with `e` but nothing after as valid
// - "1234e" -> 1234
// So let's keep that behavior here as well
let processedLength = utf8View.distance(from: utf8View.startIndex, to: index)
return .success(result, processedLength: processedLength)
}
var exponentIsNegative = false
var exponent = 0
// Get the exponent sign
if utf8View[index] == UInt8._minus || utf8View[index] == UInt8._plus {
exponentIsNegative = utf8View[index] == UInt8._minus
utf8View.formIndex(after: &index)
}
// Build the exponent
while index != utf8View.endIndex,
let digitValue = utf8View[index].digitValue {
exponent = 10 * exponent + digitValue
if exponent > 2 * Int(Int8.max) {
// Too big to fit
return .overlargeValue
}
utf8View.formIndex(after: &index)
}
if exponentIsNegative {
exponent = -exponent
}
// Check to see if it will fit into the exponent field
exponent += Int(result._exponent)
if exponent > Int8.max || exponent < Int8.min {
return .overlargeValue
}
result._exponent = Int32(exponent)
}
// If we are required to match the entire string,
// "trim" the end whitespaces and check if we are
// at the end of the string
if matchEntireString {
// Trim end spaces
index = skipWhiteSpaces(from: index)
guard index == utf8View.endIndex else {
// Any unprocessed content means the string
// contains something not valid
return .parseFailure
}
}
if index == utf8View.startIndex {
// If we weren't able to process any character
// the entire string isn't a valid decimal
return .parseFailure
}
result.compact()
let processedLength = utf8View.distance(from: utf8View.startIndex, to: index)
// if we get to this point, and have NaN,
// then the input string was probably "-0"
// or some variation on that, and
// normalize that to zero.
if result.isNaN {
return .success(Decimal(0), processedLength: processedLength)
}
return .success(result, processedLength: processedLength)
}
}
private extension Character {
init(utf8Scalar: UTF8.CodeUnit) {
self.init(Unicode.Scalar(utf8Scalar))
}
}