From 9eead0ceab74b233910089e48d506b34302f9eae Mon Sep 17 00:00:00 2001 From: Oschly Date: Sat, 22 Jul 2023 11:07:40 +0200 Subject: [PATCH 1/2] Fix `NSDecimalPower(_:_:_:_:)` behavior --- Sources/Foundation/Decimal.swift | 23 ++++++++++++++++++----- Tests/Foundation/Tests/TestDecimal.swift | 5 +++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Sources/Foundation/Decimal.swift b/Sources/Foundation/Decimal.swift index 37ca9ff541..615be82a8d 100644 --- a/Sources/Foundation/Decimal.swift +++ b/Sources/Foundation/Decimal.swift @@ -1801,7 +1801,7 @@ public func NSDecimalPower(_ result: UnsafeMutablePointer, _ number: Un return .overflow } NSDecimalCopy(result,number) - return result.pointee.power(UInt(power), roundingMode:roundingMode) + return result.pointee.power(power, roundingMode:roundingMode) } public func NSDecimalMultiplyByPowerOf10(_ result: UnsafeMutablePointer, _ number: UnsafePointer, _ power: Int16, _ roundingMode: NSDecimalNumber.RoundingMode) -> NSDecimalNumber.CalculationError { @@ -2242,11 +2242,11 @@ extension Decimal { _exponent = newExponent return .noError } - fileprivate mutating func power(_ p:UInt, roundingMode:RoundingMode) -> CalculationError { + fileprivate mutating func power(_ p:Int, roundingMode:RoundingMode) -> CalculationError { if isNaN { return .overflow } - var power = p + var power = abs(p) if power == 0 { _exponent = 0 _length = 1 @@ -2254,7 +2254,7 @@ extension Decimal { self[0] = 1 _isCompact = 1 return .noError - } else if power == 1 { + } else if power == 1 || isZero { return .noError } @@ -2297,7 +2297,20 @@ extension Decimal { let previousError = error var rightOp = self error = NSDecimalMultiply(&self, &temporary, &rightOp, roundingMode) - + + // if power is negative, use multiplicative inverse + if p < 0 { + var leftOp = Decimal(1) + var rightOp = self + + error = NSDecimalDivide( + &self, + &leftOp, + &rightOp, + roundingMode + ) + } + if previousError != .noError { // FIXME is this the intent? error = previousError } diff --git a/Tests/Foundation/Tests/TestDecimal.swift b/Tests/Foundation/Tests/TestDecimal.swift index eaf48a2f64..fd0cfd0434 100644 --- a/Tests/Foundation/Tests/TestDecimal.swift +++ b/Tests/Foundation/Tests/TestDecimal.swift @@ -398,9 +398,14 @@ class TestDecimal: XCTestCase { a = Decimal(8) XCTAssertEqual(.noError, NSDecimalPower(&result, &a, 2, .plain)) XCTAssertEqual(Decimal(64), result) + a = Decimal(8) + XCTAssertEqual(.noError, NSDecimalPower(&result, &a, -2, .plain)) + XCTAssertEqual(Decimal(1/64), result) a = Decimal(-2) XCTAssertEqual(.noError, NSDecimalPower(&result, &a, 3, .plain)) XCTAssertEqual(Decimal(-8), result) + XCTAssertEqual(.noError, NSDecimalPower(&result, &a, -3, .plain)) + XCTAssertEqual(Decimal(-1/8), result) for i in -2...10 { for j in 0...5 { var actual = Decimal(i) From 8da81c00e80c2ca98566e7fbc7a2e1aeaf547c40 Mon Sep 17 00:00:00 2001 From: Oschly Date: Sun, 30 Jul 2023 13:40:26 +0200 Subject: [PATCH 2/2] Improve tests, compare to p, not to the absPower - rename power to absPower - compare p to 0 and 1, instead of absPower (comparing absPower to 1 results in invalid results) - extract tests for negative power method to external test method - typecast test results to NSDecimalNumber in tests, since by default they were Int --- Sources/Foundation/Decimal.swift | 16 ++++++------ Tests/Foundation/Tests/TestDecimal.swift | 32 ++++++++++++++++++++---- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Sources/Foundation/Decimal.swift b/Sources/Foundation/Decimal.swift index 615be82a8d..9bfa452b55 100644 --- a/Sources/Foundation/Decimal.swift +++ b/Sources/Foundation/Decimal.swift @@ -2246,23 +2246,23 @@ extension Decimal { if isNaN { return .overflow } - var power = abs(p) - if power == 0 { + if p == 0 { _exponent = 0 _length = 1 _isNegative = 0 self[0] = 1 _isCompact = 1 return .noError - } else if power == 1 || isZero { + } else if p == 1 || isZero { return .noError } + var absPower = abs(p) var temporary = Decimal(1) var error:CalculationError = .noError - while power > 1 { - if power % 2 == 1 { + while absPower > 1 { + if absPower % 2 == 1 { let previousError = error var leftOp = temporary error = NSDecimalMultiply(&temporary, &leftOp, &self, roundingMode) @@ -2275,9 +2275,9 @@ extension Decimal { setNaN() return error } - power -= 1 + absPower -= 1 } - if power != 0 { + if absPower != 0 { let previousError = error var leftOp = self var rightOp = self @@ -2291,7 +2291,7 @@ extension Decimal { setNaN() return error } - power /= 2 + absPower /= 2 } } let previousError = error diff --git a/Tests/Foundation/Tests/TestDecimal.swift b/Tests/Foundation/Tests/TestDecimal.swift index fd0cfd0434..81b1207c67 100644 --- a/Tests/Foundation/Tests/TestDecimal.swift +++ b/Tests/Foundation/Tests/TestDecimal.swift @@ -398,14 +398,9 @@ class TestDecimal: XCTestCase { a = Decimal(8) XCTAssertEqual(.noError, NSDecimalPower(&result, &a, 2, .plain)) XCTAssertEqual(Decimal(64), result) - a = Decimal(8) - XCTAssertEqual(.noError, NSDecimalPower(&result, &a, -2, .plain)) - XCTAssertEqual(Decimal(1/64), result) a = Decimal(-2) XCTAssertEqual(.noError, NSDecimalPower(&result, &a, 3, .plain)) XCTAssertEqual(Decimal(-8), result) - XCTAssertEqual(.noError, NSDecimalPower(&result, &a, -3, .plain)) - XCTAssertEqual(Decimal(-1/8), result) for i in -2...10 { for j in 0...5 { var actual = Decimal(i) @@ -675,6 +670,32 @@ class TestDecimal: XCTestCase { XCTAssertEqual(1679616, negativeSix.raising(toPower:8).intValue) XCTAssertEqual(-10077696, negativeSix.raising(toPower:9).intValue) } + + func test_NegativePowers() { + let six = NSDecimalNumber(integerLiteral: 6) + + XCTAssertEqual(1/6 as NSDecimalNumber, six.raising(toPower: -1)) + XCTAssertEqual(1/36 as NSDecimalNumber, six.raising(toPower: -2)) + XCTAssertEqual(1/216 as NSDecimalNumber, six.raising(toPower: -3)) + XCTAssertEqual(1/1296 as NSDecimalNumber, six.raising(toPower: -4)) + XCTAssertEqual(1/7776 as NSDecimalNumber, six.raising(toPower: -5)) + XCTAssertEqual(1/46656 as NSDecimalNumber, six.raising(toPower: -6)) + XCTAssertEqual(1/279936 as NSDecimalNumber, six.raising(toPower: -7)) + XCTAssertEqual(1/1679616 as NSDecimalNumber, six.raising(toPower: -8)) + XCTAssertEqual(1/10077696 as NSDecimalNumber, six.raising(toPower: -9)) + + let negativeSix = NSDecimalNumber(integerLiteral: -6) + + XCTAssertEqual(-1/6 as NSDecimalNumber, negativeSix.raising(toPower: -1)) + XCTAssertEqual(1/36 as NSDecimalNumber, negativeSix.raising(toPower: -2)) + XCTAssertEqual(-1/216 as NSDecimalNumber, negativeSix.raising(toPower: -3)) + XCTAssertEqual(1/1296 as NSDecimalNumber, negativeSix.raising(toPower: -4)) + XCTAssertEqual(-1/7776 as NSDecimalNumber, negativeSix.raising(toPower: -5)) + XCTAssertEqual(1/46656 as NSDecimalNumber, negativeSix.raising(toPower: -6)) + XCTAssertEqual(-1/279936 as NSDecimalNumber, negativeSix.raising(toPower: -7)) + XCTAssertEqual(1/1679616 as NSDecimalNumber, negativeSix.raising(toPower: -8)) + XCTAssertEqual(-1/10077696 as NSDecimalNumber, negativeSix.raising(toPower: -9)) + } func test_RepeatingDivision() { let repeatingNumerator = Decimal(16) @@ -1551,6 +1572,7 @@ class TestDecimal: XCTestCase { ("test_Normalise", test_Normalise), ("test_NSDecimal", test_NSDecimal), ("test_PositivePowers", test_PositivePowers), + ("test_NegativePowers", test_NegativePowers), ("test_RepeatingDivision", test_RepeatingDivision), ("test_Round", test_Round), ("test_ScanDecimal", test_ScanDecimal),