From 921361726557c2a66bc5d7b0b86a097bff13d5f2 Mon Sep 17 00:00:00 2001 From: Alex Blewitt Date: Thu, 13 Oct 2016 13:31:33 +0100 Subject: [PATCH 1/2] Final implementation of Decimal This extends the normalisation routines to be able to handle mismatched size elements, and division of decimals that are larger than short values correctly. It adds tests that are the same coverage as for NSDecimal on Darwin. --- Foundation/NSDecimal.swift | 235 +++++++++------ TestFoundation/TestNSDecimal.swift | 451 +++++++++++++++++++++++------ 2 files changed, 511 insertions(+), 175 deletions(-) diff --git a/Foundation/NSDecimal.swift b/Foundation/NSDecimal.swift index c5a999f23f..8a0eb68eed 100644 --- a/Foundation/NSDecimal.swift +++ b/Foundation/NSDecimal.swift @@ -33,6 +33,9 @@ public struct Decimal { return UInt32((__lengthAndFlags & 0b0000_1111)) } set { + guard newValue <= maxMantissaLength else { + fatalError("Attempt to set a length greater than capacity \(newValue) > \(maxMantissaLength)") + } __lengthAndFlags = (__lengthAndFlags & 0b1111_0000) | UInt8(newValue & 0b0000_1111) @@ -88,9 +91,8 @@ public struct Decimal { public init(_exponent: Int32, _length: UInt32, _isNegative: UInt32, _isCompact: UInt32, _reserved: UInt32, _mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)){ self._mantissa = _mantissa self.__exponent = Int8(truncatingBitPattern: _exponent) - self.__lengthAndFlags = 0 + self.__lengthAndFlags = UInt8(_length & 0b1111) self.__reserved = 0 - self._length = _length self._isNegative = _isNegative self._isCompact = _isCompact self._reserved = _reserved @@ -564,9 +566,7 @@ fileprivate func divideByShort(_ d: inout T, _ divisor:U d[i] = UInt16(accumulator / UInt32(divisor)) carry = accumulator % UInt32(divisor) } - while d._length != 0 && d[d._length - 1] == 0 { - d._length -= 1 - } + d.trimTrailingZeros() return (UInt16(carry),.noError) } @@ -674,7 +674,7 @@ fileprivate func fitMantissa(_ big: inout WideDecimal, _ exponent: inout Int32, var previousRemainder: Bool = false // Divide by 10 as much as possible - while big._length >= Decimal.maxSize { + while big._length > Decimal.maxSize + 1 { if remainder != 0 { previousRemainder = true } @@ -727,21 +727,19 @@ fileprivate func fitMantissa(_ big: inout WideDecimal, _ exponent: inout Int32, return .lossOfPrecision; } -fileprivate func integerMultiply(_ big: inout WideDecimal, - _ left: WideDecimal, - _ right: WideDecimal) -> NSDecimalNumber.CalculationError { +fileprivate func integerMultiply(_ big: inout T, + _ left: T, + _ right: Decimal) -> NSDecimalNumber.CalculationError { if left._length == 0 || right._length == 0 { big._length = 0 return .noError } - if big._length > left._length + right._length { - big._length = left._length + right._length + if big._length == 0 || big._length > left._length + right._length { + big._length = min(big.maxMantissaLength,left._length + right._length) } - for i in 0.. NSDecimalNumber.CalculationError { +fileprivate func integerDivide(_ r: inout T, + _ cu: T, + _ cv: Decimal) -> NSDecimalNumber.CalculationError { // Calculate result = a / b. // Result could NOT be a pointer to same space as a or b. // resultLen must be >= aLen - bLen. @@ -792,17 +788,19 @@ fileprivate func integerDivide(_ r: inout WideDecimal, var u = WideDecimal(true) var v = WideDecimal(true) // divisor - var v1:UInt16, v2:UInt16 - // Simple case if cv.isZero { return .divideByZero; } // If u < v, the result is approximately 0... - if cu < cv { - r._length = 0 - return .noError; + if cu._length < cv._length { + for i in 0.. 1 is probably useless, since optimizations // up there are taking over this case. I'll keep it, just in case. - v1 = v[v._length-1] - v2 = v._length > 1 ? v[v._length-2] : 0 + let v1:UInt16 = v[v._length-1] + let v2:UInt16 = v._length > 1 ? v[v._length-2] : 0 // D2: initialize j // On each pass, build a single value for the quotient. for j in 0..> 16 // multiplication carry acc = acc & 0xffff; - let uu = u // work around compiler bug - acc = 0xffff + UInt32(uu[ul - vl + i - UInt32(j) - UInt32(1)]) - acc + sk; // subtract + acc = 0xffff + UInt32(u[ul - vl + i - UInt32(j) - UInt32(1)]) - acc + sk; // subtract sk = acc >> 16; u[ul - vl + i - UInt32(j) - UInt32(1)] = UInt16(truncatingBitPattern:acc) } @@ -905,9 +906,7 @@ fileprivate func integerDivide(_ r: inout WideDecimal, for i in 0...v._length { let ul = u._length let vl = v._length - let vv = v // work around compiler bug - let uu = u // work around compiler bug - acc = UInt32(vv[i]) + UInt32(uu[UInt32(ul) - UInt32(vl) + UInt32(i) - UInt32(j) - UInt32(1)]) + k + acc = UInt32(v[i]) + UInt32(u[UInt32(ul) - UInt32(vl) + UInt32(i) - UInt32(j) - UInt32(1)]) + k k = acc >> 16; u[UInt32(ul) - UInt32(vl) + UInt32(i) - UInt32(j) - UInt32(1)] = UInt16(truncatingBitPattern:acc) } @@ -920,14 +919,12 @@ fileprivate func integerDivide(_ r: inout WideDecimal, r._length = UInt32(ql); - while r._length != 0 && r[r._length - 1] == 0 { - r._length -= 1 - } + r.trimTrailingZeros() return .noError; } -fileprivate func integerMultiplyByPowerOf10(_ result: inout WideDecimal, _ left: WideDecimal, _ p: Int) -> NSDecimalNumber.CalculationError { +fileprivate func integerMultiplyByPowerOf10(_ result: inout T, _ left: T, _ p: Int) -> NSDecimalNumber.CalculationError { var power = p if power == 0 { result = left @@ -943,10 +940,10 @@ fileprivate func integerMultiplyByPowerOf10(_ result: inout WideDecimal, _ left: var error:NSDecimalNumber.CalculationError = .noError while power > maxpow10 { - var big = WideDecimal() + var big = T() power -= maxpow10 - let p10 = WideDecimal(pow10[maxpow10]) + let p10 = pow10[maxpow10] if !isNegative { error = integerMultiply(&big,result,p10) @@ -965,9 +962,10 @@ fileprivate func integerMultiplyByPowerOf10(_ result: inout WideDecimal, _ left: result._length = big._length } - var big = WideDecimal() + var big = T() + // Handle the rest of the power (<= maxpow10) - let p10 = WideDecimal(pow10[Int(power)]) + let p10 = pow10[Int(power)] if !isNegative { error = integerMultiply(&big, result, p10) @@ -994,7 +992,7 @@ public func NSDecimalRound(_ result: UnsafeMutablePointer, _ number: Un public func NSDecimalNormalize(_ a: UnsafeMutablePointer, _ b: UnsafeMutablePointer, _ roundingMode: NSDecimalNumber.RoundingMode) -> NSDecimalNumber.CalculationError { var diffexp = a.pointee.__exponent - b.pointee.__exponent - var result = WideDecimal() + var result = Decimal() // // If the two numbers share the same exponents, @@ -1030,19 +1028,49 @@ public func NSDecimalNormalize(_ a: UnsafeMutablePointer, _ b: UnsafeMu // Try to multiply aa to reach the same exponent level than bb // - if integerMultiplyByPowerOf10(&result, WideDecimal(aa.pointee), Int(diffexp)) == .noError { + if integerMultiplyByPowerOf10(&result, aa.pointee, Int(diffexp)) == .noError { // Succeed. Adjust the length/exponent info // and return no errorNSDecimalNormalize - aa.pointee._length = result._length - for i in 0.. 0 multiply aa by the same value + // + if !bb.pointee.isZero { + _ = integerMultiplyByPowerOf10(&result, aa.pointee, Int(maxpow10)) + aa.pointee.copyMantissa(from: result) + aa.pointee._exponent -= Int32(maxpow10) + } else { + bb.pointee._exponent = aa.pointee._exponent; + } + + // + // the two exponents are now identical, but we've lost some digits in the operation. + // + return .lossOfPrecision; } public func NSDecimalAdd(_ result: UnsafeMutablePointer, _ leftOperand: UnsafePointer, _ rightOperand: UnsafePointer, _ roundingMode: NSDecimalNumber.RoundingMode) -> NSDecimalNumber.CalculationError { @@ -1233,9 +1261,7 @@ fileprivate func integerSubtract(_ result: inout Decimal, _ left: inout Decimal, } result._length = i; - while result._length != 0 && result[result._length - 1] == 0 { - result._length -= 1 - } + result.trimTrailingZeros() return .noError; } @@ -1265,7 +1291,7 @@ public func NSDecimalMultiply(_ result: UnsafeMutablePointer, _ leftOpe var big = WideDecimal() var calculationError:NSDecimalNumber.CalculationError = .noError - calculationError = integerMultiply(&big,WideDecimal(leftOperand.pointee),WideDecimal(rightOperand.pointee)) + calculationError = integerMultiply(&big,WideDecimal(leftOperand.pointee),rightOperand.pointee) result.pointee._isNegative = (leftOperand.pointee._isNegative + rightOperand.pointee._isNegative) % 2 @@ -1340,7 +1366,7 @@ public func NSDecimalDivide(_ result: UnsafeMutablePointer, _ leftOpera } _ = integerMultiplyByPowerOf10(&big, WideDecimal(a), 38) // Trust me, it's 38 ! - _ = integerDivide(&big, big, WideDecimal(b)) + _ = integerDivide(&big, big, b) _ = fitMantissa(&big, &exponent, .down) let length = min(big._length,Decimal.maxSize) @@ -1393,16 +1419,77 @@ public func NSDecimalString(_ dcm: UnsafePointer, _ locale: AnyObject?) fileprivate protocol VariableLengthNumber { var _length: UInt32 { get set } + init() subscript(index:UInt32) -> UInt16 { get set } var isZero:Bool { get } + mutating func copyMantissa(from other:T) + mutating func zeroMantissa() + mutating func trimTrailingZeros() + var maxMantissaLength: UInt32 { get } } extension Decimal: VariableLengthNumber { - + var maxMantissaLength:UInt32 { + return Decimal.maxSize + } + fileprivate mutating func zeroMantissa() { + for i in 0.. Decimal.maxSize { + _length = Decimal.maxSize + } + while _length != 0 && self[_length - 1] == 0 { + _length -= 1 + } + } + fileprivate mutating func copyMantissa(from other: T) { + if other._length > maxMantissaLength { + for i in maxMantissaLength..(from other: T) { + let length = other is Decimal ? min(other._length,Decimal.maxSize) : other._length + for i in 0.. \(maxMantissaLength)") } __length = UInt16(newValue) } } init(_ extraWide:Bool = false) { - __length = UInt16(Decimal.maxSize * 2) + __length = 0 _mantissa = (UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0),UInt16(0)) _extraWide = extraWide } @@ -1479,26 +1566,6 @@ fileprivate struct WideDecimal : VariableLengthNumber { } } } - public func compare(to rhs: WideDecimal) -> ComparisonResult { - if self._length > rhs._length { - return .orderedDescending - } - if self._length < rhs._length { - return .orderedAscending - } - for i in (0.. rhs[i] { - return .orderedDescending - } - if self[i] < rhs[i] { - return .orderedAscending - } - } - return .orderedSame - } - public static func <(lhs: WideDecimal, rhs: WideDecimal) -> Bool { - return lhs.compare(to:rhs) == .orderedAscending - } func toDecimal() -> Decimal { var result = Decimal() result._length = self._length diff --git a/TestFoundation/TestNSDecimal.swift b/TestFoundation/TestNSDecimal.swift index 72f33f472f..9620903534 100644 --- a/TestFoundation/TestNSDecimal.swift +++ b/TestFoundation/TestNSDecimal.swift @@ -19,18 +19,92 @@ class TestNSDecimal: XCTestCase { static var allTests : [(String, (TestNSDecimal) -> () throws -> Void)] { return [ + ("test_AdditionWithNormalization", test_AdditionWithNormalization), ("test_BasicConstruction", test_BasicConstruction), ("test_Constants", test_Constants), ("test_Description", test_Description), ("test_ExplicitConstruction", test_ExplicitConstruction), ("test_Maths", test_Maths), ("test_Misc", test_Misc), + ("test_MultiplicationOverflow", test_MultiplicationOverflow), + ("test_NaNInput", test_NaNInput), + ("test_NegativeAndZeroMultiplication", test_NegativeAndZeroMultiplication), ("test_Normalise", test_Normalise), - ("test_Round", test_Round), ("test_NSDecimal", test_NSDecimal), + // ("test_PositivePowers", test_PositivePowers), // needs NSDecimalNumber implementaiton + ("test_RepeatingDivision", test_RepeatingDivision), + ("test_Round", test_Round), + ("test_SimpleMultiplication", test_SimpleMultiplication), + // ("test_SmallerNumbers", test_SmallerNumbers), // needs NSDecimalImplementation + // ("test_ZeroPower", test_ZeroPower), // needs NSDecimalImplementation ] } + func test_AdditionWithNormalization() { + + let biggie = Decimal(65536) + let smallee = Decimal(65536) + let answer = biggie/smallee + XCTAssertEqual(Decimal(1),answer) + + var one = Decimal(1) + var addend = Decimal(1) + var expected = Decimal() + var result = Decimal() + + expected._isNegative = 0; + expected._isCompact = 0; + + // 2 digits -- certain to work + addend._exponent = -1; + XCTAssertEqual(.noError, NSDecimalAdd(&result, &one, &addend, .plain), "1 + 0.1") + expected._exponent = -1; + expected._length = 1; + expected._mantissa.0 = 11; + XCTAssertEqual(.orderedSame, NSDecimalCompare(&expected, &result), "1.1 == 1 + 0.1") + + // 38 digits -- guaranteed by NSDecimal to work + addend._exponent = -37; + XCTAssertEqual(.noError, NSDecimalAdd(&result, &one, &addend, .plain), "1 + 1e-37") + expected._exponent = -37; + expected._length = 8; + expected._mantissa.0 = 0x0001; + expected._mantissa.1 = 0x0000; + expected._mantissa.2 = 0x36a0; + expected._mantissa.3 = 0x00f4; + expected._mantissa.4 = 0x46d9; + expected._mantissa.5 = 0xd5da; + expected._mantissa.6 = 0xee10; + expected._mantissa.7 = 0x0785; + XCTAssertEqual(.orderedSame, NSDecimalCompare(&expected, &result), "1 + 1e-37") + + // 39 digits -- not guaranteed to work but it happens to, so we make the test work either way + addend._exponent = -38; + let error = NSDecimalAdd(&result, &one, &addend, .plain) + XCTAssertTrue(error == .noError || error == .lossOfPrecision, "1 + 1e-38") + if error == .noError { + expected._exponent = -38; + expected._length = 8; + expected._mantissa.0 = 0x0001; + expected._mantissa.1 = 0x0000; + expected._mantissa.2 = 0x2240; + expected._mantissa.3 = 0x098a; + expected._mantissa.4 = 0xc47a; + expected._mantissa.5 = 0x5a86; + expected._mantissa.6 = 0x4ca8; + expected._mantissa.7 = 0x4b3b; + XCTAssertEqual(.orderedSame, NSDecimalCompare(&expected, &result), "1 + 1e-38") + } else { + XCTAssertEqual(.orderedSame, NSDecimalCompare(&one, &result), "1 + 1e-38") + } + + // 40 digits -- doesn't work; need to make sure it's rounding for us + addend._exponent = -39; + XCTAssertEqual(.lossOfPrecision, NSDecimalAdd(&result, &one, &addend, .plain), "1 + 1e-39") + XCTAssertEqual("1", result.description) + XCTAssertEqual(.orderedSame, NSDecimalCompare(&one, &result), "1 + 1e-39") + } + func test_BasicConstruction() { let zero = Decimal() XCTAssertEqual(20, MemoryLayout.size) @@ -39,7 +113,7 @@ class TestNSDecimal: XCTestCase { XCTAssertEqual(0, zero._isNegative) XCTAssertEqual(0, zero._isCompact) XCTAssertEqual(0, zero._reserved) - let (m0,m1,m2,m3,m4,m5,m6,m7) = zero._mantissa + let (m0, m1, m2, m3, m4, m5, m6, m7) = zero._mantissa XCTAssertEqual(0, m0) XCTAssertEqual(0, m1) XCTAssertEqual(0, m2) @@ -59,47 +133,47 @@ class TestNSDecimal: XCTestCase { XCTAssertFalse(zero.isSignaling) } func test_Constants() { - XCTAssertEqual(8,NSDecimalMaxSize) - XCTAssertEqual(32767,NSDecimalNoScale) + XCTAssertEqual(8, NSDecimalMaxSize) + XCTAssertEqual(32767, NSDecimalNoScale) let smallest = Decimal(_exponent: 127, _length: 8, _isNegative: 1, _isCompact: 1, _reserved: 0, _mantissa: (UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max)) XCTAssertEqual(smallest, Decimal.leastFiniteMagnitude) let biggest = Decimal(_exponent: 127, _length: 8, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max)) XCTAssertEqual(biggest, Decimal.greatestFiniteMagnitude) - let leastNormal = Decimal(_exponent: -127, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1,0,0,0,0,0,0,0)) + let leastNormal = Decimal(_exponent: -127, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1, 0, 0, 0, 0, 0, 0, 0)) XCTAssertEqual(leastNormal, Decimal.leastNormalMagnitude) - let leastNonzero = Decimal(_exponent: -127, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1,0,0,0,0,0,0,0)) + let leastNonzero = Decimal(_exponent: -127, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1, 0, 0, 0, 0, 0, 0, 0)) XCTAssertEqual(leastNonzero, Decimal.leastNonzeroMagnitude) let pi = Decimal(_exponent: -38, _length: 8, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (0x6623, 0x7d57, 0x16e7, 0xad0d, 0xaf52, 0x4641, 0xdfa7, 0xec58)) - XCTAssertEqual(pi,Decimal.pi) - XCTAssertEqual(10,Decimal.radix) + XCTAssertEqual(pi, Decimal.pi) + XCTAssertEqual(10, Decimal.radix) XCTAssertTrue(Decimal().isCanonical) XCTAssertFalse(Decimal().isSignalingNaN) XCTAssertFalse(Decimal.nan.isSignalingNaN) XCTAssertTrue(Decimal.nan.isNaN) - XCTAssertEqual(.quietNaN,Decimal.nan.floatingPointClass) - XCTAssertEqual(.positiveZero,Decimal().floatingPointClass) - XCTAssertEqual(.negativeNormal,smallest.floatingPointClass) - XCTAssertEqual(.positiveNormal,biggest.floatingPointClass) + XCTAssertEqual(.quietNaN, Decimal.nan.floatingPointClass) + XCTAssertEqual(.positiveZero, Decimal().floatingPointClass) + XCTAssertEqual(.negativeNormal, smallest.floatingPointClass) + XCTAssertEqual(.positiveNormal, biggest.floatingPointClass) XCTAssertFalse(Double.nan.isFinite) XCTAssertFalse(Double.nan.isInfinite) } func test_Description() { - XCTAssertEqual("0",Decimal().description) - XCTAssertEqual("0",Decimal(0).description) - XCTAssertEqual("10",Decimal(_exponent: 1, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1,0,0,0,0,0,0,0)).description) - XCTAssertEqual("10",Decimal(10).description) - XCTAssertEqual("123.458",Decimal(_exponent: -3, _length: 2, _isNegative: 0, _isCompact:1, _reserved: 0, _mantissa: (57922,1,0,0,0,0,0,0)).description) - XCTAssertEqual("123.458",Decimal(123.458).description) - XCTAssertEqual("123",Decimal(UInt8(123)).description) - XCTAssertEqual("45",Decimal(Int8(45)).description) - XCTAssertEqual("3.14159265358979323846264338327950288419",Decimal.pi.description) - XCTAssertEqual("-30000000000",Decimal(sign: .minus, exponent: 10, significand: Decimal(3)).description) - XCTAssertEqual("300000",Decimal(sign: .plus, exponent: 5, significand: Decimal(3)).description) - XCTAssertEqual("5",Decimal(signOf: Decimal(3), magnitudeOf: Decimal(5)).description) - XCTAssertEqual("-5",Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(5)).description) - XCTAssertEqual("5",Decimal(signOf: Decimal(3), magnitudeOf: Decimal(-5)).description) - XCTAssertEqual("-5",Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(-5)).description) + XCTAssertEqual("0", Decimal().description) + XCTAssertEqual("0", Decimal(0).description) + XCTAssertEqual("10", Decimal(_exponent: 1, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1, 0, 0, 0, 0, 0, 0, 0)).description) + XCTAssertEqual("10", Decimal(10).description) + XCTAssertEqual("123.458", Decimal(_exponent: -3, _length: 2, _isNegative: 0, _isCompact:1, _reserved: 0, _mantissa: (57922, 1, 0, 0, 0, 0, 0, 0)).description) + XCTAssertEqual("123.458", Decimal(123.458).description) + XCTAssertEqual("123", Decimal(UInt8(123)).description) + XCTAssertEqual("45", Decimal(Int8(45)).description) + XCTAssertEqual("3.14159265358979323846264338327950288419", Decimal.pi.description) + XCTAssertEqual("-30000000000", Decimal(sign: .minus, exponent: 10, significand: Decimal(3)).description) + XCTAssertEqual("300000", Decimal(sign: .plus, exponent: 5, significand: Decimal(3)).description) + XCTAssertEqual("5", Decimal(signOf: Decimal(3), magnitudeOf: Decimal(5)).description) + XCTAssertEqual("-5", Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(5)).description) + XCTAssertEqual("5", Decimal(signOf: Decimal(3), magnitudeOf: Decimal(-5)).description) + XCTAssertEqual("-5", Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(-5)).description) } func test_ExplicitConstruction() { @@ -109,7 +183,7 @@ class TestNSDecimal: XCTestCase { _isNegative: 3, _isCompact: 4, _reserved: UInt32(1<<18 + 1<<17 + 1), - _mantissa: (6,7,8,9,10,11,12,13) + _mantissa: (6, 7, 8, 9, 10, 11, 12, 13) ) XCTAssertEqual(0x7f, explicit._exponent) XCTAssertEqual(0x7f, explicit.exponent) @@ -119,7 +193,7 @@ class TestNSDecimal: XCTestCase { XCTAssertTrue(explicit.isSignMinus) XCTAssertEqual(0, explicit._isCompact) XCTAssertEqual(UInt32(1<<17 + 1), explicit._reserved) - let (m0,m1,m2,m3,m4,m5,m6,m7) = explicit._mantissa + let (m0, m1, m2, m3, m4, m5, m6, m7) = explicit._mantissa XCTAssertEqual(6, m0) XCTAssertEqual(7, m1) XCTAssertEqual(8, m2) @@ -143,7 +217,7 @@ class TestNSDecimal: XCTestCase { XCTAssertEqual(0, significand._isNegative) XCTAssertEqual(1, significand._isCompact) XCTAssertEqual(0, significand._reserved) - let (sm0,sm1,sm2,sm3,sm4,sm5,sm6,sm7) = significand._mantissa + let (sm0, sm1, sm2, sm3, sm4, sm5, sm6, sm7) = significand._mantissa XCTAssertEqual(6, sm0) XCTAssertEqual(7, sm1) XCTAssertEqual(8, sm2) @@ -204,14 +278,14 @@ class TestNSDecimal: XCTestCase { } func test_Misc() { - XCTAssertEqual(.minus,Decimal(-5.2).sign) - XCTAssertEqual(.plus,Decimal(5.2).sign) + XCTAssertEqual(.minus, Decimal(-5.2).sign) + XCTAssertEqual(.plus, Decimal(5.2).sign) var d = Decimal(5.2) - XCTAssertEqual(.plus,d.sign) + XCTAssertEqual(.plus, d.sign) d.negate() - XCTAssertEqual(.minus,d.sign) + XCTAssertEqual(.minus, d.sign) d.negate() - XCTAssertEqual(.plus,d.sign) + XCTAssertEqual(.plus, d.sign) XCTAssertTrue(Decimal(3.5).isEqual(to: Decimal(3.5))) XCTAssertTrue(Decimal.nan.isEqual(to: Decimal.nan)) XCTAssertTrue(Decimal(1.28).isLess(than: Decimal(2.24))) @@ -228,46 +302,226 @@ class TestNSDecimal: XCTestCase { XCTAssertFalse(Decimal.nan.isTotallyOrdered(belowOrEqualTo: Decimal(2.3))) XCTAssertTrue(Decimal(2) < Decimal(3)) XCTAssertTrue(Decimal(3) > Decimal(2)) - XCTAssertEqual(3275573729074,Decimal(1234).hashValue) + XCTAssertEqual(3275573729074, Decimal(1234).hashValue) XCTAssertEqual(Decimal(-9), Decimal(1) - Decimal(10)) - XCTAssertEqual(Decimal(3),Decimal(2).nextUp) - XCTAssertEqual(Decimal(2),Decimal(3).nextDown) - XCTAssertEqual(Decimal(-476),Decimal(1024).distance(to: Decimal(1500))) - XCTAssertEqual(Decimal(68040),Decimal(386).advanced(by: Decimal(67654))) - XCTAssertEqual(Decimal(1.234),abs(Decimal(1.234))) - XCTAssertEqual(Decimal(1.234),abs(Decimal(-1.234))) + XCTAssertEqual(Decimal(3), Decimal(2).nextUp) + XCTAssertEqual(Decimal(2), Decimal(3).nextDown) + XCTAssertEqual(Decimal(-476), Decimal(1024).distance(to: Decimal(1500))) + XCTAssertEqual(Decimal(68040), Decimal(386).advanced(by: Decimal(67654))) + XCTAssertEqual(Decimal(1.234), abs(Decimal(1.234))) + XCTAssertEqual(Decimal(1.234), abs(Decimal(-1.234))) var a = Decimal(1234) - XCTAssertEqual(.noError,NSDecimalMultiplyByPowerOf10(&a,&a,1,.plain)) - XCTAssertEqual(Decimal(12340),a) + XCTAssertEqual(.noError, NSDecimalMultiplyByPowerOf10(&a, &a, 1, .plain)) + XCTAssertEqual(Decimal(12340), a) a = Decimal(1234) - XCTAssertEqual(.noError,NSDecimalMultiplyByPowerOf10(&a,&a,2,.plain)) - XCTAssertEqual(Decimal(123400),a) - XCTAssertEqual(.overflow,NSDecimalMultiplyByPowerOf10(&a,&a,128,.plain)) + XCTAssertEqual(.noError, NSDecimalMultiplyByPowerOf10(&a, &a, 2, .plain)) + XCTAssertEqual(Decimal(123400), a) + XCTAssertEqual(.overflow, NSDecimalMultiplyByPowerOf10(&a, &a, 128, .plain)) XCTAssertTrue(a.isNaN) a = Decimal(1234) - XCTAssertEqual(.noError,NSDecimalMultiplyByPowerOf10(&a,&a,-2,.plain)) - XCTAssertEqual(Decimal(12.34),a) - XCTAssertEqual(.underflow,NSDecimalMultiplyByPowerOf10(&a,&a,-128,.plain)) + XCTAssertEqual(.noError, NSDecimalMultiplyByPowerOf10(&a, &a, -2, .plain)) + XCTAssertEqual(Decimal(12.34), a) + XCTAssertEqual(.underflow, NSDecimalMultiplyByPowerOf10(&a, &a, -128, .plain)) XCTAssertTrue(a.isNaN) a = Decimal(1234) - XCTAssertEqual(.noError,NSDecimalPower(&a,&a,0,.plain)) - XCTAssertEqual(Decimal(1),a) + XCTAssertEqual(.noError, NSDecimalPower(&a, &a, 0, .plain)) + XCTAssertEqual(Decimal(1), a) a = Decimal(8) - XCTAssertEqual(.noError,NSDecimalPower(&a,&a,2,.plain)) - XCTAssertEqual(Decimal(64),a) + XCTAssertEqual(.noError, NSDecimalPower(&a, &a, 2, .plain)) + XCTAssertEqual(Decimal(64), a) a = Decimal(-2) - XCTAssertEqual(.noError,NSDecimalPower(&a,&a,3,.plain)) - XCTAssertEqual(Decimal(-8),a) + XCTAssertEqual(.noError, NSDecimalPower(&a, &a, 3, .plain)) + XCTAssertEqual(Decimal(-8), a) for i in -2...10 { for j in 0...5 { var actual = Decimal(i) - XCTAssertEqual(.noError,NSDecimalPower(&actual,&actual,j,.plain)) - let expected = Decimal(pow(Double(i),Double(j))) + XCTAssertEqual(.noError, NSDecimalPower(&actual, &actual, j, .plain)) + let expected = Decimal(pow(Double(i), Double(j))) XCTAssertEqual(expected, actual, "\(actual) == \(i)^\(j)") } } } + func test_MultiplicationOverflow() { + var multiplicand = Decimal(_exponent: 0, _length: 8, _isNegative: 0, _isCompact: 0, _reserved: 0, _mantissa: ( 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff )) + + var result = Decimal() + var multiplier = Decimal(1) + + multiplier._mantissa.0 = 2 + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &multiplicand, &multiplier, .plain), "2 * max mantissa") + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &multiplier, &multiplicand, .plain), "max mantissa * 2") + + multiplier._exponent = 0x7f + XCTAssertEqual(.overflow, NSDecimalMultiply(&result, &multiplicand, &multiplier, .plain), "2e127 * max mantissa") + XCTAssertEqual(.overflow, NSDecimalMultiply(&result, &multiplier, &multiplicand, .plain), "max mantissa * 2e127") + } + + func test_NaNInput() { + var NaN = Decimal.nan + var one = Decimal(1) + var result = Decimal() + + XCTAssertNotEqual(.noError, NSDecimalAdd(&result, &NaN, &one, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN + 1") + XCTAssertNotEqual(.noError, NSDecimalAdd(&result, &one, &NaN, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "1 + NaN") + + XCTAssertNotEqual(.noError, NSDecimalSubtract(&result, &NaN, &one, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN - 1") + XCTAssertNotEqual(.noError, NSDecimalSubtract(&result, &one, &NaN, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "1 - NaN") + + XCTAssertNotEqual(.noError, NSDecimalMultiply(&result, &NaN, &one, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN * 1") + XCTAssertNotEqual(.noError, NSDecimalMultiply(&result, &one, &NaN, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "1 * NaN") + + XCTAssertNotEqual(.noError, NSDecimalDivide(&result, &NaN, &one, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN / 1") + XCTAssertNotEqual(.noError, NSDecimalDivide(&result, &one, &NaN, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "1 / NaN") + + XCTAssertNotEqual(.noError, NSDecimalPower(&result, &NaN, 0, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN ^ 0") + XCTAssertNotEqual(.noError, NSDecimalPower(&result, &NaN, 4, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN ^ 4") + XCTAssertNotEqual(.noError, NSDecimalPower(&result, &NaN, 5, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN ^ 5") + + XCTAssertNotEqual(.noError, NSDecimalMultiplyByPowerOf10(&result, &NaN, 0, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e0") + XCTAssertNotEqual(.noError, NSDecimalMultiplyByPowerOf10(&result, &NaN, 4, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e4") + XCTAssertNotEqual(.noError, NSDecimalMultiplyByPowerOf10(&result, &NaN, 5, .plain)) + XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e5") + } + + func test_NegativeAndZeroMultiplication() { + var one = Decimal(1) + var zero = Decimal(0) + var negativeOne = Decimal(-1) + + var result = Decimal() + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &one, &one, .plain), "1 * 1") + XCTAssertEqual(.orderedSame, NSDecimalCompare(&one, &result), "1 * 1") + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &one, &negativeOne, .plain), "1 * -1") + XCTAssertEqual(.orderedSame, NSDecimalCompare(&negativeOne, &result), "1 * -1") + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &negativeOne, &one, .plain), "-1 * 1") + XCTAssertEqual(.orderedSame, NSDecimalCompare(&negativeOne, &result), "-1 * 1") + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &negativeOne, &negativeOne, .plain), "-1 * -1") + XCTAssertEqual(.orderedSame, NSDecimalCompare(&one, &result), "-1 * -1") + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &one, &zero, .plain), "1 * 0") + XCTAssertEqual(.orderedSame, NSDecimalCompare(&zero, &result), "1 * 0") + XCTAssertEqual(0, result._isNegative, "1 * 0") + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &zero, &one, .plain), "0 * 1") + XCTAssertEqual(.orderedSame, NSDecimalCompare(&zero, &result), "0 * 1") + XCTAssertEqual(0, result._isNegative, "0 * 1") + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &negativeOne, &zero, .plain), "-1 * 0") + XCTAssertEqual(.orderedSame, NSDecimalCompare(&zero, &result), "-1 * 0") + XCTAssertEqual(0, result._isNegative, "-1 * 0") + + XCTAssertEqual(.noError, NSDecimalMultiply(&result, &zero, &negativeOne, .plain), "0 * -1") + XCTAssertEqual(.orderedSame, NSDecimalCompare(&zero, &result), "0 * -1") + XCTAssertEqual(0, result._isNegative, "0 * -1") + } + + func test_Normalise() { + var one = Decimal(1) + var ten = Decimal(-10) + XCTAssertEqual(.noError, NSDecimalNormalize(&one, &ten, .plain)) + XCTAssertEqual(Decimal(1), one) + XCTAssertEqual(Decimal(-10), ten) + XCTAssertEqual(1, one._length) + XCTAssertEqual(1, ten._length) + one = Decimal(1) + ten = Decimal(10) + XCTAssertEqual(.noError, NSDecimalNormalize(&one, &ten, .plain)) + XCTAssertEqual(Decimal(1), one) + XCTAssertEqual(Decimal(10), ten) + XCTAssertEqual(1, one._length) + XCTAssertEqual(1, ten._length) + } + + func test_NSDecimal() { + var nan = Decimal.nan + XCTAssertTrue(NSDecimalIsNotANumber(&nan)) + var zero = Decimal() + XCTAssertFalse(NSDecimalIsNotANumber(&zero)) + var three = Decimal(3) + var guess = Decimal() + NSDecimalCopy(&guess, &three) + XCTAssertEqual(three, guess) + + var f = Decimal(_exponent: 0, _length: 2, _isNegative: 0, _isCompact: 0, _reserved: 0, _mantissa: (0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000)) + let before = f.description + XCTAssertEqual(0, f._isCompact) + NSDecimalCompact(&f) + XCTAssertEqual(1, f._isCompact) + let after = f.description + XCTAssertEqual(before, after) + } + + func test_PositivePowers() { + let six = NSDecimalNumber(integerLiteral: 6) + + XCTAssertEqual(6, six.raising(toPower:1).intValue) + XCTAssertEqual(36, six.raising(toPower:2).intValue) + XCTAssertEqual(216, six.raising(toPower:3).intValue) + XCTAssertEqual(1296, six.raising(toPower:4).intValue) + XCTAssertEqual(7776, six.raising(toPower:5).intValue) + XCTAssertEqual(46656, six.raising(toPower:6).intValue) + XCTAssertEqual(279936, six.raising(toPower:7).intValue) + XCTAssertEqual(1679616, six.raising(toPower:8).intValue) + XCTAssertEqual(10077696, six.raising(toPower:9).intValue) + + let negativeSix = NSDecimalNumber(integerLiteral: -6) + + XCTAssertEqual(-6, negativeSix.raising(toPower:1).intValue) + XCTAssertEqual(36, negativeSix.raising(toPower:2).intValue) + XCTAssertEqual(-216, negativeSix.raising(toPower:3).intValue) + XCTAssertEqual(1296, negativeSix.raising(toPower:4).intValue) + XCTAssertEqual(-7776, negativeSix.raising(toPower:5).intValue) + XCTAssertEqual(46656, negativeSix.raising(toPower:6).intValue) + XCTAssertEqual(-279936, negativeSix.raising(toPower:7).intValue) + XCTAssertEqual(1679616, negativeSix.raising(toPower:8).intValue) + XCTAssertEqual(-10077696, negativeSix.raising(toPower:9).intValue) + } + + func test_RepeatingDivision() { + let repeatingNumerator = Decimal(16) + let repeatingDenominator = Decimal(9) + let repeating = repeatingNumerator / repeatingDenominator + + let numerator = Decimal(1010) + var result = numerator / repeating + + var expected = Decimal() + expected._exponent = -35; + expected._length = 8; + expected._isNegative = 0; + expected._isCompact = 1; + expected._reserved = 0; + expected._mantissa.0 = 51946; + expected._mantissa.1 = 3; + expected._mantissa.2 = 15549; + expected._mantissa.3 = 55864; + expected._mantissa.4 = 57984; + expected._mantissa.5 = 55436; + expected._mantissa.6 = 45186; + expected._mantissa.7 = 10941; + + XCTAssertEqual(.orderedSame, NSDecimalCompare(&expected, &result), "568.12500000000000000000000000000248554: \(expected.description) != \(result.description)"); + } + func test_Round() { let testCases = [ // expected, start, scale, round @@ -290,48 +544,63 @@ class TestNSDecimal: XCTestCase { ( -5.5, -5.5, 1, Decimal.RoundingMode.up ), ( -6.5, -6.5, 1, Decimal.RoundingMode.plain ), ( -7.5, -7.5, 1, Decimal.RoundingMode.bankers ), - ] + ] for testCase in testCases { let (expected, start, scale, mode) = testCase var num = Decimal(start) - NSDecimalRound(&num,&num,scale,mode) + NSDecimalRound(&num, &num, scale, mode) XCTAssertEqual(Decimal(expected), num) } } - func test_Normalise() { - var one = Decimal(1) - var ten = Decimal(-10) - XCTAssertEqual(.noError,NSDecimalNormalize(&one,&ten,.plain)) - XCTAssertEqual(Decimal(1),one) - XCTAssertEqual(Decimal(-10),ten) - XCTAssertEqual(1,one._length) - XCTAssertEqual(1,ten._length) - one = Decimal(1) - ten = Decimal(10) - XCTAssertEqual(.noError,NSDecimalNormalize(&one,&ten,.plain)) - XCTAssertEqual(Decimal(1),one) - XCTAssertEqual(Decimal(10),ten) - XCTAssertEqual(1,one._length) - XCTAssertEqual(1,ten._length) + func test_SimpleMultiplication() { + var multiplicand = Decimal() + multiplicand._isNegative = 0 + multiplicand._isCompact = 0 + multiplicand._length = 1 + multiplicand._exponent = 1 + + var multiplier = multiplicand + multiplier._exponent = 2 + + var expected = multiplicand + expected._isNegative = 0 + expected._isCompact = 0 + expected._exponent = 3 + expected._length = 1 + + var result = Decimal() + + for i in 1.. Date: Fri, 14 Oct 2016 20:45:36 +0100 Subject: [PATCH 2/2] Implement NSDecimalNumber Using the functionality in NSDecimal, implement the NSDecimalNumber such that the additional tests in TestNSDecimal pass. --- Foundation/NSDecimal.swift | 4 +- Foundation/NSDecimalNumber.swift | 274 ++++++++++++++++++++++++----- Foundation/NSNumber.swift | 6 +- TestFoundation/TestNSDecimal.swift | 6 +- 4 files changed, 242 insertions(+), 48 deletions(-) diff --git a/Foundation/NSDecimal.swift b/Foundation/NSDecimal.swift index 8a0eb68eed..c6e0961fc6 100644 --- a/Foundation/NSDecimal.swift +++ b/Foundation/NSDecimal.swift @@ -1437,7 +1437,7 @@ extension Decimal: VariableLengthNumber { self[i] = 0 } } - fileprivate mutating func trimTrailingZeros() { + internal mutating func trimTrailingZeros() { if _length > Decimal.maxSize { _length = Decimal.maxSize } @@ -1697,7 +1697,7 @@ extension Decimal { _exponent = newExponent; self.compact(); } - fileprivate func compare(to other:Decimal) -> ComparisonResult { + internal func compare(to other:Decimal) -> ComparisonResult { // NaN is a special case and is arbitrary ordered before everything else // Conceptually comparing with NaN is bogus anyway but raising or // always returning the same answer will confuse the sorting algorithms diff --git a/Foundation/NSDecimalNumber.swift b/Foundation/NSDecimalNumber.swift index 759dc286a3..a8b1db4ac7 100644 --- a/Foundation/NSDecimalNumber.swift +++ b/Foundation/NSDecimalNumber.swift @@ -75,12 +75,35 @@ public protocol NSDecimalNumberBehaviors { // Receiver can raise, return a new value, or return nil to ignore the exception. +fileprivate func handle(_ error: NSDecimalNumber.CalculationError, _ handler: NSDecimalNumberBehaviors) { + // handle the error condition, such as throwing an error for over/underflow +} /*************** NSDecimalNumber: the class ***********/ open class NSDecimalNumber : NSNumber { - - public convenience init(mantissa: UInt64, exponent: Int16, isNegative flag: Bool) { NSUnimplemented() } - public init(decimal dcm: Decimal) { NSUnimplemented() } + + fileprivate let decimal: Decimal + public convenience init(mantissa: UInt64, exponent: Int16, isNegative: Bool) { + var d = Decimal() + d._exponent = Int32(exponent) + d._isNegative = isNegative ? 1 : 0 + var man = mantissa + d._mantissa.0 = UInt16(man & 0xffff) + man >>= 4 + d._mantissa.1 = UInt16(man & 0xffff) + man >>= 4 + d._mantissa.2 = UInt16(man & 0xffff) + man >>= 4 + d._mantissa.3 = UInt16(man & 0xffff) + d._length = 4 + d.trimTrailingZeros() + // TODO more parts of the mantissa... + self.init(decimal: d) + } + public init(decimal dcm: Decimal) { + self.decimal = dcm + super.init() + } public convenience init(string numberValue: String?) { NSUnimplemented() } public convenience init(string numberValue: String?, locale: AnyObject?) { NSUnimplemented() } @@ -89,15 +112,19 @@ open class NSDecimalNumber : NSNumber { } public required convenience init(floatLiteral value: Double) { - NSUnimplemented() + self.init(decimal:Decimal(value)) } public required convenience init(booleanLiteral value: Bool) { - NSUnimplemented() + if value { + self.init(integerLiteral: 1) + } else { + self.init(integerLiteral: 0) + } } public required convenience init(integerLiteral value: Int) { - NSUnimplemented() + self.init(decimal:Decimal(value)) } public required convenience init(bytes buffer: UnsafeRawPointer, objCType type: UnsafePointer) { @@ -105,42 +132,120 @@ open class NSDecimalNumber : NSNumber { } open override func description(withLocale locale: Locale?) -> String { NSUnimplemented() } + + open class var zero: NSDecimalNumber { + return NSDecimalNumber(integerLiteral: 0) + } + open class var one: NSDecimalNumber { + return NSDecimalNumber(integerLiteral: 1) + } + open class var minimum: NSDecimalNumber { + return NSDecimalNumber(decimal:Decimal.leastFiniteMagnitude) + } + open class var maximum: NSDecimalNumber { + return NSDecimalNumber(decimal:Decimal.greatestFiniteMagnitude) + + } + open class var notANumber: NSDecimalNumber { + return NSDecimalNumber(decimal: Decimal.nan) + } - // TODO: "declarations from extensions cannot be overridden yet" - // Although it's not clear we actually need to redeclare this here when the extension adds it to the superclass of this class - // open var decimalValue: Decimal { NSUnimplemented() } - - open class var zero: NSDecimalNumber { NSUnimplemented() } - open class var one: NSDecimalNumber { NSUnimplemented() } - open class var minimum: NSDecimalNumber { NSUnimplemented() } - open class var maximum: NSDecimalNumber { NSUnimplemented() } - open class var notANumber: NSDecimalNumber { NSUnimplemented() } - - open func adding(_ decimalNumber: NSDecimalNumber) -> NSDecimalNumber { NSUnimplemented() } - open func adding(_ decimalNumber: NSDecimalNumber, withBehavior behavior: NSDecimalNumberBehaviors?) -> NSDecimalNumber { NSUnimplemented() } - - open func subtracting(_ decimalNumber: NSDecimalNumber) -> NSDecimalNumber { NSUnimplemented() } - open func subtracting(_ decimalNumber: NSDecimalNumber, withBehavior behavior: NSDecimalNumberBehaviors?) -> NSDecimalNumber { NSUnimplemented() } - - open func multiplying(by decimalNumber: NSDecimalNumber) -> NSDecimalNumber { NSUnimplemented() } - open func multiplying(by decimalNumber: NSDecimalNumber, withBehavior behavior: NSDecimalNumberBehaviors?) -> NSDecimalNumber { NSUnimplemented() } - - open func dividing(by decimalNumber: NSDecimalNumber) -> NSDecimalNumber { NSUnimplemented() } - open func dividing(by decimalNumber: NSDecimalNumber, withBehavior behavior: NSDecimalNumberBehaviors?) -> NSDecimalNumber { NSUnimplemented() } + open func adding(_ other: NSDecimalNumber) -> NSDecimalNumber { + return adding(other, withBehavior: nil) + } + open func adding(_ other: NSDecimalNumber, withBehavior b: NSDecimalNumberBehaviors?) -> NSDecimalNumber { + var result = Decimal() + var left = self.decimal + var right = other.decimal + let behavior = b ?? NSDecimalNumber.defaultBehavior + let roundingMode = behavior.roundingMode() + let error = NSDecimalAdd(&result, &left, &right, roundingMode) + handle(error,behavior) + return NSDecimalNumber(decimal: result) + } + + open func subtracting(_ other: NSDecimalNumber) -> NSDecimalNumber { + return subtracting(other, withBehavior: nil) + } + open func subtracting(_ other: NSDecimalNumber, withBehavior b: NSDecimalNumberBehaviors?) -> NSDecimalNumber { + var result = Decimal() + var left = self.decimal + var right = other.decimal + let behavior = b ?? NSDecimalNumber.defaultBehavior + let roundingMode = behavior.roundingMode() + let error = NSDecimalSubtract(&result, &left, &right, roundingMode) + handle(error,behavior) + return NSDecimalNumber(decimal: result) + } + open func multiplying(by other: NSDecimalNumber) -> NSDecimalNumber { + return multiplying(by: other, withBehavior: nil) + } + open func multiplying(by other: NSDecimalNumber, withBehavior b: NSDecimalNumberBehaviors?) -> NSDecimalNumber { + var result = Decimal() + var left = self.decimal + var right = other.decimal + let behavior = b ?? NSDecimalNumber.defaultBehavior + let roundingMode = behavior.roundingMode() + let error = NSDecimalMultiply(&result, &left, &right, roundingMode) + handle(error,behavior) + return NSDecimalNumber(decimal: result) + } - open func raising(toPower power: Int) -> NSDecimalNumber { NSUnimplemented() } - open func raising(toPower power: Int, withBehavior behavior: NSDecimalNumberBehaviors?) -> NSDecimalNumber { NSUnimplemented() } + open func dividing(by other: NSDecimalNumber) -> NSDecimalNumber { + return dividing(by: other, withBehavior: nil) + } + open func dividing(by other: NSDecimalNumber, withBehavior b: NSDecimalNumberBehaviors?) -> NSDecimalNumber { + var result = Decimal() + var left = self.decimal + var right = other.decimal + let behavior = b ?? NSDecimalNumber.defaultBehavior + let roundingMode = behavior.roundingMode() + let error = NSDecimalDivide(&result, &left, &right, roundingMode) + handle(error,behavior) + return NSDecimalNumber(decimal: result) + } - open func multiplying(byPowerOf10 power: Int16) -> NSDecimalNumber { NSUnimplemented() } - open func multiplying(byPowerOf10 power: Int16, withBehavior behavior: NSDecimalNumberBehaviors?) -> NSDecimalNumber { NSUnimplemented() } + open func raising(toPower power: Int) -> NSDecimalNumber { + return raising(toPower:power, withBehavior: nil) + } + open func raising(toPower power: Int, withBehavior b: NSDecimalNumberBehaviors?) -> NSDecimalNumber { + var result = Decimal() + var input = self.decimal + let behavior = b ?? NSDecimalNumber.defaultBehavior + let roundingMode = behavior.roundingMode() + let error = NSDecimalPower(&result, &input, power, roundingMode) + handle(error,behavior) + return NSDecimalNumber(decimal: result) + } + + open func multiplying(byPowerOf10 power: Int16) -> NSDecimalNumber { + return multiplying(byPowerOf10: power, withBehavior: nil) + } + open func multiplying(byPowerOf10 power: Int16, withBehavior b: NSDecimalNumberBehaviors?) -> NSDecimalNumber { + var result = Decimal() + var input = self.decimal + let behavior = b ?? NSDecimalNumber.defaultBehavior + let roundingMode = behavior.roundingMode() + let error = NSDecimalPower(&result, &input, Int(power), roundingMode) + handle(error,behavior) + return NSDecimalNumber(decimal: result) + } open func rounding(accordingToBehavior behavior: NSDecimalNumberBehaviors?) -> NSDecimalNumber { NSUnimplemented() } // Round to the scale of the behavior. - open override func compare(_ decimalNumber: NSNumber) -> ComparisonResult { NSUnimplemented() } // compare two NSDecimalNumbers - - open class var defaultBehavior: NSDecimalNumberBehaviors { NSUnimplemented() } + open override func compare(_ decimalNumber: NSNumber) -> ComparisonResult { + if let num = decimalNumber as? NSDecimalNumber { + return decimal.compare(to:num.decimal) + } else { + return decimal.compare(to:Decimal(decimalNumber.doubleValue)) + } + } + + open class var defaultBehavior: NSDecimalNumberBehaviors { + return NSDecimalNumberHandler.defaultBehavior + } // One behavior per thread - The default behavior is // rounding mode: NSRoundPlain // scale: No defined scale (full precision) @@ -150,7 +255,54 @@ open class NSDecimalNumber : NSNumber { open override var objCType: UnsafePointer { NSUnimplemented() } // return 'd' for double - open override var doubleValue: Double { NSUnimplemented() } + open override var int8Value: Int8 { + return Int8(decimal.doubleValue) + } + open override var uint8Value: UInt8 { + return UInt8(decimal.doubleValue) + } + open override var int16Value: Int16 { + return Int16(decimal.doubleValue) + } + open override var uint16Value: UInt16 { + return UInt16(decimal.doubleValue) + } + open override var int32Value: Int32 { + return Int32(decimal.doubleValue) + } + open override var uint32Value: UInt32 { + return UInt32(decimal.doubleValue) + } + open override var int64Value: Int64 { + return Int64(decimal.doubleValue) + } + open override var uint64Value: UInt64 { + return UInt64(decimal.doubleValue) + } + open override var floatValue: Float { + return Float(decimal.doubleValue) + } + open override var doubleValue: Double { + return decimal.doubleValue + } + open override var boolValue: Bool { + return !decimal.isZero + } + open override var intValue: Int { + return Int(decimal.doubleValue) + } + open override var uintValue: UInt { + return UInt(decimal.doubleValue) + } + + open override func isEqual(_ value: Any?) -> Bool { + if let number = value as? NSDecimalNumber { + return self.decimal == number.decimal + } else { + return false + } + } + } // return an approximate double value @@ -158,7 +310,26 @@ open class NSDecimalNumber : NSNumber { /*********** A class for defining common behaviors *******/ open class NSDecimalNumberHandler : NSObject, NSDecimalNumberBehaviors, NSCoding { - + + static let defaultBehavior = NSDecimalNumberHandler() + + let _roundingMode: NSDecimalNumber.RoundingMode + let _scale:Int16 + + let _raiseOnExactness: Bool + let _raiseOnOverflow: Bool + let _raiseOnUnderflow: Bool + let _raiseOnDivideByZero: Bool + + public override init() { + _roundingMode = .plain + _scale = Int16(NSDecimalNoScale) + + _raiseOnExactness = false + _raiseOnOverflow = true + _raiseOnUnderflow = true + _raiseOnDivideByZero = true + } public required init?(coder aDecoder: NSCoder) { NSUnimplemented() } @@ -167,24 +338,43 @@ open class NSDecimalNumberHandler : NSObject, NSDecimalNumberBehaviors, NSCoding NSUnimplemented() } - open class func `default`() -> NSDecimalNumberHandler { NSUnimplemented() } + open class func `default`() -> NSDecimalNumberHandler { + return defaultBehavior + } // rounding mode: NSRoundPlain // scale: No defined scale (full precision) // ignore exactnessException (return nil) // raise on overflow, underflow and divide by zero. - public init(roundingMode: NSDecimalNumber.RoundingMode, scale: Int16, raiseOnExactness exact: Bool, raiseOnOverflow overflow: Bool, raiseOnUnderflow underflow: Bool, raiseOnDivideByZero divideByZero: Bool) { NSUnimplemented() } + public init(roundingMode: NSDecimalNumber.RoundingMode, scale: Int16, raiseOnExactness exact: Bool, raiseOnOverflow overflow: Bool, raiseOnUnderflow underflow: Bool, raiseOnDivideByZero divideByZero: Bool) { + _roundingMode = roundingMode + _scale = scale + _raiseOnExactness = exact + _raiseOnOverflow = overflow + _raiseOnUnderflow = underflow + _raiseOnDivideByZero = divideByZero + } - open func roundingMode() -> NSDecimalNumber.RoundingMode { NSUnimplemented() } + open func roundingMode() -> NSDecimalNumber.RoundingMode { + return _roundingMode + } - open func scale() -> Int16 { NSUnimplemented() } - // The scale could return NO_SCALE for no defined scale. + // The scale could return NoScale for no defined scale. + open func scale() -> Int16 { + return _scale + } } extension NSNumber { - public var decimalValue: Decimal { NSUnimplemented() } + public var decimalValue: Decimal { + if let d = self as? NSDecimalNumber { + return d.decimal + } else { + return Decimal(self.doubleValue) + } + } } // Could be silently inexact for float and double. diff --git a/Foundation/NSNumber.swift b/Foundation/NSNumber.swift index 724cc020ad..14bec84edf 100644 --- a/Foundation/NSNumber.swift +++ b/Foundation/NSNumber.swift @@ -290,7 +290,11 @@ open class NSNumber : NSValue { super.init() _CFNumberInitBool(_cfObject, value) } - + + override internal init() { + super.init() + } + public required convenience init(bytes buffer: UnsafeRawPointer, objCType: UnsafePointer) { guard let type = _NSSimpleObjCType(UInt8(objCType.pointee)) else { fatalError("NSNumber.init: unsupported type encoding spec '\(String(cString: objCType))'") diff --git a/TestFoundation/TestNSDecimal.swift b/TestFoundation/TestNSDecimal.swift index 9620903534..f28e16ccab 100644 --- a/TestFoundation/TestNSDecimal.swift +++ b/TestFoundation/TestNSDecimal.swift @@ -31,12 +31,12 @@ class TestNSDecimal: XCTestCase { ("test_NegativeAndZeroMultiplication", test_NegativeAndZeroMultiplication), ("test_Normalise", test_Normalise), ("test_NSDecimal", test_NSDecimal), - // ("test_PositivePowers", test_PositivePowers), // needs NSDecimalNumber implementaiton + ("test_PositivePowers", test_PositivePowers), ("test_RepeatingDivision", test_RepeatingDivision), ("test_Round", test_Round), ("test_SimpleMultiplication", test_SimpleMultiplication), - // ("test_SmallerNumbers", test_SmallerNumbers), // needs NSDecimalImplementation - // ("test_ZeroPower", test_ZeroPower), // needs NSDecimalImplementation + ("test_SmallerNumbers", test_SmallerNumbers), + ("test_ZeroPower", test_ZeroPower), ] }