//===----------------------------------------------------------------------===// // // 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) internal static func _decimal( 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)) } }