diff --git a/Sources/FoundationEssentials/Decimal/Decimal+Compatibility.swift b/Sources/FoundationEssentials/Decimal/Decimal+Compatibility.swift index 41e9b20e8..26c0406e8 100644 --- a/Sources/FoundationEssentials/Decimal/Decimal+Compatibility.swift +++ b/Sources/FoundationEssentials/Decimal/Decimal+Compatibility.swift @@ -84,7 +84,7 @@ extension Decimal : _ObjectiveCBridgeable { @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 + exponent: y, roundingMode: .plain ) return result ?? .nan } @@ -92,7 +92,7 @@ public func pow(_ x: Decimal, _ y: Int) -> Decimal { @_spi(SwiftCorelibsFoundation) public func _pow(_ x: Decimal, _ y: Int) -> Decimal { let result = try? x._power( - exponent: UInt(y), roundingMode: .plain + exponent: y, roundingMode: .plain ) return result ?? .nan } @@ -233,7 +233,9 @@ private func __NSDecimalPower( _ roundingMode: Decimal.RoundingMode ) -> Decimal.CalculationError { do { - let power = try decimal.pointee._power(exponent: UInt(exponent), roundingMode: roundingMode) + let power = try decimal.pointee._power( + exponent: exponent, roundingMode: roundingMode + ) result.pointee = power return .noError } catch { diff --git a/Sources/FoundationEssentials/Decimal/Decimal+Math.swift b/Sources/FoundationEssentials/Decimal/Decimal+Math.swift index cb680c46e..dffeea2a2 100644 --- a/Sources/FoundationEssentials/Decimal/Decimal+Math.swift +++ b/Sources/FoundationEssentials/Decimal/Decimal+Math.swift @@ -367,7 +367,7 @@ extension Decimal { } internal func _power( - exponent: UInt, roundingMode: RoundingMode + exponent: Int, roundingMode: RoundingMode ) throws -> Decimal { if self.isNaN { throw _CalculationError.overflow @@ -375,7 +375,11 @@ extension Decimal { if exponent == 0 { return Decimal(1) } - var power = exponent + if self == .zero { + // Technically 0^-n is undefined, return NaN + return exponent > 0 ? Decimal(0) : .nan + } + var power = abs(exponent) var result = self var temporary = Decimal(1) while power > 1 { @@ -395,6 +399,14 @@ extension Decimal { result = try temporary._multiply( by: result, roundingMode: roundingMode ) + // Negative Exponent Rule + // x^-n = 1/(x^n) + if exponent < 0 { + result = try Decimal(1)._divide( + by: result, + roundingMode: roundingMode + ) + } return result } diff --git a/Tests/FoundationEssentialsTests/DecimalTests.swift b/Tests/FoundationEssentialsTests/DecimalTests.swift index 9d1e22e2a..5d205138d 100644 --- a/Tests/FoundationEssentialsTests/DecimalTests.swift +++ b/Tests/FoundationEssentialsTests/DecimalTests.swift @@ -657,20 +657,20 @@ final class DecimalTests : XCTestCase { // Positive base let six = Decimal(6) for exponent in 1 ..< 10 { - result = try six._power(exponent: UInt(exponent), roundingMode: .plain) + result = try six._power(exponent: exponent, roundingMode: .plain) XCTAssertEqual(result.doubleValue, pow(6.0, Double(exponent))) } // Negative base let negativeSix = Decimal(-6) for exponent in 1 ..< 10 { - result = try negativeSix._power(exponent: UInt(exponent), roundingMode: .plain) + result = try negativeSix._power(exponent: exponent, roundingMode: .plain) XCTAssertEqual(result.doubleValue, pow(-6.0, Double(exponent))) } for i in -2 ... 10 { for j in 0 ... 5 { let actual = Decimal(i) let result = try actual._power( - exponent: UInt(j), roundingMode: .plain + exponent: j, roundingMode: .plain ) let expected = Decimal(pow(Double(i), Double(j))) XCTAssertEqual(expected, result, "\(result) == \(i)^\(j)") @@ -1008,10 +1008,10 @@ final class DecimalTests : XCTestCase { for i in -2...10 { for j in 0...5 { let power = Decimal(i) - let actual = try power._power(exponent: UInt(j), roundingMode: .plain) + let actual = try power._power(exponent: j, roundingMode: .plain) let expected = Decimal(pow(Double(i), Double(j))) XCTAssertEqual(expected, actual, "\(actual) == \(i)^\(j)") - XCTAssertEqual(expected, try power._power(exponent: UInt(j), roundingMode: .plain)) + XCTAssertEqual(expected, try power._power(exponent: j, roundingMode: .plain)) } } @@ -1301,4 +1301,40 @@ final class DecimalTests : XCTestCase { XCTAssertEqual(length, 3) } #endif + + func testNegativePower() { + func test(withBase base: Decimal, power: Int) { + XCTAssertEqual( + try base._power(exponent: -power, roundingMode: .plain), + try Decimal(1)/base._power(exponent: power, roundingMode: .plain), + "Base: \(base), Power: \(power)" + ) + } + // Negative Exponent Rule + // x^-n = 1/(x^n) + for power in 2 ..< 10 { + // Positive Integer base + test(withBase: Decimal(Int.random(in: 1 ..< 10)), power: power) + + // Negative Integer base + test(withBase: Decimal(Int.random(in: -10 ..< -1)), power: power) + + // Postive Double base + test(withBase: Decimal(Double.random(in: 0 ..< 1.0)), power: power) + + // Negative Double base + test(withBase: Decimal(Double.random(in: -1.0 ..< 0.0)), power: power) + + // For zero base: 0^n = 0; 0^(-n) = nan + XCTAssertEqual( + try Decimal(0)._power(exponent: power, roundingMode: .plain), + Decimal(0) + ) + XCTAssertEqual( + try Decimal(0)._power(exponent: -power, roundingMode: .plain), + Decimal.nan + ) + } + + } }