Skip to content

Commit e0ec0f5

Browse files
committed
Cherry-pick pow edge case fixes from main.
1 parent aa76ee5 commit e0ec0f5

File tree

6 files changed

+43
-9
lines changed

6 files changed

+43
-9
lines changed

Sources/ComplexModule/ElementaryFunctions.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,14 +413,21 @@ extension Complex: ElementaryFunctions {
413413
}
414414

415415
// MARK: - pow-like functions
416+
/// `exp(w*log(z))`
417+
///
418+
/// Edge cases for this function are defined according to the defining
419+
/// expression exp(w log(z)), except that we define pow(0, w) to be 0
420+
/// instead of infinity when w is in the (strict) right half-plane, so that
421+
/// we agree with RealType.pow on the positive real line.
416422
@inlinable
417423
public static func pow(_ z: Complex, _ w: Complex) -> Complex {
424+
if z.isZero { return w.real > 0 ? zero : infinity }
418425
return exp(w * log(z))
419426
}
420427

421428
@inlinable
422429
public static func pow(_ z: Complex, _ n: Int) -> Complex {
423-
if z.isZero { return .zero }
430+
if z.isZero { return n < 0 ? infinity : n == 0 ? one : zero }
424431
// TODO: this implementation is not quite correct, because n may be
425432
// rounded in conversion to RealType. This only effects very extreme
426433
// cases, so we'll leave it alone for now.

Sources/RealModule/Double+Real.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ extension Double: Real {
150150
@_transparent
151151
public static func pow(_ x: Double, _ y: Double) -> Double {
152152
guard x >= 0 else { return .nan }
153+
if x == 0 && y == 0 { return .nan }
153154
return libm_pow(x, y)
154155
}
155156

Sources/RealModule/ElementaryFunctions.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -292,20 +292,24 @@ public protocol ElementaryFunctions: AdditiveArithmetic {
292292

293293
/// exp(y * log(x)) computed with additional internal precision.
294294
///
295-
/// See also:
296-
/// -
297-
/// - `sqrt()`
298-
/// - `root()`
295+
/// The edge-cases of this function are defined based on the behavior of the
296+
/// expression `exp(y log x)`, matching IEEE 754's `powr` operation.
297+
/// In particular, this means that if `x` and `y` are both zero, `pow(x,y)`
298+
/// is `nan` for real types and `infinity` for complex types, rather than 1.
299+
///
300+
/// There is also a `pow(_:Self,_:Int)` overload, whose behavior is defined
301+
/// in terms of repeated multiplication, and hence returns 1 for this case.
299302
///
303+
/// See also `sqrt()` and `root()`.
300304
static func pow(_ x: Self, _ y: Self) -> Self
301305

302306
/// `x` raised to the nth power.
303307
///
304-
/// See also:
305-
/// -
306-
/// - `sqrt()`
307-
/// - `root()`
308+
/// The edge-cases of this function are defined in terms of repeated
309+
/// multiplication or division, rather than exp(n log x). In particular,
310+
/// `Float.pow(0, 0)` is 1.
308311
///
312+
/// See also `sqrt()` and `root()`.
309313
static func pow(_ x: Self, _ n: Int) -> Self
310314

311315
/// The [square root][wiki] of `x`.

Sources/RealModule/Float+Real.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ extension Float: Real {
137137
@_transparent
138138
public static func pow(_ x: Float, _ y: Float) -> Float {
139139
guard x >= 0 else { return .nan }
140+
if x == 0 && y == 0 { return .nan }
140141
return libm_powf(x, y)
141142
}
142143

Sources/RealModule/Float80+Real.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ extension Float80: Real {
132132
@_transparent
133133
public static func pow(_ x: Float80, _ y: Float80) -> Float80 {
134134
guard x >= 0 else { return .nan }
135+
if x == 0 && y == 0 { return .nan }
135136
return libm_powl(x, y)
136137
}
137138

Tests/RealTests/ElementaryFunctionChecks.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,31 +128,51 @@ internal extension Real where Self: BinaryFloatingPoint {
128128
}
129129
}
130130

131+
extension Real {
132+
static func powZeroChecks() {
133+
// pow(_:Self,_:Self) is defined by exp(y log(x)) and has edge-cases to
134+
// match. In particular, if x is zero, log(x) is -infinity, so pow(0,0)
135+
// is exp(0 * -infinity) = exp(nan) = nan.
136+
XCTAssertEqual(pow(0, -1 as Self), infinity)
137+
XCTAssert(pow(0, 0 as Self).isNaN)
138+
XCTAssertEqual(pow(0, 1 as Self), zero)
139+
// pow(_:Self,_:Int) is defined by repeated multiplication or division,
140+
// and hence pow(0, 0) is 1.
141+
XCTAssertEqual(pow(0, -1), infinity)
142+
XCTAssertEqual(pow(0, 0), 1)
143+
XCTAssertEqual(pow(0, 1), zero)
144+
}
145+
}
146+
131147
final class ElementaryFunctionChecks: XCTestCase {
132148

133149
#if swift(>=5.4) && !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
134150
func testFloat16() {
135151
if #available(macOS 11.0, iOS 14.0, watchOS 14.0, tvOS 7.0, *) {
136152
Float16.elementaryFunctionChecks()
137153
Float16.realFunctionChecks()
154+
Float16.powZeroChecks()
138155
}
139156
}
140157
#endif
141158

142159
func testFloat() {
143160
Float.elementaryFunctionChecks()
144161
Float.realFunctionChecks()
162+
Float.powZeroChecks()
145163
}
146164

147165
func testDouble() {
148166
Double.elementaryFunctionChecks()
149167
Double.realFunctionChecks()
168+
Double.powZeroChecks()
150169
}
151170

152171
#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android)
153172
func testFloat80() {
154173
Float80.elementaryFunctionChecks()
155174
Float80.realFunctionChecks()
175+
Float80.powZeroChecks()
156176
}
157177
#endif
158178
}

0 commit comments

Comments
 (0)