Skip to content

Commit 2baff8f

Browse files
committed
Also fixup real edge cases for pow(0,0).
1 parent b9e8b97 commit 2baff8f

File tree

5 files changed

+48
-16
lines changed

5 files changed

+48
-16
lines changed

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/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)