Skip to content

Commit 89cfa62

Browse files
Merge pull request #235 from stephentyrone/complex-pow
Fix edge-cases for Complex.pow(.zero, <Int>), and Real.pow(zero, zero)
2 parents 2624562 + 2baff8f commit 89cfa62

File tree

7 files changed

+71
-19
lines changed

7 files changed

+71
-19
lines changed

Sources/ComplexModule/Complex+ElementaryFunctions.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,9 @@ extension Complex: ElementaryFunctions {
420420

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

Sources/RealModule/Double+Real.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@ extension Double: Real {
107107
libm_exp2(x)
108108
}
109109

110-
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
110+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
111111
@_transparent
112112
public static func exp10(_ x: Double) -> Double {
113113
libm_exp10(x)
114114
}
115-
#endif
115+
#endif
116116

117-
#if os(macOS) && arch(x86_64)
117+
#if os(macOS) && arch(x86_64)
118118
// Workaround for macOS bug (<rdar://problem/56844150>) where hypot can
119119
// overflow for values very close to the overflow boundary of the naive
120120
// algorithm. Since this is only for macOS, we can just unconditionally
@@ -125,12 +125,12 @@ extension Double: Real {
125125
let y80 = Float80(y)
126126
return Double(Float80.sqrt(x80*x80 + y80*y80))
127127
}
128-
#else
128+
#else
129129
@_transparent
130130
public static func hypot(_ x: Double, _ y: Double) -> Double {
131131
libm_hypot(x, y)
132132
}
133-
#endif
133+
#endif
134134

135135
@_transparent
136136
public static func gamma(_ x: Double) -> Double {
@@ -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

@@ -211,13 +212,13 @@ extension Double: Real {
211212
libm_atan2(y, x)
212213
}
213214

214-
#if !os(Windows)
215+
#if !os(Windows)
215216
@_transparent
216217
public static func logGamma(_ x: Double) -> Double {
217218
var dontCare: Int32 = 0
218219
return libm_lgamma(x, &dontCare)
219220
}
220-
#endif
221+
#endif
221222

222223
@_transparent
223224
public static func _mulAdd(_ a: Double, _ b: Double, _ c: Double) -> Double {

Sources/RealModule/ElementaryFunctions.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,24 @@ public protocol ElementaryFunctions: AdditiveArithmetic {
228228

229229
/// exp(y * log(x)) computed with additional internal precision.
230230
///
231-
/// See also `sqrt()` and `root()`.
231+
/// The edge-cases of this function are defined based on the behavior of the
232+
/// expression `exp(y log x)`, matching IEEE 754's `powr` operation.
233+
/// In particular, this means that if `x` and `y` are both zero, `pow(x,y)`
234+
/// is `nan` for real types and `infinity` for complex types, rather than 1.
235+
///
236+
/// There is also a `pow(_:Self,_:Int)` overload, whose behavior is defined
237+
/// in terms of repeated multiplication, and hence returns 1 for this case.
232238
///
239+
/// See also `sqrt()` and `root()`.
233240
static func pow(_ x: Self, _ y: Self) -> Self
234241

235242
/// `x` raised to the nth power.
236243
///
237-
/// See also `sqrt()` and `root()`.
244+
/// The edge-cases of this function are defined in terms of repeated
245+
/// multiplication or division, rather than exp(n log x). In particular,
246+
/// `Float.pow(0, 0)` is 1.
238247
///
248+
/// See also `sqrt()` and `root()`.
239249
static func pow(_ x: Self, _ n: Int) -> Self
240250

241251
/// The [square root][wiki] of `x`.
@@ -248,6 +258,5 @@ public protocol ElementaryFunctions: AdditiveArithmetic {
248258
/// The nth root of `x`.
249259
///
250260
/// See also `pow()` and `sqrt()`.
251-
///
252261
static func root(_ x: Self, _ n: Int) -> Self
253262
}

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/ComplexTests/ElementaryFunctionTests.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,18 @@ final class ElementaryFunctionTests: XCTestCase {
400400
}
401401
}
402402

403+
func testPowR<T: Real & FixedWidthFloatingPoint>(_ type: T.Type) {
404+
XCTAssertEqual(Complex<T>.pow(.zero, -.one), .infinity)
405+
XCTAssertEqual(Complex<T>.pow(.zero, .zero), .infinity)
406+
XCTAssertEqual(Complex<T>.pow(.zero, +.one), .infinity)
407+
}
408+
409+
func testPowN<T: Real & FixedWidthFloatingPoint>(_ type: T.Type) {
410+
XCTAssertEqual(Complex<T>.pow(.zero, -1), .infinity)
411+
XCTAssertEqual(Complex<T>.pow(.zero, 0), .one)
412+
XCTAssertEqual(Complex<T>.pow(.zero, +1), .zero)
413+
}
414+
403415
func testFloat() {
404416
testExp(Float.self)
405417
testExpMinusOne(Float.self)
@@ -411,6 +423,8 @@ final class ElementaryFunctionTests: XCTestCase {
411423
testAcosh(Float.self)
412424
testAsinh(Float.self)
413425
testAtanh(Float.self)
426+
testPowR(Float.self)
427+
testPowN(Float.self)
414428
}
415429

416430
func testDouble() {
@@ -424,9 +438,11 @@ final class ElementaryFunctionTests: XCTestCase {
424438
testAcosh(Double.self)
425439
testAsinh(Double.self)
426440
testAtanh(Double.self)
441+
testPowR(Double.self)
442+
testPowN(Double.self)
427443
}
428444

429-
#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android)
445+
#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android)
430446
func testFloat80() {
431447
testExp(Float80.self)
432448
testExpMinusOne(Float80.self)
@@ -438,6 +454,8 @@ final class ElementaryFunctionTests: XCTestCase {
438454
testAcosh(Float80.self)
439455
testAsinh(Float80.self)
440456
testAtanh(Float80.self)
457+
testPowR(Float80.self)
458+
testPowN(Float80.self)
441459
}
442-
#endif
460+
#endif
443461
}

Tests/RealTests/ElementaryFunctionChecks.swift

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,39 +120,59 @@ internal extension Real where Self: BinaryFloatingPoint {
120120
assertClose(0.4041169094348222983238250859191217675, Self.erf(0.375))
121121
assertClose(0.5958830905651777016761749140808782324, Self.erfc(0.375))
122122
assertClose(2.3704361844166009086464735041766525098, Self.gamma(0.375))
123-
#if !os(Windows)
123+
#if !os(Windows)
124124
assertClose( -0.11775527074107877445136203331798850, Self.logGamma(1.375))
125125
XCTAssertEqual(.plus, Self.signGamma(1.375))
126126
XCTAssertEqual(.minus, Self.signGamma(-2.375))
127-
#endif
127+
#endif
128+
}
129+
}
130+
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)
128144
}
129145
}
130146

131147
final class ElementaryFunctionChecks: XCTestCase {
132148

133-
#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
149+
#if !((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
}
140-
#endif
157+
#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

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

0 commit comments

Comments
 (0)