diff --git a/Sources/FoundationEssentials/Decimal/CMakeLists.txt b/Sources/FoundationEssentials/Decimal/CMakeLists.txt index 81cade38a..2fdd8ccc7 100644 --- a/Sources/FoundationEssentials/Decimal/CMakeLists.txt +++ b/Sources/FoundationEssentials/Decimal/CMakeLists.txt @@ -14,4 +14,5 @@ target_sources(FoundationEssentials PRIVATE Decimal.swift Decimal+Conformances.swift - Decimal+Math.swift) + Decimal+Math.swift + Decimal+Compatibility.swift) diff --git a/Sources/FoundationEssentials/Decimal/Decimal+Compatibility.swift b/Sources/FoundationEssentials/Decimal/Decimal+Compatibility.swift new file mode 100644 index 000000000..3b0c45feb --- /dev/null +++ b/Sources/FoundationEssentials/Decimal/Decimal+Compatibility.swift @@ -0,0 +1,469 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +// NSDecimal compatibility API + +#if FOUNDATION_FRAMEWORK +// For feature flag +internal import _ForSwiftFoundation +#endif + +#if FOUNDATION_FRAMEWORK +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +extension Decimal { + public typealias RoundingMode = NSDecimalNumber.RoundingMode + public typealias CalculationError = NSDecimalNumber.CalculationError +} + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +extension Decimal { + @available(swift, obsoleted: 4, message: "Please use arithmetic operators instead") + @_transparent + public mutating func add(_ other: Decimal) { + self += other + } + + @available(swift, obsoleted: 4, message: "Please use arithmetic operators instead") + @_transparent + public mutating func subtract(_ other: Decimal) { + self -= other + } + + @available(swift, obsoleted: 4, message: "Please use arithmetic operators instead") + @_transparent + public mutating func multiply(by other: Decimal) { + self *= other + } + + @available(swift, obsoleted: 4, message: "Please use arithmetic operators instead") + @_transparent + public mutating func divide(by other: Decimal) { + self /= other + } +} + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +extension Decimal : _ObjectiveCBridgeable { + @_semantics("convertToObjectiveC") + public func _bridgeToObjectiveC() -> NSDecimalNumber { + return NSDecimalNumber(decimal: self) + } + + public static func _forceBridgeFromObjectiveC(_ x: NSDecimalNumber, result: inout Decimal?) { + if !_conditionallyBridgeFromObjectiveC(x, result: &result) { + fatalError("Unable to bridge \(_ObjectiveCType.self) to \(self)") + } + } + + public static func _conditionallyBridgeFromObjectiveC(_ input: NSDecimalNumber, result: inout Decimal?) -> Bool { + result = input.decimalValue + return true + } + + @_effects(readonly) + public static func _unconditionallyBridgeFromObjectiveC(_ source: NSDecimalNumber?) -> Decimal { + guard let src = source else { return Decimal(_exponent: 0, _length: 0, _isNegative: 0, _isCompact: 0, _reserved: 0, _mantissa: (0, 0, 0, 0, 0, 0, 0, 0)) } + return src.decimalValue + } +} +#endif + +// MARK: - Bridging code to C functions +// We have one implementation function for each, and an entry point for both Darwin (cdecl, exported from the framework), and swift-corelibs-foundation (SPI here and available via that package as API) + +#if FOUNDATION_FRAMEWORK +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +public func pow(_ x: Decimal, _ y: Int) -> Decimal { + let result = try? x._power( + exponent: UInt(y), roundingMode: .plain + ) + return result ?? .nan +} +#else +@_spi(SwiftCorelibsFoundation) +public func _pow(_ x: Decimal, _ y: Int) -> Decimal { + let result = try? x._power( + exponent: UInt(y), roundingMode: .plain + ) + return result ?? .nan +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalAdd( + _ result: UnsafeMutablePointer, + _ lhs: UnsafePointer, + _ rhs: UnsafePointer, + _ roundingMode: Decimal.RoundingMode +) -> Decimal.CalculationError { + do { + let addition = try lhs.pointee._add( + rhs: rhs.pointee, roundingMode: roundingMode + ) + result.pointee = addition.result + if addition.lossOfPrecision { + return .lossOfPrecision + } else { + return .noError + } + } catch { + let converted = _convertError(error) + result.pointee = .nan + return converted + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalAdd") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalAdd(_ result: UnsafeMutablePointer, _ lhs: UnsafePointer, _ rhs: UnsafePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalAdd(result, lhs, rhs, roundingMode) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalAdd(_ result: UnsafeMutablePointer, _ lhs: UnsafePointer, _ rhs: UnsafePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalAdd(result, lhs, rhs, roundingMode) +} +#endif + + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalSubtract( + _ result: UnsafeMutablePointer, + _ lhs: UnsafePointer, + _ rhs: UnsafePointer, + _ roundingMode: Decimal.RoundingMode +) -> Decimal.CalculationError { + do { + let subtraction = try lhs.pointee._subtract( + rhs: rhs.pointee, roundingMode: roundingMode + ) + result.pointee = subtraction + return .noError + } catch { + let converted = _convertError(error) + result.pointee = .nan + return converted + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalSubtract") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalSubtract(_ result: UnsafeMutablePointer, _ lhs: UnsafePointer, _ rhs: UnsafePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalSubtract(result, lhs, rhs, roundingMode) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalSubtract(_ result: UnsafeMutablePointer, _ lhs: UnsafePointer, _ rhs: UnsafePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalSubtract(result, lhs, rhs, roundingMode) +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalMultiply( + _ result: UnsafeMutablePointer, + _ lhs: UnsafePointer, + _ rhs: UnsafePointer, + _ roundingMode: Decimal.RoundingMode +) -> Decimal.CalculationError { + do { + let product = try lhs.pointee._multiply( + by: rhs.pointee, roundingMode: roundingMode + ) + result.pointee = product + return .noError + } catch { + let converted = _convertError(error) + result.pointee = .nan + return converted + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalMultiply") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalMultiply(_ result: UnsafeMutablePointer, _ lhs: UnsafePointer, _ rhs: UnsafePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalMultiply(result, lhs, rhs, roundingMode) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalMultiply(_ result: UnsafeMutablePointer, _ lhs: UnsafePointer, _ rhs: UnsafePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalMultiply(result, lhs, rhs, roundingMode) +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalDivide( + _ result: UnsafeMutablePointer, + _ lhs: UnsafePointer, + _ rhs: UnsafePointer, + _ roundingMode: Decimal.RoundingMode +) -> Decimal.CalculationError { + do { + let product = try lhs.pointee._divide( + by: rhs.pointee, roundingMode: roundingMode + ) + result.pointee = product + return .noError + } catch { + let converted = _convertError(error) + result.pointee = .nan + return converted + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalDivide") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalDivide(_ result: UnsafeMutablePointer, _ lhs: UnsafePointer, _ rhs: UnsafePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalDivide(result, lhs, rhs, roundingMode) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalDivide(_ result: UnsafeMutablePointer, _ lhs: UnsafePointer, _ rhs: UnsafePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalDivide(result, lhs, rhs, roundingMode) +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalPower( + _ result: UnsafeMutablePointer, + _ decimal: UnsafePointer, + _ exponent: Int, + _ roundingMode: Decimal.RoundingMode +) -> Decimal.CalculationError { + do { + let power = try decimal.pointee._power(exponent: UInt(exponent), roundingMode: roundingMode) + result.pointee = power + return .noError + } catch { + let converted = _convertError(error) + result.pointee = .nan + return converted + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalPower") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalPower(_ result: UnsafeMutablePointer, _ decimal: UnsafePointer, _ exponent: Int, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalPower(result, decimal, exponent, roundingMode) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalPower(_ result: UnsafeMutablePointer, _ decimal: UnsafePointer, _ exponent: Int, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalPower(result, decimal, exponent, roundingMode) +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalMultiplyByPowerOf10( + _ result: UnsafeMutablePointer, + _ decimal: UnsafePointer, + _ power: CShort, + _ roundingMode: Decimal.RoundingMode +) -> Decimal.CalculationError { + do { + let product = try decimal.pointee._multiplyByPowerOfTen(power: Int(power), roundingMode: roundingMode) + result.pointee = product + return .noError + } catch { + let converted = _convertError(error) + result.pointee = .nan + return converted + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalMultiplyByPowerOf10") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalMultiplyByPowerOf10(_ result: UnsafeMutablePointer, _ decimal: UnsafePointer, _ power: CShort, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalMultiplyByPowerOf10(result, decimal, power, roundingMode) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalMultiplyByPowerOf10(_ result: UnsafeMutablePointer, _ decimal: UnsafePointer, _ power: CShort, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalMultiplyByPowerOf10(result, decimal, power, roundingMode) +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalCompare( + _ lhs: UnsafePointer, + _ rhs: UnsafePointer +) -> ComparisonResult { + return Decimal._compare(lhs: lhs.pointee, rhs: rhs.pointee) +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalCompare") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalCompare(_ lhs: UnsafePointer, _ rhs: UnsafePointer) -> ComparisonResult { + __NSDecimalCompare(lhs, rhs) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalCompare(_ lhs: UnsafePointer, _ rhs: UnsafePointer) -> ComparisonResult { + __NSDecimalCompare(lhs, rhs) +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalRound( + _ result: UnsafeMutablePointer, + _ decimal: UnsafePointer, + _ scale: Int, + _ roundingMode: Decimal.RoundingMode +) { + do { + let rounded = try decimal.pointee._round( + scale: scale, + roundingMode: roundingMode + ) + result.pointee = rounded + } catch { + // Noop since this method does not + // return a calculation error + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalRound") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalRound(_ result: UnsafeMutablePointer, _ decimal: UnsafePointer, _ scale: Int, _ roundingMode: Decimal.RoundingMode) { + __NSDecimalRound(result, decimal, scale, roundingMode) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalRound(_ result: UnsafeMutablePointer, _ decimal: UnsafePointer, _ scale: Int, _ roundingMode: Decimal.RoundingMode) { + __NSDecimalRound(result, decimal, scale, roundingMode) +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalNormalize( + _ lhs: UnsafeMutablePointer, + _ rhs: UnsafeMutablePointer, + _ roundingMode: Decimal.RoundingMode +) -> Decimal.CalculationError { + do { + var a = lhs.pointee + var b = rhs.pointee + let lossPrecision = try Decimal._normalize( + a: &a, b: &b, roundingMode: roundingMode + ) + lhs.pointee = a + rhs.pointee = b + if lossPrecision { + return .lossOfPrecision + } + return .noError + } catch { + let converted = _convertError(error) + return converted + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalNormalize") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalNormalize(_ lhs: UnsafeMutablePointer, _ rhs: UnsafeMutablePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalNormalize(lhs, rhs, roundingMode) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalNormalize(_ lhs: UnsafeMutablePointer, _ rhs: UnsafeMutablePointer, _ roundingMode: Decimal.RoundingMode) -> Decimal.CalculationError { + __NSDecimalNormalize(lhs, rhs, roundingMode) +} +#endif + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalCompact") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalCompact(_ number: UnsafeMutablePointer) { + var value = number.pointee + value.compact() + number.pointee = value +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalCompact(_ number: UnsafeMutablePointer) { + var value = number.pointee + value.compact() + number.pointee = value +} +#endif + +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func __NSDecimalString( + _ decimal: UnsafePointer, + _ locale: Any? = nil +) -> String { + let useLocale = locale as? Locale + return decimal.pointee._toString(with: useLocale) +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("NSDecimalString") +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@usableFromInline internal func NSDecimalString(_ decimal: UnsafePointer, _ locale: Any? = nil) -> String { + __NSDecimalString(decimal, locale) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSDecimalString(_ decimal: UnsafePointer, _ locale: Any? = nil) -> String { + __NSDecimalString(decimal, locale) +} +#endif + +internal func __NSStringToDecimal( + _ string: String, + processedLength: UnsafeMutablePointer, + result: UnsafeMutablePointer +) { + let parsed = Decimal._decimal( + from: string.utf8, + decimalSeparator: ".".utf8, + matchEntireString: false + ) + processedLength.pointee = parsed.processedLength + if let parsedResult = parsed.result { + result.pointee = parsedResult + } +} + +#if FOUNDATION_FRAMEWORK +@_cdecl("_NSStringToDecimal") +internal func _NSStringToDecimal(_ string: String, processedLength: UnsafeMutablePointer, result: UnsafeMutablePointer) { + __NSStringToDecimal(string, processedLength: processedLength, result: result) +} +#else +@_spi(SwiftCorelibsFoundation) +public func _NSStringToDecimal(_ string: String, processedLength: UnsafeMutablePointer, result: UnsafeMutablePointer) { + __NSStringToDecimal(string, processedLength: processedLength, result: result) +} +#endif + +private func _convertError(_ error: any Error) -> Decimal.CalculationError { + guard let calculationError = error as? Decimal._CalculationError else { + return .noError + } + switch calculationError { + case .overflow: + return .overflow + case .underflow: + return .underflow + case .divideByZero: + return .divideByZero + } +} diff --git a/Sources/FoundationEssentials/Decimal/Decimal+Conformances.swift b/Sources/FoundationEssentials/Decimal/Decimal+Conformances.swift index 4e0276c81..264089263 100644 --- a/Sources/FoundationEssentials/Decimal/Decimal+Conformances.swift +++ b/Sources/FoundationEssentials/Decimal/Decimal+Conformances.swift @@ -18,7 +18,7 @@ internal import _ForSwiftFoundation extension Decimal : CustomStringConvertible { public init?(string: __shared String, locale: __shared Locale? = nil) { let decimalSeparator = locale?.decimalSeparator ?? "." - guard let value = Decimal.decimal( + guard let value = Decimal._decimal( from: string.utf8, decimalSeparator: decimalSeparator.utf8, matchEntireString: false @@ -29,7 +29,7 @@ extension Decimal : CustomStringConvertible { } public var description: String { - return self.toString() + return self._toString() } } diff --git a/Sources/FoundationEssentials/Decimal/Decimal+Math.swift b/Sources/FoundationEssentials/Decimal/Decimal+Math.swift index 68e0bd5c4..172454463 100644 --- a/Sources/FoundationEssentials/Decimal/Decimal+Math.swift +++ b/Sources/FoundationEssentials/Decimal/Decimal+Math.swift @@ -266,6 +266,19 @@ extension Decimal { result._exponent = secureExponent return result } + + internal func _multiplyBy10AndAdd( + number: UInt16 + ) throws -> Decimal { + do { + var result = try _multiply(byShort: 10) + result = try result._add(number) + return result + } catch { + throw _CalculationError.overflow + } + } + internal func _divide(by divisor: UInt16) throws -> (result: Decimal, remainder: UInt16) { let (resultValue, remainder) = try Self._integerDivideByShort( @@ -702,6 +715,18 @@ extension Decimal { } return value } + + #if FOUNDATION_FRAMEWORK + #else + @_spi(SwiftCorelibsFoundation) + public var _int64Value: Int64 { int64Value } + + @_spi(SwiftCorelibsFoundation) + public var _uint64Value: UInt64 { uint64Value } + + @_spi(SwiftCorelibsFoundation) + public var _doubleValue: Double { doubleValue } + #endif } // MARK: - Integer Mathmatics diff --git a/Sources/FoundationEssentials/Decimal/Decimal.swift b/Sources/FoundationEssentials/Decimal/Decimal.swift index b7cb2154f..f664f685a 100644 --- a/Sources/FoundationEssentials/Decimal/Decimal.swift +++ b/Sources/FoundationEssentials/Decimal/Decimal.swift @@ -22,7 +22,8 @@ import ucrt @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) public struct Decimal: Sendable { - internal typealias Mantissa = (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16) + @_spi(SwiftCorelibsFoundation) + public typealias Mantissa = (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16) internal struct Storage: Sendable { var exponent: Int8 @@ -60,6 +61,7 @@ public struct Decimal: Sendable { self.storage.lengthFlagsAndReserved |= newLength // set the new length } } + // Bool internal var _isNegative: UInt32 { get { @@ -73,6 +75,7 @@ public struct Decimal: Sendable { } } } + // Bool internal var _isCompact: UInt32 { get { @@ -86,6 +89,7 @@ public struct Decimal: Sendable { } } } + // Only 18 bits internal var _reserved: UInt32 { get { @@ -117,7 +121,8 @@ public struct Decimal: Sendable { } } - internal init( + @_spi(SwiftCorelibsFoundation) + public init( _exponent: Int32 = 0, _length: UInt32, _isNegative: UInt32 = 0, @@ -126,17 +131,25 @@ public struct Decimal: Sendable { _mantissa: Mantissa ) { let length: UInt8 = (UInt8(truncatingIfNeeded: _length) & 0xF) << 4 - let isNagitive: UInt8 = UInt8(truncatingIfNeeded: _isNegative & 0x1) == 0 ? 0 : 0b00001000 + 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 | isNagitive | isCompact | reservedLeft, + 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, @@ -170,7 +183,23 @@ extension Decimal { // MARK: - String extension Decimal { - internal func toString(with locale: Locale? = nil) -> String { +#if FOUNDATION_FRAMEWORK +#else + @_spi(SwiftCorelibsFoundation) + public func toString(with locale: Locale? = nil) -> String { + _toString(with: locale) + } + + @_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) + } +#endif + internal func _toString(with locale: Locale? = nil) -> String { if self.isNaN { return "NaN" } @@ -221,7 +250,7 @@ extension Decimal { return String(buffer.reversed()) } - internal static func decimal( + internal static func _decimal( from stringView: String.UTF8View, decimalSeparator: String.UTF8View, matchEntireString: Bool @@ -299,9 +328,7 @@ extension Decimal { } continue } - guard let product = try? multiplyBy10AndAdd( - result, - number: UInt16(digitValue) + guard let product = try? result._multiplyBy10AndAdd(number: UInt16(digitValue) ) else { tooBigToFit = true incrementExponent(&result) @@ -324,9 +351,7 @@ extension Decimal { guard !tooBigToFit else { continue } - guard let product = try? multiplyBy10AndAdd( - result, - number: UInt16(digitValue) + guard let product = try? result._multiplyBy10AndAdd(number: UInt16(digitValue) ) else { tooBigToFit = true continue diff --git a/Sources/FoundationEssentials/JSON/JSONDecoder.swift b/Sources/FoundationEssentials/JSON/JSONDecoder.swift index 7bff04fd7..8fff08777 100644 --- a/Sources/FoundationEssentials/JSON/JSONDecoder.swift +++ b/Sources/FoundationEssentials/JSON/JSONDecoder.swift @@ -1074,7 +1074,7 @@ extension FixedWidthInteger { extension Decimal { init?(entire string: String) { - guard let value = Decimal.decimal( + guard let value = Decimal._decimal( from: string.utf8, decimalSeparator: ".".utf8, matchEntireString: true diff --git a/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift b/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift index 191cbc263..026945d62 100644 --- a/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift +++ b/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift @@ -18,10 +18,10 @@ import FoundationEssentials import Android #elseif canImport(Glibc) import Glibc -#endif - -#if canImport(CRT) +#elseif canImport(CRT) import CRT +#elseif canImport(Darwin) +import Darwin #endif internal import _FoundationICU diff --git a/Sources/FoundationInternationalization/Date+ICU.swift b/Sources/FoundationInternationalization/Date+ICU.swift index 3e253b672..3895915ea 100644 --- a/Sources/FoundationInternationalization/Date+ICU.swift +++ b/Sources/FoundationInternationalization/Date+ICU.swift @@ -19,6 +19,8 @@ internal import _FoundationICU import Android #elseif canImport(Glibc) import Glibc +#elseif canImport(Darwin) +import Darwin #endif /// Internal extensions on Date, for interop with ICU. diff --git a/Tests/FoundationEssentialsTests/DecimalTests.swift b/Tests/FoundationEssentialsTests/DecimalTests.swift index b89f921cb..9163d1878 100644 --- a/Tests/FoundationEssentialsTests/DecimalTests.swift +++ b/Tests/FoundationEssentialsTests/DecimalTests.swift @@ -17,6 +17,7 @@ import TestSupport #if FOUNDATION_FRAMEWORK @testable import Foundation #else +@_spi(SwiftCorelibsFoundation) @testable import FoundationEssentials #endif @@ -111,10 +112,10 @@ final class DecimalTests : XCTestCase { func test_DescriptionWithLocale() { let decimal = Decimal(string: "-123456.789")! - XCTAssertEqual(decimal.toString(with: nil), "-123456.789") - let en = decimal.toString(with: Locale(identifier: "en_GB")) + XCTAssertEqual(decimal._toString(with: nil), "-123456.789") + let en = decimal._toString(with: Locale(identifier: "en_GB")) XCTAssertEqual(en, "-123456.789") - let fr = decimal.toString(with: Locale(identifier: "fr_FR")) + let fr = decimal._toString(with: Locale(identifier: "fr_FR")) XCTAssertEqual(fr, "-123456,789") } @@ -1208,6 +1209,17 @@ final class DecimalTests : XCTestCase { XCTAssertNotEqual(x.nextUp, x) } + #if FOUNDATION_FRAMEWORK + #else + func test_toString() { + let decimal = Decimal(string: "-123456.789")! + XCTAssertEqual(decimal.toString(with: nil), "-123456.789") + let en = decimal.toString(with: Locale(identifier: "en_GB")) + XCTAssertEqual(en, "-123456.789") + let fr = decimal.toString(with: Locale(identifier: "fr_FR")) + XCTAssertEqual(fr, "-123456,789") + } + func test_int64Value() { XCTAssertEqual(Decimal(-1).int64Value, -1) XCTAssertEqual(Decimal(0).int64Value, 0) @@ -1230,4 +1242,26 @@ final class DecimalTests : XCTestCase { let pi = Decimal(Double.pi) XCTAssertEqual(pi.int64Value, 3) } + + func test_doubleValue() { + XCTAssertEqual(Decimal(0).doubleValue, 0) + XCTAssertEqual(Decimal(1).doubleValue, 1) + XCTAssertEqual(Decimal(-1).doubleValue, -1) + XCTAssertTrue(Decimal.nan.doubleValue.isNaN) + XCTAssertEqual(Decimal(UInt64.max).doubleValue, Double(1.8446744073709552e+19)) + } + + func test_decimalFromString() { + let string = "x123x" + let scanLocation = 1 + + let start = string.index(string.startIndex, offsetBy: scanLocation, limitedBy: string.endIndex)! + let substring = string[start..