From dea795539b36450153e8b92f14d4f7617f89cbe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Bystr=C3=B6m=20Ericsson?= Date: Thu, 10 Oct 2024 10:18:43 +0200 Subject: [PATCH] Rewrite BinaryInteger+Geometry.swift tests (#110). --- Sources/TestKit2/Expect+Geometry.swift | 27 +++ .../BinaryInteger+Geometry.swift | 190 ++++++++++-------- 2 files changed, 136 insertions(+), 81 deletions(-) create mode 100644 Sources/TestKit2/Expect+Geometry.swift diff --git a/Sources/TestKit2/Expect+Geometry.swift b/Sources/TestKit2/Expect+Geometry.swift new file mode 100644 index 00000000..c179c2c3 --- /dev/null +++ b/Sources/TestKit2/Expect+Geometry.swift @@ -0,0 +1,27 @@ +//=----------------------------------------------------------------------------= +// This source file is part of the Ultimathnum open source project. +// +// Copyright (c) 2023 Oscar Byström Ericsson +// Licensed under Apache License, Version 2.0 +// +// See http://www.apache.org/licenses/LICENSE-2.0 for license information. +//=----------------------------------------------------------------------------= + +import CoreKit + +//*============================================================================* +// MARK: * Expect x Geometry +//*============================================================================* + +@inlinable public func Ɣexpect( + _ value: T, + isqrt expectation: T, + at location: SourceLocation = #_sourceLocation +) throws where T: BinaryInteger { + + let low = try #require(expectation.squared().optional()) + #expect(value >= low, sourceLocation: location) + + guard let high = expectation.incremented().squared().optional() else { return } + #expect(value < high, sourceLocation: location) +} diff --git a/Tests/UltimathnumTests/BinaryInteger+Geometry.swift b/Tests/UltimathnumTests/BinaryInteger+Geometry.swift index 554e675f..e57e9730 100644 --- a/Tests/UltimathnumTests/BinaryInteger+Geometry.swift +++ b/Tests/UltimathnumTests/BinaryInteger+Geometry.swift @@ -9,127 +9,155 @@ import CoreKit import RandomIntKit -import TestKit +import TestKit2 //*============================================================================* // MARK: * Binary Integer x Geometry //*============================================================================* -final class BinaryIntegerTestsOnGeometry: XCTestCase { +@Suite struct BinaryIntegerTestsOnGeometry { //=------------------------------------------------------------------------= // MARK: Tests //=------------------------------------------------------------------------= - /// Checks all values in `0` through `min(T.max, 255)`. - func testSquareRootOfSmallValues() { - func whereIs(_ type: T.Type) where T: BinaryInteger { - var low = (base: 0 as U32, square: 0 as U32) - var high = (base: 1 as U32, square: 1 as U32) - - while low.square <= U8.max { - for value in low.square ..< high.square { - guard let value = T.exactly(value).optional() else { return } - Test().isqrt(value, T(load: low.base)) - } - - low = high - high.base = high.base.incremented().unwrap() - high.square = (high).base.squared().unwrap() + @Test("BinaryInteger/isqrt() - [entropic]", arguments: binaryIntegers, fuzzers) + func integerSquareRoot(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) + + if let type = type as? any SystemsIntegerWhereIsUnsigned.Type { + try whereIsSystemsIntegerWhereIsUnsigned(type) + } + + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + for _ in 0 ..< conditional(debug: 32, release: 256) { + let value = T.entropic(through: Shift.max(or: 255), as: .natural, using: &randomness) + try Ɣexpect(value, isqrt: try #require(value.isqrt())) } } - for type in binaryIntegers { - whereIs(type) + func whereIsSystemsIntegerWhereIsUnsigned(_ type: T.Type) throws where T: SystemsIntegerWhereIsUnsigned { + for _ in 0 ..< conditional(debug: 32, release: 256) { + let value = T.entropic(through: Shift.max(or: 255), as: .natural, using: &randomness) + try Ɣexpect(value, isqrt: value.isqrt()) + } + } + } + + @Test("BinaryInteger/isqrt() - recovery mechanism [entropic]", arguments: systemsIntegersWhereIsUnsigned, fuzzers) + func integerSquareRootRecoveryMechanism(type: any SystemsIntegerWhereIsUnsigned.Type, randomness: consuming FuzzerInt) { + whereIs(type) + + func whereIs(_ type: T.Type) where T: SystemsIntegerWhereIsUnsigned { + for _ in 0 ..< 32 { + let random = T.entropic(using: &randomness) + let expectation: T = random.isqrt() + for error in Bool.all { + #expect(random.veto(error).isqrt() == expectation.veto(error)) + } + } } } +} + +//*============================================================================* +// MARK: * Binary Integer x Geometry x Edge Cases +//*============================================================================* + +@Suite(.tags(.documentation)) struct BinaryIntegerTestsOnGeometryEdgeCases { + + //=------------------------------------------------------------------------= + // MARK: Tests + //=------------------------------------------------------------------------= /// - Note: A binary algorithm may make a correct initial guess here. - func testSquareRootOfPowerOf2Squares() { - func whereIs(_ type: T.Type) where T: SystemsInteger & UnsignedInteger { - for index in 0 ..< IX(size: type)/2 { + @Test("BinaryInteger/isqrt() - natural power-of-2 squares", arguments: binaryIntegers, fuzzers) + func integerSquareRootOfNaturalPowerOf2Squares(type: any BinaryInteger.Type, randomness: consuming FuzzerInt) { + whereIs(type) + + func whereIs(_ type: T.Type) where T: BinaryInteger { + for index in 0 ..< ((IX(size: T.self) ?? 256) / 2) { let expectation = T.lsb << index let power = expectation << index - Test().isqrt(power, expectation) + #expect(power.isqrt() == expectation) } } - - for type in systemsIntegersWhereIsUnsigned { - whereIs(type) - } } - func testSquareRootOfInfiniteOrNegativeIsNil() { - func whereIs(_ type: T.Type) where T: BinaryInteger { - guard type.isSigned || type.isArbitrary else { return } - Test().none((~0 as T).isqrt()) - Test().none((~1 as T).isqrt()) - Test().none((~2 as T).isqrt()) - Test().none((~3 as T).isqrt()) - } + @Test("BinaryInteger/isqrt() - negative is nil [uniform]", arguments: binaryIntegersWhereIsSigned, fuzzers) + func integerSquareRootOfNegativeIsNil(type: any SignedInteger.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - for type in binaryIntegers { - whereIs(type) + func whereIs(_ type: T.Type) throws where T: SignedInteger { + let low = T(repeating: Bit.one).up(Shift.max(or: 255)) + let high = T(repeating: Bit.one) + + #expect(low .isqrt() == nil) + #expect(high.isqrt() == nil) + + for _ in 0 ..< 32 { + let random = T.random(in: low...high, using: &randomness) + #expect(random.isqrt() == nil) + } } } - //=------------------------------------------------------------------------= - // MARK: Tests x Random - //=------------------------------------------------------------------------= - - func testSquareRootByFuzzing() { - func whereIs(_ type: T.Type, size: IX, rounds: IX, randomness: consuming FuzzerInt) where T: UnsignedInteger { - let index = Shift(Count(size/2-1)) - for _ in 0 ..< rounds { - let expectation = T.random(through: index, using: &randomness) - let limit = expectation.times(2).incremented().unwrap() - let error = T.random(in: T.zero ..< limit, using: &randomness) ?? T.zero - let power = expectation.squared().plus(error) - Test().same(power.optional()?.isqrt(), expectation) - } - } + @Test("BinaryInteger/isqrt() - infinite is nil [uniform]", arguments: arbitraryIntegersWhereIsUnsigned, fuzzers) + func integerSquareRootOfInfiniteIsNil(type: any ArbitraryIntegerWhereIsUnsigned.Type, randomness: consuming FuzzerInt) throws { + try whereIs(type) - for type in binaryIntegersWhereIsUnsigned { - #if DEBUG - whereIs(type, size: IX(size: type) ?? 128, rounds: 032, randomness: fuzzer) - #else - whereIs(type, size: IX(size: type) ?? 256, rounds: 256, randomness: fuzzer) - #endif + func whereIs(_ type: T.Type) throws where T: ArbitraryIntegerWhereIsUnsigned { + let low = T(repeating: Bit.one).up(Shift.max(or: 255)) + let high = T(repeating: Bit.one) + + #expect(low .isqrt() == nil) + #expect(high.isqrt() == nil) + + for _ in 0 ..< 32 { + let random = T.random(in: low...high, using: &randomness) + #expect(random.isqrt() == nil) + } } } } -//=----------------------------------------------------------------------------= -// MARK: + Recoverable -//=----------------------------------------------------------------------------= +//*============================================================================* +// MARK: * Binary Integer x Geometry x Examples +//*============================================================================* -extension BinaryIntegerTestsOnGeometry { +@Suite(.tags(.documentation), .serialized) struct BinaryIntegerTestsOnGeometryExamples { //=------------------------------------------------------------------------= // MARK: Tests //=------------------------------------------------------------------------= - func testErrorPropagationMechanism() { - func whereIs(_ type: T.Type, rounds: IX, randomness: consuming FuzzerInt) where T: UnsignedInteger & SystemsInteger { - var success: IX = 0 - - func random() -> T { - let index = IX.random(in: 0.. = instance.isqrt().veto(false) - success &+= IX(Bit(instance.veto(false).isqrt() == expectation)) - success &+= IX(Bit(instance.veto(true ).isqrt() == expectation.veto())) - } - - Test().same(success, rounds &* 2) + @Test("BinaryInteger/isqrt()", arguments: [ + + ( 0 as U8, 0 as U8), + ( 1 as U8, 1 as U8), + ( 2 as U8, 1 as U8), + ( 3 as U8, 1 as U8), + ( 4 as U8, 2 as U8), + ( 5 as U8, 2 as U8), + ( 6 as U8, 2 as U8), + ( 7 as U8, 2 as U8), + ( 8 as U8, 2 as U8), + ( 9 as U8, 3 as U8), + (10 as U8, 3 as U8), + (11 as U8, 3 as U8), + (12 as U8, 3 as U8), + (13 as U8, 3 as U8), + (14 as U8, 3 as U8), + (15 as U8, 3 as U8), + (16 as U8, 4 as U8), + + ] as [(U8, U8)]) func integerSquareRootOfSmallNaturals(value: U8, expectation: U8) throws { + for type in binaryIntegers { + try whereIs(type) } - for type in systemsIntegersWhereIsUnsigned { - whereIs(type, rounds: 32, randomness: fuzzer) + func whereIs(_ type: T.Type) throws where T: BinaryInteger { + try Ɣexpect(T(value), isqrt: T(expectation)) } } }