From 6450221c7fea77b8c458ca7baac953cc939c41ae Mon Sep 17 00:00:00 2001 From: Simon Evans Date: Thu, 2 Aug 2018 02:27:05 +0100 Subject: [PATCH 1/3] SR-1464: NSNumber.description is not compatible between OS X and linux - Use Swift's stringification of numbers for .description and .stringValue - Use String(format:locale:...) with the correct format for the NSNumber type if locale is not nil. - Enable use of __CFStringFormatLocalizedNumber() on DEPLOYMENT_TARGET_LINUX. --- CoreFoundation/String.subproj/CFString.c | 4 +- Foundation/NSNumber.swift | 93 +++++++++++++++----- TestFoundation/TestNSNumber.swift | 105 ++++++++++++++++++++--- TestFoundation/TestNSString.swift | 6 +- 4 files changed, 166 insertions(+), 42 deletions(-) diff --git a/CoreFoundation/String.subproj/CFString.c b/CoreFoundation/String.subproj/CFString.c index 8c943636dd..4ae5e36bda 100644 --- a/CoreFoundation/String.subproj/CFString.c +++ b/CoreFoundation/String.subproj/CFString.c @@ -6013,7 +6013,7 @@ enum { CFFormatDummyPointerType = 42 /* special case for %n */ }; -#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS +#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX /* Only come in here if spec->type is CFFormatLongType or CFFormatDoubleType. Pass in 0 for width or precision if not specified. Returns false if couldn't do the format (with the assumption the caller falls back to unlocalized). */ static Boolean __CFStringFormatLocalizedNumber(CFMutableStringRef output, CFLocaleRef locale, const CFPrintValue *values, const CFFormatSpec *spec, SInt32 width, SInt32 precision, Boolean hasPrecision) { @@ -7109,7 +7109,7 @@ static Boolean __CFStringAppendFormatCore(CFMutableStringRef outputString, CFStr switch (specs[curSpec].type) { case CFFormatLongType: case CFFormatDoubleType: -#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS +#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX if (localizedFormatting && (specs[curSpec].flags & kCFStringFormatLocalizable)) { // We have a locale, so we do localized formatting if (__CFStringFormatLocalizedNumber(outputString, (CFLocaleRef)formatOptions, values, &specs[curSpec], width, precision, hasPrecision)) break; } diff --git a/Foundation/NSNumber.swift b/Foundation/NSNumber.swift index b693bbee5c..2e8e4768e0 100644 --- a/Foundation/NSNumber.swift +++ b/Foundation/NSNumber.swift @@ -915,7 +915,7 @@ open class NSNumber : NSValue { } open var stringValue: String { - return description(withLocale: nil) + return self.description } /// Create an instance initialized to `value`. @@ -958,29 +958,47 @@ open class NSNumber : NSValue { } } - private static let _numberFormatterForNilLocale: CFNumberFormatter = { - let formatter: CFNumberFormatter - formatter = CFNumberFormatterCreate(nil, CFLocaleCopyCurrent(), kCFNumberFormatterNoStyle) - CFNumberFormatterSetProperty(formatter, kCFNumberFormatterMaxFractionDigits, 15._bridgeToObjectiveC()) - return formatter - }() - open func description(withLocale locale: Locale?) -> String { - // CFNumberFormatterCreateStringWithNumber() does not like numbers of type - // SInt128Type, as it loses the type when looking it up and treats it as - // an SInt64Type, so special case them. - if _CFNumberGetType2(_cfObject) == kCFNumberSInt128Type { - return String(format: "%@", unsafeBitCast(_cfObject, to: UnsafePointer.self)) - } + guard let locale = locale else { return self.description } + switch _CFNumberGetType2(_cfObject) { + case kCFNumberSInt8Type, kCFNumberCharType: + return String(format: "%d", locale: locale, self.int8Value) - let aLocale = locale - let formatter: CFNumberFormatter - if (aLocale == nil) { - formatter = NSNumber._numberFormatterForNilLocale - } else { - formatter = CFNumberFormatterCreate(nil, aLocale?._cfObject, kCFNumberFormatterDecimalStyle) + case kCFNumberSInt16Type, kCFNumberShortType: + return String(format: "%hi", locale: locale, self.int16Value) + + case kCFNumberSInt32Type: + return String(format: "%d", locale: locale, self.int32Value) + + case kCFNumberIntType, kCFNumberLongType, kCFNumberNSIntegerType, kCFNumberCFIndexType: + return String(format: "%ld", locale: locale, self.intValue) + + case kCFNumberSInt64Type, kCFNumberLongLongType: + return String(format: "%lld", locale: locale, self.int64Value) + + case kCFNumberSInt128Type: + let value = self.int128Value + if value.high == 0 { + return value.low.description // BUG: "%llu" doesnt work correctly and treats number as signed + } else { + // BUG: Note the locale is actually ignored here as this is converted using CFNumber.c:emit128() + return String(format: "%@", locale: locale, unsafeBitCast(_cfObject, to: UnsafePointer.self)) + } + + case kCFNumberFloatType, kCFNumberCGFloatType, kCFNumberFloatType: + return String(format: "%0.7g", locale: locale, self.floatValue) + + case kCFNumberFloat64Type, kCFNumberDoubleType: + return String(format: "%0.16g", locale: locale, self.doubleValue) + + case kCFNumberCGFloatType: + if Int.max == Int32.max { + return String(format: "%0.7g", locale: locale, self.floatValue) + } else { + return String(format: "%0.16g", locale: locale, self.doubleValue) + } + default: fatalError("Unknown NSNumber Type") } - return CFNumberFormatterCreateStringWithNumber(nil, formatter, self._cfObject)._swiftObject } override open var _cfTypeID: CFTypeID { @@ -988,9 +1006,38 @@ open class NSNumber : NSValue { } open override var description: String { - return description(withLocale: nil) + switch _CFNumberGetType2(_cfObject) { + case kCFNumberSInt8Type, kCFNumberCharType, kCFNumberSInt16Type, kCFNumberShortType, + kCFNumberSInt32Type, kCFNumberIntType, kCFNumberLongType, kCFNumberNSIntegerType, kCFNumberCFIndexType: + return self.intValue.description + + case kCFNumberSInt64Type, kCFNumberLongLongType: + return self.int64Value.description + + case kCFNumberSInt128Type: + let value = self.int128Value + if value.high == 0 { + return value.low.description + } else { + return String(format: "%@", locale: nil, unsafeBitCast(_cfObject, to: UnsafePointer.self)) + } + + case kCFNumberFloatType, kCFNumberCGFloatType, kCFNumberFloatType: + return self.floatValue.description + + case kCFNumberFloat64Type, kCFNumberDoubleType: + return self.doubleValue.description + + case kCFNumberCGFloatType: + if Int.max == Int32.max { + return self.floatValue.description + } else { + return self.doubleValue.description + } + default: fatalError("Unknown NSNumber Type") + } } - + internal func _cfNumberType() -> CFNumberType { switch objCType.pointee { case 0x42: return kCFNumberCharType diff --git a/TestFoundation/TestNSNumber.swift b/TestFoundation/TestNSNumber.swift index 204bb890b1..1d2108fb65 100644 --- a/TestFoundation/TestNSNumber.swift +++ b/TestFoundation/TestNSNumber.swift @@ -1016,21 +1016,100 @@ class TestNSNumber : XCTestCase { func test_description() { - let nsnumber: NSNumber = 1000 - let expectedDesc = "1000" - XCTAssertEqual(nsnumber.description, expectedDesc, "expected \(expectedDesc) but received \(nsnumber.description)") + XCTAssertEqual(NSNumber(value: 1000).description, "1000") + XCTAssertEqual(NSNumber(value: 0.001).description, "0.001") + + XCTAssertEqual(NSNumber(value: Int8.min).description, "-128") + XCTAssertEqual(NSNumber(value: Int8.max).description, "127") + XCTAssertEqual(NSNumber(value: Int16.min).description, "-32768") + XCTAssertEqual(NSNumber(value: Int16.max).description, "32767") + XCTAssertEqual(NSNumber(value: Int32.min).description, "-2147483648") + XCTAssertEqual(NSNumber(value: Int32.max).description, "2147483647") + XCTAssertEqual(NSNumber(value: Int64.min).description, "-9223372036854775808") + XCTAssertEqual(NSNumber(value: Int64.max).description, "9223372036854775807") + + XCTAssertEqual(NSNumber(value: UInt8.min).description, "0") + XCTAssertEqual(NSNumber(value: UInt8.max).description, "255") + XCTAssertEqual(NSNumber(value: UInt16.min).description, "0") + XCTAssertEqual(NSNumber(value: UInt16.max).description, "65535") + XCTAssertEqual(NSNumber(value: UInt32.min).description, "0") + XCTAssertEqual(NSNumber(value: UInt32.max).description, "4294967295") + XCTAssertEqual(NSNumber(value: UInt64.min).description, "0") + XCTAssertEqual(NSNumber(value: UInt64.max).description, "18446744073709551615") } - + func test_descriptionWithLocale() { - let nsnumber: NSNumber = 1000 - let values : Dictionary = [ - Locale(identifier: "en_GB") : "1,000", - Locale(identifier: "de_DE") : "1.000", - ] - for (locale, expectedDesc) in values { - let receivedDesc = nsnumber.description(withLocale: locale) - XCTAssertEqual(receivedDesc, expectedDesc, "expected \(expectedDesc) but received \(receivedDesc)") - } + // nil Locale + XCTAssertEqual(NSNumber(value: 1000).description(withLocale: nil), "1000") + XCTAssertEqual(NSNumber(value: 0.001).description(withLocale: nil), "0.001") + + XCTAssertEqual(NSNumber(value: Int8.min).description(withLocale: nil), "-128") + XCTAssertEqual(NSNumber(value: Int8.max).description(withLocale: nil), "127") + XCTAssertEqual(NSNumber(value: Int16.min).description(withLocale: nil), "-32768") + XCTAssertEqual(NSNumber(value: Int16.max).description(withLocale: nil), "32767") + XCTAssertEqual(NSNumber(value: Int32.min).description(withLocale: nil), "-2147483648") + XCTAssertEqual(NSNumber(value: Int32.max).description(withLocale: nil), "2147483647") + XCTAssertEqual(NSNumber(value: Int64.min).description(withLocale: nil), "-9223372036854775808") + XCTAssertEqual(NSNumber(value: Int64.max).description(withLocale: nil), "9223372036854775807") + + XCTAssertEqual(NSNumber(value: UInt8.min).description(withLocale: nil), "0") + XCTAssertEqual(NSNumber(value: UInt8.max).description(withLocale: nil), "255") + XCTAssertEqual(NSNumber(value: UInt16.min).description(withLocale: nil), "0") + XCTAssertEqual(NSNumber(value: UInt16.max).description(withLocale: nil), "65535") + XCTAssertEqual(NSNumber(value: UInt32.min).description(withLocale: nil), "0") + XCTAssertEqual(NSNumber(value: UInt32.max).description(withLocale: nil), "4294967295") + XCTAssertEqual(NSNumber(value: UInt64.min).description(withLocale: nil), "0") + XCTAssertEqual(NSNumber(value: UInt64.max).description(withLocale: nil), "18446744073709551615") + + // en_GB Locale + XCTAssertEqual(NSNumber(value: 1000).description(withLocale: Locale(identifier: "en_GB")), "1,000") + XCTAssertEqual(NSNumber(value: 0.001).description(withLocale: Locale(identifier: "en_GB")), "0.001") + + XCTAssertEqual(NSNumber(value: Int8.min).description(withLocale: Locale(identifier: "en_GB")), "-128") + XCTAssertEqual(NSNumber(value: Int8.max).description(withLocale: Locale(identifier: "en_GB")), "127") + XCTAssertEqual(NSNumber(value: Int16.min).description(withLocale: Locale(identifier: "en_GB")), "-32,768") + XCTAssertEqual(NSNumber(value: Int16.max).description(withLocale: Locale(identifier: "en_GB")), "32,767") + XCTAssertEqual(NSNumber(value: Int32.min).description(withLocale: Locale(identifier: "en_GB")), "-2,147,483,648") + XCTAssertEqual(NSNumber(value: Int32.max).description(withLocale: Locale(identifier: "en_GB")), "2,147,483,647") + XCTAssertEqual(NSNumber(value: Int64.min).description(withLocale: Locale(identifier: "en_GB")), "-9,223,372,036,854,775,808") + XCTAssertEqual(NSNumber(value: Int64.max).description(withLocale: Locale(identifier: "en_GB")), "9,223,372,036,854,775,807") + + XCTAssertEqual(NSNumber(value: UInt8.min).description(withLocale: Locale(identifier: "en_GB")), "0") + XCTAssertEqual(NSNumber(value: UInt8.max).description(withLocale: Locale(identifier: "en_GB")), "255") + XCTAssertEqual(NSNumber(value: UInt16.min).description(withLocale: Locale(identifier: "en_GB")), "0") + XCTAssertEqual(NSNumber(value: UInt16.max).description(withLocale: Locale(identifier: "en_GB")), "65,535") + XCTAssertEqual(NSNumber(value: UInt32.min).description(withLocale: Locale(identifier: "en_GB")), "0") + XCTAssertEqual(NSNumber(value: UInt32.max).description(withLocale: Locale(identifier: "en_GB")), "4,294,967,295") + XCTAssertEqual(NSNumber(value: UInt64.min).description(withLocale: Locale(identifier: "en_GB")), "0") + + // This is the correct value but currently buggy and the locale is not used + // XCTAssertEqual(NSNumber(value: UInt64.max).description(withLocale: Locale(identifier: "en_GB")), "18,446,744,073,709,551,615") + XCTAssertEqual(NSNumber(value: UInt64.max).description(withLocale: Locale(identifier: "en_GB")), "18446744073709551615") + + // de_DE Locale + XCTAssertEqual(NSNumber(value: 1000).description(withLocale: Locale(identifier: "de_DE")), "1.000") + XCTAssertEqual(NSNumber(value: 0.001).description(withLocale: Locale(identifier: "de_DE")), "0,001") + + XCTAssertEqual(NSNumber(value: Int8.min).description(withLocale: Locale(identifier: "de_DE")), "-128") + XCTAssertEqual(NSNumber(value: Int8.max).description(withLocale: Locale(identifier: "de_DE")), "127") + XCTAssertEqual(NSNumber(value: Int16.min).description(withLocale: Locale(identifier: "de_DE")), "-32.768") + XCTAssertEqual(NSNumber(value: Int16.max).description(withLocale: Locale(identifier: "de_DE")), "32.767") + XCTAssertEqual(NSNumber(value: Int32.min).description(withLocale: Locale(identifier: "de_DE")), "-2.147.483.648") + XCTAssertEqual(NSNumber(value: Int32.max).description(withLocale: Locale(identifier: "de_DE")), "2.147.483.647") + XCTAssertEqual(NSNumber(value: Int64.min).description(withLocale: Locale(identifier: "de_DE")), "-9.223.372.036.854.775.808") + XCTAssertEqual(NSNumber(value: Int64.max).description(withLocale: Locale(identifier: "de_DE")), "9.223.372.036.854.775.807") + + XCTAssertEqual(NSNumber(value: UInt8.min).description(withLocale: Locale(identifier: "de_DE")), "0") + XCTAssertEqual(NSNumber(value: UInt8.max).description(withLocale: Locale(identifier: "de_DE")), "255") + XCTAssertEqual(NSNumber(value: UInt16.min).description(withLocale: Locale(identifier: "de_DE")), "0") + XCTAssertEqual(NSNumber(value: UInt16.max).description(withLocale: Locale(identifier: "de_DE")), "65.535") + XCTAssertEqual(NSNumber(value: UInt32.min).description(withLocale: Locale(identifier: "de_DE")), "0") + XCTAssertEqual(NSNumber(value: UInt32.max).description(withLocale: Locale(identifier: "de_DE")), "4.294.967.295") + XCTAssertEqual(NSNumber(value: UInt64.min).description(withLocale: Locale(identifier: "de_DE")), "0") + + // This is the correct value but currently buggy and the locale is not used + //XCTAssertEqual(NSNumber(value: UInt64.max).description(withLocale: Locale(identifier: "de_DE")), "18.446.744.073.709.551.615") + XCTAssertEqual(NSNumber(value: UInt64.max).description(withLocale: Locale(identifier: "de_DE")), "18446744073709551615") } func test_objCType() { diff --git a/TestFoundation/TestNSString.swift b/TestFoundation/TestNSString.swift index c6cc5a25e7..ac4635d8b1 100755 --- a/TestFoundation/TestNSString.swift +++ b/TestFoundation/TestNSString.swift @@ -832,19 +832,17 @@ class TestNSString: LoopbackServerTest { XCTAssertEqual(string, "Default value is 1000 (42.0)") } -#if false // these two tests expose bugs in icu4c's localization on some linux builds (disable until we can get a uniform fix for this) withVaList(argument) { pointer in - let string = NSString(format: "en_GB value is %d (%.1f)", locale: Locale.init(localeIdentifier: "en_GB"), arguments: pointer) + let string = NSString(format: "en_GB value is %d (%.1f)", locale: Locale.init(identifier: "en_GB") as AnyObject, arguments: pointer) XCTAssertEqual(string, "en_GB value is 1,000 (42.0)") } withVaList(argument) { pointer in - let string = NSString(format: "de_DE value is %d (%.1f)", locale: Locale.init(localeIdentifier: "de_DE"), arguments: pointer) + let string = NSString(format: "de_DE value is %d (%.1f)", locale: Locale.init(identifier: "de_DE") as AnyObject, arguments: pointer) XCTAssertEqual(string, "de_DE value is 1.000 (42,0)") } -#endif withVaList(argument) { pointer in From 2480df61747596b9795e967d137d6d0fda59e6e9 Mon Sep 17 00:00:00 2001 From: Simon Evans Date: Sun, 14 Oct 2018 21:28:40 +0100 Subject: [PATCH 2/3] SR-1464: Update ifdef to use TARGET_OS_* --- CoreFoundation/String.subproj/CFString.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CoreFoundation/String.subproj/CFString.c b/CoreFoundation/String.subproj/CFString.c index 4ae5e36bda..bc589acc67 100644 --- a/CoreFoundation/String.subproj/CFString.c +++ b/CoreFoundation/String.subproj/CFString.c @@ -6013,7 +6013,7 @@ enum { CFFormatDummyPointerType = 42 /* special case for %n */ }; -#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX +#if TARGET_OS_MAC || TARGET_OS_WIN32 || TARGET_OS_LINUX /* Only come in here if spec->type is CFFormatLongType or CFFormatDoubleType. Pass in 0 for width or precision if not specified. Returns false if couldn't do the format (with the assumption the caller falls back to unlocalized). */ static Boolean __CFStringFormatLocalizedNumber(CFMutableStringRef output, CFLocaleRef locale, const CFPrintValue *values, const CFFormatSpec *spec, SInt32 width, SInt32 precision, Boolean hasPrecision) { @@ -7109,7 +7109,7 @@ static Boolean __CFStringAppendFormatCore(CFMutableStringRef outputString, CFStr switch (specs[curSpec].type) { case CFFormatLongType: case CFFormatDoubleType: -#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX +#if TARGET_OS_MAC || TARGET_OS_WIN32 || TARGET_OS_LINUX if (localizedFormatting && (specs[curSpec].flags & kCFStringFormatLocalizable)) { // We have a locale, so we do localized formatting if (__CFStringFormatLocalizedNumber(outputString, (CFLocaleRef)formatOptions, values, &specs[curSpec], width, precision, hasPrecision)) break; } From 7cb608f2d0d666223f5fd340eaa686c769b30bb4 Mon Sep 17 00:00:00 2001 From: Simon Evans Date: Tue, 13 Nov 2018 22:39:53 +0000 Subject: [PATCH 3/3] SR-1464: Add NSDecimalNumber.description --- Foundation/NSDecimalNumber.swift | 6 +++++- TestFoundation/TestDecimal.swift | 27 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Foundation/NSDecimalNumber.swift b/Foundation/NSDecimalNumber.swift index 68ce7fc81b..355fc5e969 100644 --- a/Foundation/NSDecimalNumber.swift +++ b/Foundation/NSDecimalNumber.swift @@ -212,7 +212,11 @@ open class NSDecimalNumber : NSNumber { public required convenience init(bytes buffer: UnsafeRawPointer, objCType type: UnsafePointer) { NSRequiresConcreteImplementation() } - + + open override var description: String { + return self.decimal.description + } + open override func description(withLocale locale: Locale?) -> String { guard locale == nil else { fatalError("Locale not supported: \(locale!)") diff --git a/TestFoundation/TestDecimal.swift b/TestFoundation/TestDecimal.swift index 4a1312fbfc..ad73d7e8bd 100644 --- a/TestFoundation/TestDecimal.swift +++ b/TestFoundation/TestDecimal.swift @@ -518,7 +518,7 @@ class TestDecimal: XCTestCase { XCTAssertEqual(1, ten._length) } - func test_NSDecimal() { + func test_NSDecimal() throws { var nan = Decimal.nan XCTAssertTrue(NSDecimalIsNotANumber(&nan)) var zero = Decimal() @@ -539,6 +539,31 @@ class TestDecimal: XCTestCase { let nsd1 = NSDecimalNumber(decimal: Decimal(2657.6)) let nsd2 = NSDecimalNumber(floatLiteral: 2657.6) XCTAssertEqual(nsd1, nsd2) + + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(Int8.min)).description, Int8.min.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(Int8.max)).description, Int8.max.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(UInt8.min)).description, UInt8.min.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(UInt8.max)).description, UInt8.max.description) + + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(Int16.min)).description, Int16.min.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(Int16.max)).description, Int16.max.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(UInt16.min)).description, UInt16.min.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(UInt16.max)).description, UInt16.max.description) + + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(Int32.min)).description, Int32.min.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(Int32.max)).description, Int32.max.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(UInt32.min)).description, UInt32.min.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(UInt32.max)).description, UInt32.max.description) + + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(Int64.min)).description, Int64.min.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(Int64.max)).description, Int64.max.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(UInt64.min)).description, UInt64.min.description) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(UInt64.max)).description, UInt64.max.description) + + XCTAssertEqual(try NSDecimalNumber(decimal: Decimal(string: "12.34").unwrapped()).description, "12.34") + XCTAssertEqual(try NSDecimalNumber(decimal: Decimal(string: "0.0001").unwrapped()).description, "0.0001") + XCTAssertEqual(try NSDecimalNumber(decimal: Decimal(string: "-1.0002").unwrapped()).description, "-1.0002") + XCTAssertEqual(try NSDecimalNumber(decimal: Decimal(string: "0.0").unwrapped()).description, "0") } func test_PositivePowers() {