Share
Embed
浮動小数の比較について
XCTestのXCTAssertEqual(_:_:accuracy:_:file:line:)とSwift Numericsが提供するisApproximatelyEqual()の違いについて
Kishikawa Katsumi
February 12, 2026
Transcript
34 slides
-
let center = CGPoint(x: 900, y: 900) let radius: CGFloat
= 1000 let p = pointOnCircle(center: center, radius: radius, angle: .pi / 2) #expect(p.x == center.x) -
let center = CGPoint(x: 900, y: 900) let radius: CGFloat
= 1000 let p = pointOnCircle(center: center, radius: radius, angle: .pi / 2) XCTAssertEqual(p.x, center.x, accuracy: 0.000001) -
XCTest Swift Testing XCTAssert(x), XCTAssertTrue(x) #expect(x) XCTAssertFalse(x) #expect(!x) XCTAssertNil(x) #expect(x
== nil) XCTAssertNotNil(x) #expect(x != nil) XCTAssertEqual(x, y) #expect(x == y) XCTAssertNotEqual(x, y) #expect(x != y) XCTAssertIdentical(x, y) #expect(x === y) XCTAssertNotIdentical(x, y) #expect(x !== y) XCTAssertGreaterThan(x, y) #expect(x > y) XCTAssertGreaterThanOrEqual(x, y) #expect(x >= y) XCTAssertLessThanOrEqual(x, y) #expect(x <= y) XCTAssertLessThan(x, y) #expect(x < y) XCTAssertThrowsError(try f()) #expect(throws: (any Error).self) { try f() } XCTAssertThrowsError(try f()) { error in … } let error = #expect(throws: (any Error).self) { try f() } XCTAssertNoThrow(try f()) #expect(throws: Never.self) { try f() } try XCTUnwrap(x) try #require(x) XCTFail("…") Issue.record("…") -
9$5FTU4XJGU5FTUJOH The testing library doesn’t provide an equivalent of XCTAssertEqual(_:_:accuracy:_:file:line:).
To compare two numeric values within a speci fi ed accuracy, use isApproximatelyEqual() from swift-numerics. -
9$"TTFSU&RVBM *NQMFNFOUBUJPO private func areEqual<T: Numeric>(_ exp1: T, _ exp2:
T, accuracy: T) -> Bool { // Test with equality first to handle comparing inf/-inf with itself. if exp1 == exp2 { return true } else { // NaN values are handled implicitly, since the <= operator returns false when comparing any value to NaN. let difference = (exp1.magnitude > exp2.magnitude) ? exp1 - exp2 : exp2 - exp1 return difference.magnitude <= accuracy.magnitude } } https://github.com/swiftlang/swift-corelibs-foundation/blob/main/Sources/XCTest/Public/XCTAssert.swift -
9$"TTFSU&RVBM *NQMFNFOUBUJPO private func areEqual<T: Numeric>(_ exp1: T, _ exp2:
T, accuracy: T) -> Bool { // Test with equality first to handle comparing inf/-inf with itself. if exp1 == exp2 { return true } else { // NaN values are handled implicitly, since the <= operator returns false when comparing any value to NaN. let difference = (exp1.magnitude > exp2.magnitude) ? exp1 - exp2 : exp2 - exp1 return difference.magnitude <= accuracy.magnitude } } https://github.com/swiftlang/swift-corelibs-foundation/blob/main/Sources/XCTest/Public/XCTAssert.swift -
9$"TTFSU&RVBM *NQMFNFOUBUJPO private func areEqual<T: Numeric>(_ exp1: T, _ exp2:
T, accuracy: T) -> Bool { // Test with equality first to handle comparing inf/-inf with itself. if exp1 == exp2 { return true } else { // NaN values are handled implicitly, since the <= operator returns false when comparing any value to NaN. let difference = (exp1.magnitude > exp2.magnitude) ? exp1 - exp2 : exp2 - exp1 return difference.magnitude <= accuracy.magnitude } } https://github.com/swiftlang/swift-corelibs-foundation/blob/main/Sources/XCTest/Public/XCTAssert.swift -
9$"TTFSU&RVBM *NQMFNFOUBUJPO private func areEqual<T: Numeric>(_ exp1: T, _ exp2:
T, accuracy: T) -> Bool { // Test with equality first to handle comparing inf/-inf with itself. if exp1 == exp2 { return true } else { // NaN values are handled implicitly, since the <= operator returns false when comparing any value to NaN. let difference = (exp1.magnitude > exp2.magnitude) ? exp1 - exp2 : exp2 - exp1 return difference.magnitude <= accuracy.magnitude } } https://github.com/swiftlang/swift-corelibs-foundation/blob/main/Sources/XCTest/Public/XCTAssert.swift -
https://github.com/apple/swift-numerics/blob/main/Sources/RealModule/ApproximateEquality.swift public func isApproximatelyEqual<Magnitude>( to other: Self, absoluteTolerance: Magnitude, relativeTolerance:
Magnitude = 0, norm: (Self) -> Magnitude ) -> Bool where Magnitude: FloatingPoint { assert( absoluteTolerance >= 0 && absoluteTolerance.isFinite, "absoluteTolerance should be non-negative and finite, " + "but is \(absoluteTolerance)." ) assert( relativeTolerance >= 0 && relativeTolerance <= 1, "relativeTolerance should be non-negative and <= 1, " + "but is \(relativeTolerance)." ) if self == other { return true } let delta = norm(self - other) let scale = max(norm(self), norm(other)) let bound = max(absoluteTolerance, scale*relativeTolerance) return delta.isFinite && delta <= bound } -
r ઈରޡࠩabs(a - b) < tolerance ʢྫʣ9$5FTUͷXCTAssertEqual(_:_:accuracy:) r ઈରޡࠩ ૬ରޡࠩabs(a
- b) <= max(absTol, scale * relTol) ʢྫʣTXJGUOVNFSJDT r ओʹ૬ରޡࠩɺθϩۙΛಛผѻ͍ ʢྫʣ4& ઈରޡࠩɾ૬ରޡࠩ 'MPBUJOH1PJOU$PNQBSJTPO -
r θϩۙͷখ͞ͳઈରޡࠩɺڊେͳʹରͯ͠૬ରޡࠩΛ͏ r ͲͷΑ͏ͳͰൺֱΛ౷Ұతͳํ๏Ͱߦ͑Δ r ୯ʹઈରޡࠩͱ૬ରޡࠩΛ྆ํࢦఆ͍ͯ͠ΔΘ͚Ͱͳ͘ɺҰͭͷࣜʹΑͬͯ ∣a−b∣ ≤ max(absTol, scale
× relTol) ઈରޡࠩʢBCT5PMʣͱ૬ରޡࠩʢTDBMFºSFM5PMʣ͕͘͠ͳΔڥքͰࣗಈతʹ ΓସΘΔ ઈରޡࠩͱ૬ରޡࠩͷ߹ʢ4XJGU/VNFSJDTʣ ઈରޡࠩɾ૬ରޡࠩ -
https://github.com/apple/swift-numerics/blob/main/Sources/RealModule/ApproximateEquality.swift public func isApproximatelyEqual<Magnitude>( to other: Self, absoluteTolerance: Magnitude, relativeTolerance:
Magnitude = 0, norm: (Self) -> Magnitude ) -> Bool where Magnitude: FloatingPoint { assert( absoluteTolerance >= 0 && absoluteTolerance.isFinite, "absoluteTolerance should be non-negative and finite, " + "but is \(absoluteTolerance)." ) assert( relativeTolerance >= 0 && relativeTolerance <= 1, "relativeTolerance should be non-negative and <= 1, " + "but is \(relativeTolerance)." ) if self == other { return true } let delta = norm(self - other) let scale = max(norm(self), norm(other)) let bound = max(absoluteTolerance, scale*relativeTolerance) return delta.isFinite && delta <= bound } -
https://github.com/apple/swift-numerics/blob/main/Sources/RealModule/ApproximateEquality.swift public func isApproximatelyEqual<Magnitude>( to other: Self, absoluteTolerance: Magnitude, relativeTolerance:
Magnitude = 0, norm: (Self) -> Magnitude ) -> Bool where Magnitude: FloatingPoint { assert( absoluteTolerance >= 0 && absoluteTolerance.isFinite, "absoluteTolerance should be non-negative and finite, " + "but is \(absoluteTolerance)." ) assert( relativeTolerance >= 0 && relativeTolerance <= 1, "relativeTolerance should be non-negative and <= 1, " + "but is \(relativeTolerance)." ) if self == other { return true } let delta = norm(self - other) let scale = max(norm(self), norm(other)) let bound = max(absoluteTolerance, scale*relativeTolerance) return delta.isFinite && delta <= bound } -
https://github.com/apple/swift-numerics/blob/main/Sources/RealModule/ApproximateEquality.swift public func isApproximatelyEqual<Magnitude>( to other: Self, absoluteTolerance: Magnitude, relativeTolerance:
Magnitude = 0, norm: (Self) -> Magnitude ) -> Bool where Magnitude: FloatingPoint { assert( absoluteTolerance >= 0 && absoluteTolerance.isFinite, "absoluteTolerance should be non-negative and finite, " + "but is \(absoluteTolerance)." ) assert( relativeTolerance >= 0 && relativeTolerance <= 1, "relativeTolerance should be non-negative and <= 1, " + "but is \(relativeTolerance)." ) if self == other { return true } let delta = norm(self - other) let scale = max(norm(self), norm(other)) let bound = max(absoluteTolerance, scale*relativeTolerance) return delta.isFinite && delta <= bound } -
https://github.com/apple/swift-numerics/blob/main/Sources/RealModule/ApproximateEquality.swift public func isApproximatelyEqual<Magnitude>( to other: Self, absoluteTolerance: Magnitude, relativeTolerance:
Magnitude = 0, norm: (Self) -> Magnitude ) -> Bool where Magnitude: FloatingPoint { assert( absoluteTolerance >= 0 && absoluteTolerance.isFinite, "absoluteTolerance should be non-negative and finite, " + "but is \(absoluteTolerance)." ) assert( relativeTolerance >= 0 && relativeTolerance <= 1, "relativeTolerance should be non-negative and <= 1, " + "but is \(relativeTolerance)." ) if self == other { return true } let delta = norm(self - other) let scale = max(norm(self), norm(other)) let bound = max(absoluteTolerance, scale*relativeTolerance) return delta.isFinite && delta <= bound } -
r ුಈখશҰகͷൺֱͰ͖ͳ͍ͷͰڐ༰ޡࠩͳΒҰகͱߟ͑Δ r ڐ༰ޡࠩઈରޡࠩͱ૬ରޡ͕ࠩ͋Δ r ͷੑ࣭খ͞ͳͱڊେͳʹΑ͍͚ͬͯΔ r 4XJGU/VNFSJDTแׅతͳαϙʔτΛఏڙ͍ͯ͠ΔͷͰ9$5FTUΑΓ͘͠ݟ ͑Δ r
4XJGU5FTUJOHͰ9$5FTUͱಉͳ͕ؔཉ͍͚ͩ͠ͳΒabs(a - b) <= tol ͱ͍͏ؔΛαοͱॻ͘ͷΞϦ ·ͱΊ ුಈখͷൺֱɾͦΕͧΕͷ࣮ͷҧ͍ -
r %PDVNFOUUIFSFDPNNFOEFEQBUUFSOGPSQFSGPSNJOH fl PBUJOHQPJOU FRVBMJUZXJUIBDDVSBDZFYQFDUBUJPOT BOBMPHPVTUP 9$5"TTFSU&RVBM @@BDDVSBDZ IUUQTHJUIVCDPNTXJGUMBOHTXJGUUFTUJOHJTTVFT
r 9$5"TTFSU&RVBMXJUIBDDVSBDZ IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOYDUFTU YDUBTTFSUFRVBM @@BDDVSBDZ@ fi MFMJOF FQV r "QQSPYJNBUF&RVBMJUZTXJGU 4XJGU/VNFSJDT IUUQTHJUIVCDPNBQQMFTXJGUOVNFSJDTCMPCNBJO4PVSDFT3FBM.PEVMF "QQSPYJNBUF&RVBMJUZTXJGU ࢀߟจݙ