Skip to content

Commit 7ff707f

Browse files
Merge pull request #83 from stephentyrone/pown
Initial pass at pow(Self, Int) for Float and Double
2 parents 55f33b7 + de53996 commit 7ff707f

File tree

2 files changed

+163
-33
lines changed

2 files changed

+163
-33
lines changed

Sources/Real/ScalarConformances.swift

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,21 @@ extension Float: Real {
5050
}
5151

5252
@_transparent public static func pow(_ x: Float, _ n: Int) -> Float {
53-
// TODO: this implementation is not quite correct, because n may be
54-
// rounded in conversion to Float. This only effects very extreme cases,
55-
// so we'll leave it alone for now; however, it gets the sign wrong if
56-
// it rounds an odd number to an even number, so we should fix it soon.
57-
return libm_powf(x, Float(n))
53+
// If n is exactly representable as Float, we can just call powf:
54+
if let y = Float(exactly: n) {
55+
return libm_powf(x, y)
56+
}
57+
// Otherwise, n is too large to losslessly represent as Float.
58+
// The range of "interesting" n is -1488522191 ... 1744361944; outside
59+
// of this range, all x != 1 overflow or underflow, so only the parity
60+
// of x matters. We don't really care about the specific range at all,
61+
// only that the bounds fit exactly into two Floats. Mask the low 24
62+
// bits of n, get pow with that exponent (this contains the parity),
63+
// then get pow with the rest (this may round, but if it does we've
64+
// saturated anyway, so it doesn't matter).
65+
let low = n & 0xffffff
66+
let high = n - low
67+
return libm_powf(x, Float(low)) * libm_powf(x, Float(high))
5868
}
5969

6070
@_transparent public static func root(_ x: Float, _ n: Int) -> Float {
@@ -129,11 +139,22 @@ extension Double: Real {
129139
}
130140

131141
@_transparent public static func pow(_ x: Double, _ n: Int) -> Double {
132-
// TODO: this implementation is not quite correct, because n may be
133-
// rounded in conversion to Double. This only effects very extreme cases,
134-
// so we'll leave it alone for now; however, it gets the sign wrong if
135-
// it rounds an odd number to an even number, so we should fix it soon.
136-
return libm_pow(x, Double(n))
142+
// If n is exactly representable as Double, we can just call pow:
143+
// Note that all calls on a 32b platform go down this path.
144+
if let y = Double(exactly: n) {
145+
return libm_pow(x, y)
146+
}
147+
// Otherwise, n is too large to losslessly represent as Double, so we
148+
// just split it into two parts, high and low. This is always exact,
149+
// so the only source of error is pow itself and the multiplication.
150+
//
151+
// mask constant is spelled in this funny way because if we just anded
152+
// with the hex value, we'd get a compile error on 32b platforms, even
153+
// though this whole branch is dead code on 32b.
154+
let mask = Int(truncatingIfNeeded: 0x1fffffffffffff as UInt64)
155+
let low = n & mask
156+
let high = n - low
157+
return libm_pow(x, Double(low)) * libm_pow(x, Double(high))
137158
}
138159

139160
@_transparent public static func root(_ x: Double, _ n: Int) -> Double {
@@ -191,10 +212,9 @@ extension Float80: Real {
191212
}
192213

193214
@_transparent public static func pow(_ x: Float80, _ n: Int) -> Float80 {
194-
// TODO: this implementation is not quite correct, because n may be
195-
// rounded in conversion to Float80. This only effects very extreme cases,
196-
// so we'll leave it alone for now; however, it gets the sign wrong if
197-
// it rounds an odd number to an even number, so we should fix it soon.
215+
// Every Int value is exactly representable as Float80, so we don't need
216+
// to do anything fancy--unlike Float and Double, we can just call the
217+
// libm pow function.
198218
return libm_powl(x, Float80(n))
199219
}
200220

Tests/RealTests/RealTests.swift

Lines changed: 129 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--- ElementaryFunctionTests.swift ------------------------*- swift -*-===//
1+
//===--- ElementaryFunctionChecks.swift ------------------------*- swift -*-===//
22
//
33
// This source file is part of the Swift Numerics open source project
44
//
@@ -22,28 +22,42 @@ func sanityCheck<T>(_ expected: TestLiteralType, _ actual: T,
2222
ulps allowed: T = 16,
2323
file: StaticString = #file, line: UInt = #line)
2424
where T: BinaryFloatingPoint {
25+
// Shortcut relative-error check if we got the sign wrong; it's OK to
26+
// underflow to zero a little bit early, but we don't want to allow going
27+
// right through zero to the other side.
28+
XCTAssert(actual.sign == expected.sign, "\(actual) != \(expected) as \(T.self)", file: file, line: line)
2529
// Default tolerance is 16 ulps; It's OK to relax this as needed for new
26-
// platforms, as these tests are *not* intended to validate the math
30+
// platforms, as these checks are *not* intended to validate the math
2731
// library--they are only intended to check that the Swift bindings are
2832
// calling the right functions in the math library. It's important, however
29-
// not to relax the tolerance beyond a few hundred ulps, because these tests
33+
// not to relax the tolerance beyond a few hundred ulps, because these checks
3034
// need to detect errors where the *wrong function* is being called; e.g.
3135
// we need to flag an implentation that inadvertently called the C hypotf
3236
// function instead of hypot. This is especially important because the C
3337
// shims that we're calling through will allow silent type conversions.
3438
if actual == T(expected) || actual.isNaN && expected.isNaN {
3539
return
3640
}
37-
// Compute error in ulp, compare to tolerance.
38-
let absoluteError = T(abs(TestLiteralType(actual) - expected))
39-
let ulpError = absoluteError / T(expected).ulp
40-
XCTAssert(ulpError <= allowed, "\(actual) != \(expected) as \(T.self)" +
41-
"\n \(ulpError)-ulp error exceeds \(allowed)-ulp tolerance.",
42-
file: file, line: line)
41+
// Special-case where expected or observed is infinity.
42+
// Artificially knock everything down a binade, treat actual infinity as
43+
// the base of the next binade up.
44+
if actual.isInfinite || T(expected).isInfinite {
45+
let scaledExpected = TestLiteralType(signOf: expected,
46+
magnitudeOf: expected.isInfinite ? TestLiteralType.greatestFiniteMagnitude.binade : 0.5 * expected
47+
)
48+
let scaledActual = T(signOf: actual,
49+
magnitudeOf: actual.isInfinite ? T.greatestFiniteMagnitude.binade : 0.5 * actual
50+
)
51+
return sanityCheck(scaledExpected, scaledActual, ulps: allowed, file: file, line: line)
52+
}
53+
// Compute error in ulp, compare to tolerance.
54+
let absoluteError = T(abs(TestLiteralType(actual) - expected)).magnitude
55+
let ulpError = absoluteError / max(T(expected).magnitude, T.leastNormalMagnitude).ulp
56+
XCTAssert(ulpError <= allowed, "\(actual) != \(expected) as \(T.self)\n\(ulpError)-ulp error exceeds \(allowed)-ulp tolerance.", file: file, line: line)
4357
}
4458

4559
internal extension ElementaryFunctions where Self: BinaryFloatingPoint {
46-
static func elementaryFunctionTests() {
60+
static func elementaryFunctionChecks() {
4761
sanityCheck(1.1863995522992575361931268186727044683, Self.acos(0.375))
4862
sanityCheck(0.3843967744956390830381948729670469737, Self.asin(0.375))
4963
sanityCheck(0.3587706702705722203959200639264604997, Self.atan(0.375))
@@ -69,7 +83,7 @@ internal extension ElementaryFunctions where Self: BinaryFloatingPoint {
6983
}
7084

7185
internal extension Real where Self: BinaryFloatingPoint {
72-
static func realFunctionTests() {
86+
static func realFunctionChecks() {
7387
sanityCheck(1.2968395546510096659337541177924511598, Self.exp2(0.375))
7488
sanityCheck(2.3713737056616552616517527574788898386, Self.exp10(0.375))
7589
sanityCheck(-1.415037499278843818546261056052183491, Self.log2(0.375))
@@ -85,25 +99,121 @@ internal extension Real where Self: BinaryFloatingPoint {
8599
XCTAssertEqual(.minus, Self.signGamma(-2.375))
86100
#endif
87101
}
102+
103+
static func testPownCommon() {
104+
// If x is -1, then the result is ±1 with sign chosen by parity of n.
105+
// Simply converting n to Real will flip parity when n is large, so
106+
// first check that we get those cases right.
107+
XCTAssertEqual(Self.pow(-1, 0), 1)
108+
XCTAssertEqual(Self.pow(-1, 1), -1)
109+
XCTAssertEqual(Self.pow(-1, -1), -1)
110+
XCTAssertEqual(Self.pow(-1, 2), 1)
111+
XCTAssertEqual(Self.pow(-1, -2), 1)
112+
XCTAssertEqual(Self.pow(-1, Int.max - 1), 1)
113+
XCTAssertEqual(Self.pow(-1, -Int.max + 1), 1)
114+
XCTAssertEqual(Self.pow(-1, Int.max), -1)
115+
XCTAssertEqual(Self.pow(-1, -Int.max), -1)
116+
XCTAssertEqual(Self.pow(-1, Int.min), 1)
117+
}
118+
}
119+
120+
extension Float {
121+
static func testPown() {
122+
testPownCommon()
123+
let u = Float(1).nextUp
124+
let d = Float(1).nextDown
125+
// Smallest exponents not exactly representable as Float.
126+
sanityCheck(-7.3890560989306677280287919329569359, Float.pow(-u, 0x1000001))
127+
sanityCheck(-0.3678794082804575860056608283059288, Float.pow(-d, 0x1000001))
128+
// Exponents close to overflow boundary.
129+
sanityCheck(-3.4028231352500001570898203463449749e38, Float.pow(-u, 744261161))
130+
sanityCheck( 3.4028235408981285772043562848249166e38, Float.pow(-u, 744261162))
131+
sanityCheck(-3.4028239465463053543440887892352174e38, Float.pow(-u, 744261163))
132+
sanityCheck( 3.4028233551634475284795244782720072e38, Float.pow(-d, -1488522190))
133+
sanityCheck(-3.4028235579875369356575053576685267e38, Float.pow(-d, -1488522191))
134+
sanityCheck( 3.4028237608116384320940078199368685e38, Float.pow(-d, -1488522192))
135+
// Exponents close to underflow boundary.
136+
sanityCheck( 7.0064936491761438872280296737844625e-46, Float.pow(-u, -872181048))
137+
sanityCheck(-7.0064928139371132951305928725186420e-46, Float.pow(-u, -872181049))
138+
sanityCheck( 7.0064919786981822712727285793333389e-46, Float.pow(-u, -872181050))
139+
sanityCheck(-7.0064924138100205091278464932003585e-46, Float.pow(-d, 1744361943))
140+
sanityCheck( 7.0064919961905290625123586120258840e-46, Float.pow(-d, 1744361944))
141+
sanityCheck(-7.0064915785710625079583096856510544e-46, Float.pow(-d, 1744361945))
142+
// Just hammer max/min exponents, these always saturate, but this will reveal
143+
// errors in some implementations that one could try.
144+
sanityCheck( .infinity, Self.pow(-u, Int.max - 1))
145+
sanityCheck( 0.0, Self.pow(-d, Int.max - 1))
146+
sanityCheck( 0.0, Self.pow(-u, -Int.max + 1))
147+
sanityCheck( .infinity, Self.pow(-d, -Int.max + 1))
148+
sanityCheck(-.infinity, Self.pow(-u, Int.max))
149+
sanityCheck(-0.0, Self.pow(-d, Int.max))
150+
sanityCheck(-0.0, Self.pow(-u, -Int.max))
151+
sanityCheck(-.infinity, Self.pow(-d, -Int.max))
152+
sanityCheck( 0.0, Self.pow(-u, Int.min))
153+
sanityCheck( .infinity, Self.pow(-d, Int.min))
154+
}
88155
}
89156

90-
final class ElementaryFunctionTests: XCTestCase {
157+
extension Double {
158+
static func testPown() {
159+
testPownCommon()
160+
// Following tests only make sense (and are only necessary) on 64b platforms.
161+
#if arch(arm64) || arch(x86_64)
162+
let u: Double = 1.nextUp
163+
let d: Double = 1.nextDown
164+
// Smallest exponent not exactly representable as Double.
165+
sanityCheck(-7.3890560989306502272304274605750685, Double.pow(-u, 0x20000000000001))
166+
sanityCheck(-0.1353352832366126918939994949724833, Double.pow(-u, -0x20000000000001))
167+
sanityCheck(-0.3678794411714422603312898889458068, Double.pow(-d, 0x20000000000001))
168+
sanityCheck(-2.7182818284590456880451484776630468, Double.pow(-d, -0x20000000000001))
169+
// Exponents close to overflow boundary.
170+
sanityCheck( 1.7976931348623151738531864721534215e308, Double.pow(-u, 3196577161300664268))
171+
sanityCheck(-1.7976931348623155730212483790972209e308, Double.pow(-u, 3196577161300664269))
172+
sanityCheck( 1.7976931348623159721893102860411089e308, Double.pow(-u, 3196577161300664270))
173+
sanityCheck( 1.7976931348623157075547244136070910e308, Double.pow(-d, -6393154322601327474))
174+
sanityCheck(-1.7976931348623159071387553670790721e308, Double.pow(-d, -6393154322601327475))
175+
sanityCheck( 1.7976931348623161067227863205510754e308, Double.pow(-d, -6393154322601327476))
176+
// Exponents close to underflow boundary.
177+
sanityCheck( 2.4703282292062334560337346683707907e-324, Double.pow(-u, -3355781687888880946))
178+
sanityCheck(-2.4703282292062329075106789791206172e-324, Double.pow(-u, -3355781687888880947))
179+
sanityCheck( 2.4703282292062323589876232898705654e-324, Double.pow(-u, -3355781687888880948))
180+
sanityCheck(-2.4703282292062332640976590913373022e-324, Double.pow(-d, 6711563375777760775))
181+
sanityCheck( 2.4703282292062329898361312467121758e-324, Double.pow(-d, 6711563375777760776))
182+
sanityCheck(-2.4703282292062327155746034020870799e-324, Double.pow(-d, 6711563375777760777))
183+
// Just hammer max/min exponents, these always saturate, but this will reveal
184+
// errors in some implementations that one could try.
185+
sanityCheck( .infinity, Self.pow(-u, Int.max - 1))
186+
sanityCheck( 0.0, Self.pow(-d, Int.max - 1))
187+
sanityCheck( 0.0, Self.pow(-u, -Int.max + 1))
188+
sanityCheck( .infinity, Self.pow(-d, -Int.max + 1))
189+
sanityCheck(-.infinity, Self.pow(-u, Int.max))
190+
sanityCheck(-0.0, Self.pow(-d, Int.max))
191+
sanityCheck(-0.0, Self.pow(-u, -Int.max))
192+
sanityCheck(-.infinity, Self.pow(-d, -Int.max))
193+
sanityCheck( 0.0, Self.pow(-u, Int.min))
194+
sanityCheck( .infinity, Self.pow(-d, Int.min))
195+
#endif
196+
}
197+
}
198+
199+
final class ElementaryFunctionChecks: XCTestCase {
91200

92201
func testFloat() {
93-
Float.elementaryFunctionTests()
94-
Float.realFunctionTests()
202+
Float.elementaryFunctionChecks()
203+
Float.realFunctionChecks()
204+
Float.testPown()
95205
}
96206

97207
func testDouble() {
98-
Double.elementaryFunctionTests()
99-
Double.realFunctionTests()
208+
Double.elementaryFunctionChecks()
209+
Double.realFunctionChecks()
210+
Double.testPown()
100211
}
101212

102-
103213
#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android)
104214
func testFloat80() {
105-
Float80.elementaryFunctionTests()
106-
Float80.realFunctionTests()
215+
Float80.elementaryFunctionChecks()
216+
Float80.realFunctionChecks()
107217
}
108218
#endif
109219
}

0 commit comments

Comments
 (0)