|
| 1 | +//===--- AugmentedArithmeticTests.swift -----------------------*- swift -*-===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2021 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | + |
| 13 | +import RealModule |
| 14 | +import XCTest |
| 15 | +import _TestSupport |
| 16 | + |
| 17 | +final class AugmentedArithmeticTests: XCTestCase { |
| 18 | + |
| 19 | + func testTwoSumSpecials<T: Real & FixedWidthFloatingPoint>(_: T.Type) { |
| 20 | + // Must be exact and not overflow on outer bounds |
| 21 | + var x = T.greatestFiniteMagnitude |
| 22 | + var y = -T.greatestFiniteMagnitude |
| 23 | + XCTAssertEqual(Augmented.sum(x, y).head, .zero) |
| 24 | + XCTAssertEqual(Augmented.sum(x, y).tail, .zero) |
| 25 | + XCTAssert(Augmented.sum(x, y).head.isFinite) |
| 26 | + XCTAssert(Augmented.sum(x, y).tail.isFinite) |
| 27 | + // Must be exact on lower subnormal bounds |
| 28 | + x = T.leastNonzeroMagnitude |
| 29 | + y = -T.leastNonzeroMagnitude |
| 30 | + XCTAssertEqual(Augmented.sum(x, y).head, .zero) |
| 31 | + XCTAssertEqual(Augmented.sum(x, y).tail, .zero) |
| 32 | + // Must preserve floating point signs for: |
| 33 | + // (1) (+0) + (-0) == +0 |
| 34 | + // (2) (-0) + (+0) == +0 |
| 35 | + // (3) (-0) + (-0) == -0 |
| 36 | + x = T(sign: .plus, exponent: 1, significand: 0) |
| 37 | + y = T(sign: .minus, exponent: 1, significand: 0) |
| 38 | + XCTAssertEqual(Augmented.sum(x, y).head.sign, .plus) // (1) |
| 39 | + XCTAssertEqual(Augmented.sum(x, y).tail.sign, .plus) |
| 40 | + x = T(sign: .minus, exponent: 1, significand: 0) |
| 41 | + y = T(sign: .plus, exponent: 1, significand: 0) |
| 42 | + XCTAssertEqual(Augmented.sum(x, y).head.sign, .plus) // (2) |
| 43 | + XCTAssertEqual(Augmented.sum(x, y).tail.sign, .plus) |
| 44 | + x = T(sign: .minus, exponent: 1, significand: 0) |
| 45 | + y = T(sign: .minus, exponent: 1, significand: 0) |
| 46 | + XCTAssertEqual(Augmented.sum(x, y).head.sign, .minus) // (3) |
| 47 | + XCTAssertEqual(Augmented.sum(x, y).tail.sign, .plus) |
| 48 | + // Infinity and NaN are propagated correctly |
| 49 | + XCTAssertEqual(Augmented.sum( 0, T.infinity).head, T.infinity) |
| 50 | + XCTAssertEqual(Augmented.sum( T.infinity, 0 ).head, T.infinity) |
| 51 | + XCTAssertEqual(Augmented.sum( T.infinity, T.infinity).head, T.infinity) |
| 52 | + XCTAssertEqual(Augmented.sum( 0, -T.infinity).head, -T.infinity) |
| 53 | + XCTAssertEqual(Augmented.sum(-T.infinity, 0 ).head, -T.infinity) |
| 54 | + XCTAssertEqual(Augmented.sum(-T.infinity, -T.infinity).head, -T.infinity) |
| 55 | + XCTAssert(Augmented.sum( T.infinity, -T.infinity).head.isNaN) |
| 56 | + XCTAssert(Augmented.sum(-T.infinity, T.infinity).head.isNaN) |
| 57 | + XCTAssert(Augmented.sum( T.infinity, T.nan).head.isNaN) |
| 58 | + XCTAssert(Augmented.sum( T.nan, T.infinity).head.isNaN) |
| 59 | + XCTAssert(Augmented.sum(-T.infinity, T.nan).head.isNaN) |
| 60 | + XCTAssert(Augmented.sum( T.nan, -T.infinity).head.isNaN) |
| 61 | + XCTAssert(Augmented.sum( 0, T.nan).head.isNaN) |
| 62 | + XCTAssert(Augmented.sum(T.nan, 0).head.isNaN) |
| 63 | + XCTAssert(Augmented.sum(T.nan, T.nan).head.isNaN) |
| 64 | + } |
| 65 | + |
| 66 | + func testTwoSumRandomValues<T: Real & FixedWidthFloatingPoint>(_: T.Type) { |
| 67 | + // For randomly-chosen well-scaled finite values, we expect: |
| 68 | + // (1) `head` to be exactly the IEEE 754 sum of `a + b` |
| 69 | + // (2) `tail` to be less than or equal `head.ulp/2` |
| 70 | + // (3) the result of `twoSum` for unordered input to be exactly equal to |
| 71 | + // the result of `fastTwoSum` for ordered input. |
| 72 | + var g = SystemRandomNumberGenerator() |
| 73 | + let values: [T] = (0 ..< 100).map { _ in |
| 74 | + T.random( |
| 75 | + in: T.ulpOfOne ..< 1, |
| 76 | + using: &g) |
| 77 | + } |
| 78 | + for a in values { |
| 79 | + for b in values { |
| 80 | + let twoSum = Augmented.sum(a, b) |
| 81 | + XCTAssertEqual(twoSum.head, a + b) // (1) |
| 82 | + XCTAssert(twoSum.tail.magnitude <= twoSum.head.ulp/2) // (2) |
| 83 | + let x: T = a.magnitude < b.magnitude ? b : a |
| 84 | + let y: T = a.magnitude < b.magnitude ? a : b |
| 85 | + let fastTwoSum = Augmented.sum(large: x, small: y) |
| 86 | + XCTAssertEqual(twoSum.head, fastTwoSum.head) // (3) |
| 87 | + XCTAssertEqual(twoSum.tail, fastTwoSum.tail) // (3) |
| 88 | + } |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + func testTwoSumCancellation<T: Real & FixedWidthFloatingPoint>(_: T.Type) { |
| 93 | + // Must be exact for exactly representable values |
| 94 | + XCTAssertEqual(Augmented.sum( 0.984375, 1.375).head, 2.359375) |
| 95 | + XCTAssertEqual(Augmented.sum( 0.984375, 1.375).tail, 0.0) |
| 96 | + XCTAssertEqual(Augmented.sum(-0.984375, 1.375).head, 0.390625) |
| 97 | + XCTAssertEqual(Augmented.sum(-0.984375, 1.375).tail, 0.0) |
| 98 | + XCTAssertEqual(Augmented.sum( 0.984375,-1.375).head,-0.390625) |
| 99 | + XCTAssertEqual(Augmented.sum( 0.984375,-1.375).tail, 0.0) |
| 100 | + XCTAssertEqual(Augmented.sum(-0.984375,-1.375).head,-2.359375) |
| 101 | + XCTAssertEqual(Augmented.sum(-0.984375,-1.375).tail, 0.0) |
| 102 | + XCTAssertEqual(Augmented.sum( 1.375, 0.984375).head, 2.359375) |
| 103 | + XCTAssertEqual(Augmented.sum( 1.375, 0.984375).tail, 0.0) |
| 104 | + XCTAssertEqual(Augmented.sum( 1.375,-0.984375).head, 0.390625) |
| 105 | + XCTAssertEqual(Augmented.sum( 1.375,-0.984375).tail, 0.0) |
| 106 | + XCTAssertEqual(Augmented.sum(-1.375, 0.984375).head,-0.390625) |
| 107 | + XCTAssertEqual(Augmented.sum(-1.375, 0.984375).tail, 0.0) |
| 108 | + XCTAssertEqual(Augmented.sum(-1.375,-0.984375).head,-2.359375) |
| 109 | + XCTAssertEqual(Augmented.sum(-1.375,-0.984375).tail, 0.0) |
| 110 | + // Must handle cancellation when `b` is not representable in `a` and |
| 111 | + // we expect `b` to be lost entirely in the calculation of `a + b`. |
| 112 | + var a: T = 1.0 |
| 113 | + var b: T = .ulpOfOne * .ulpOfOne |
| 114 | + var twoSum = Augmented.sum(a, b) |
| 115 | + XCTAssertEqual(twoSum.head, a) // a + b = a |
| 116 | + XCTAssertEqual(twoSum.tail, b) // Error: b |
| 117 | + twoSum = Augmented.sum( a, -b) |
| 118 | + XCTAssertEqual(twoSum.head, a) |
| 119 | + XCTAssertEqual(twoSum.tail,-b) |
| 120 | + twoSum = Augmented.sum(-a, b) |
| 121 | + XCTAssertEqual(twoSum.head,-a) |
| 122 | + XCTAssertEqual(twoSum.tail, b) |
| 123 | + twoSum = Augmented.sum(-a, -b) |
| 124 | + XCTAssertEqual(twoSum.head,-a) |
| 125 | + XCTAssertEqual(twoSum.tail,-b) |
| 126 | + // Must handle cancellation when `b` is only partially representable in `a`. |
| 127 | + // We expect the fractional digits of `b` to be cancelled in the following |
| 128 | + // example but the fractional digits to be preserved in `tail`. |
| 129 | + let exponent = T.Exponent(T.significandBitCount + 1) |
| 130 | + a = T(sign: .plus, exponent: exponent, significand: 1.0) |
| 131 | + b = 256 + 0.5 |
| 132 | + twoSum = Augmented.sum( a, b) |
| 133 | + XCTAssertEqual(twoSum.head, a + 256) |
| 134 | + XCTAssertEqual(twoSum.tail, 0.5) |
| 135 | + twoSum = Augmented.sum( a, -b) |
| 136 | + XCTAssertEqual(twoSum.head, a - 256) |
| 137 | + XCTAssertEqual(twoSum.tail, -0.5) |
| 138 | + twoSum = Augmented.sum(-a, b) |
| 139 | + XCTAssertEqual(twoSum.head, -a + 256) |
| 140 | + XCTAssertEqual(twoSum.tail, 0.5) |
| 141 | + twoSum = Augmented.sum(-a, -b) |
| 142 | + XCTAssertEqual(twoSum.head, -a - 256) |
| 143 | + XCTAssertEqual(twoSum.tail, -0.5) |
| 144 | + } |
| 145 | + |
| 146 | + func testTwoSum() { |
| 147 | + testTwoSumSpecials(Float32.self) |
| 148 | + testTwoSumRandomValues(Float32.self) |
| 149 | + testTwoSumCancellation(Float32.self) |
| 150 | + testTwoSumSpecials(Float64.self) |
| 151 | + testTwoSumRandomValues(Float64.self) |
| 152 | + testTwoSumCancellation(Float64.self) |
| 153 | +#if (arch(i386) || arch(x86_64)) && !os(Windows) && !os(Android) |
| 154 | + testTwoSumSpecials(Float80.self) |
| 155 | + testTwoSumRandomValues(Float80.self) |
| 156 | + testTwoSumCancellation(Float80.self) |
| 157 | +#endif |
| 158 | + } |
| 159 | +} |
| 160 | + |
0 commit comments