Skip to content

Implement negative power support to pow(_ x: Decimal, _ y: Int). #895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ 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
}
#else
@_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
}
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 14 additions & 2 deletions Sources/FoundationEssentials/Decimal/Decimal+Math.swift
Original file line number Diff line number Diff line change
Expand Up @@ -367,15 +367,19 @@ extension Decimal {
}

internal func _power(
exponent: UInt, roundingMode: RoundingMode
exponent: Int, roundingMode: RoundingMode
) throws -> Decimal {
if self.isNaN {
throw _CalculationError.overflow
}
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 {
Expand All @@ -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
}

Expand Down
46 changes: 41 additions & 5 deletions Tests/FoundationEssentialsTests/DecimalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down Expand Up @@ -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))
}
}

Expand Down Expand Up @@ -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
)
}

}
}