Skip to content

Commit 9a3a326

Browse files
committed
Refactor NumberClass to elegant hierarchical enum with associated values
Replace flat enum with 10 cases to hierarchical structure using associated values for more elegant and Swift-like API. ## Changes ### NumberClass Enum Redesign **Before (flat structure)**: ```swift public enum NumberClass { case signalingNaN case quietNaN case negativeInfinity case negativeNormal case negativeSubnormal case negativeZero case positiveZero case positiveSubnormal case positiveNormal case positiveInfinity } ``` **After (hierarchical with associated values)**: ```swift public enum NumberClass: Sendable, Equatable { case nan(NaNKind) case positive(FiniteKind) case negative(FiniteKind) public enum NaNKind: Sendable, Equatable { case signaling case quiet } public enum FiniteKind: Sendable, Equatable { case infinity case normal case subnormal case zero } } ``` ### API Improvements **Pattern matching is now more elegant**: ```swift switch IEEE_754.Classification.numberClass(value) { case .nan(.signaling): // Handle signaling NaN case .nan(.quiet): // Handle quiet NaN case .positive(.infinity): // Handle +∞ case .positive(.normal): // Handle positive normal case .negative(.zero): // Handle -0.0 } ``` **Equality checks are clearer**: ```swift // Before numberClass(value) == .positiveNormal // After numberClass(value) == .positive(.normal) ``` ### Implementation Updates Updated `numberClass(_:)` for both Double and Float to return hierarchical structure: - Classify NaN with associated `.signaling` or `.quiet` - Classify finite values with sign (`.positive` or `.negative`) and kind - More maintainable implementation with clear separation of concerns ### Benefits 1. **More Swiftonian**: Uses associated values like Result<Success, Failure> 2. **Better pattern matching**: Can match on categories (.nan, .positive, .negative) 3. **Type safety**: Impossible states like .nan(.normal) don't exist 4. **Clearer semantics**: Sign and kind are explicit in the type 5. **Sendable & Equatable**: All nested enums conform properly ### Tests Updated All 545 tests updated to use new API: - `.signalingNaN` → `.nan(.signaling)` - `.quietNaN` → `.nan(.quiet)` - `.positiveNormal` → `.positive(.normal)` - `.negativeZero` → `.negative(.zero)` - etc. Tests: All 545 tests pass (100% pass rate)
1 parent eef08a8 commit 9a3a326

File tree

2 files changed

+107
-74
lines changed

2 files changed

+107
-74
lines changed

Sources/IEEE_754/IEEE_754.Classification.swift

Lines changed: 81 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -421,87 +421,120 @@ extension IEEE_754.Classification {
421421
///
422422
/// ## See Also
423423
/// - IEEE 754-2019 Section 5.7.2: class predicate
424-
public enum NumberClass {
425-
/// Signaling NaN
426-
case signalingNaN
427-
/// Quiet NaN
428-
case quietNaN
429-
/// Negative infinity
430-
case negativeInfinity
431-
/// Negative normal number
432-
case negativeNormal
433-
/// Negative subnormal number
434-
case negativeSubnormal
435-
/// Negative zero
436-
case negativeZero
437-
/// Positive zero
438-
case positiveZero
439-
/// Positive subnormal number
440-
case positiveSubnormal
441-
/// Positive normal number
442-
case positiveNormal
443-
/// Positive infinity
444-
case positiveInfinity
424+
/// IEEE 754 Number Class
425+
///
426+
/// Represents the 10 IEEE 754 number classes using a hierarchical structure
427+
/// with associated values for more elegant pattern matching.
428+
///
429+
/// ## Usage
430+
///
431+
/// ```swift
432+
/// switch IEEE_754.Classification.numberClass(value) {
433+
/// case .nan(.signaling):
434+
/// // Handle signaling NaN
435+
/// case .nan(.quiet):
436+
/// // Handle quiet NaN
437+
/// case .positive(.infinity):
438+
/// // Handle +∞
439+
/// case .positive(.normal):
440+
/// // Handle positive normal
441+
/// case .negative(.zero):
442+
/// // Handle -0.0
443+
/// }
444+
/// ```
445+
public enum NumberClass: Sendable, Equatable {
446+
/// NaN (Not a Number)
447+
case nan(NaNKind)
448+
/// Positive value
449+
case positive(FiniteKind)
450+
/// Negative value
451+
case negative(FiniteKind)
452+
453+
/// Kind of NaN
454+
public enum NaNKind: Sendable, Equatable {
455+
/// Signaling NaN (raises exception on most operations)
456+
case signaling
457+
/// Quiet NaN (propagates through operations)
458+
case quiet
459+
}
460+
461+
/// Kind of finite or infinite value
462+
public enum FiniteKind: Sendable, Equatable {
463+
/// Infinity (±∞)
464+
case infinity
465+
/// Normal number (normalized with implicit leading 1)
466+
case normal
467+
/// Subnormal number (denormalized)
468+
case subnormal
469+
/// Zero (±0)
470+
case zero
471+
}
445472
}
446473

447474
/// Returns the class of a Double value - IEEE 754 `class`
448475
///
449-
/// Classifies the value into one of 10 IEEE 754 number classes.
476+
/// Classifies the value into one of 10 IEEE 754 number classes using a hierarchical structure.
450477
///
451478
/// - Parameter value: The value to classify
452479
/// - Returns: The number class
453480
///
454481
/// Example:
455482
/// ```swift
456-
/// IEEE_754.Classification.numberClass(3.14) // .positiveNormal
457-
/// IEEE_754.Classification.numberClass(-0.0) // .negativeZero
458-
/// IEEE_754.Classification.numberClass(.infinity) // .positiveInfinity
459-
/// IEEE_754.Classification.numberClass(.nan) // .quietNaN
483+
/// IEEE_754.Classification.numberClass(3.14) // .positive(.normal)
484+
/// IEEE_754.Classification.numberClass(-0.0) // .negative(.zero)
485+
/// IEEE_754.Classification.numberClass(.infinity) // .positive(.infinity)
486+
/// IEEE_754.Classification.numberClass(.nan) // .nan(.quiet)
460487
/// ```
461488
@inlinable
462489
public static func numberClass(_ value: Double) -> NumberClass {
463490
if value.isNaN {
464-
return value.isSignalingNaN ? .signalingNaN : .quietNaN
491+
return .nan(value.isSignalingNaN ? .signaling : .quiet)
465492
}
493+
494+
let kind: NumberClass.FiniteKind
466495
if value.isInfinite {
467-
return value.sign == .minus ? .negativeInfinity : .positiveInfinity
468-
}
469-
if value.isZero {
470-
return value.sign == .minus ? .negativeZero : .positiveZero
496+
kind = .infinity
497+
} else if value.isZero {
498+
kind = .zero
499+
} else if value.isSubnormal {
500+
kind = .subnormal
501+
} else {
502+
kind = .normal
471503
}
472-
if value.isSubnormal {
473-
return value.sign == .minus ? .negativeSubnormal : .positiveSubnormal
474-
}
475-
return value.sign == .minus ? .negativeNormal : .positiveNormal
504+
505+
return value.sign == .minus ? .negative(kind) : .positive(kind)
476506
}
477507

478508
/// Returns the class of a Float value - IEEE 754 `class`
479509
///
480-
/// Classifies the value into one of 10 IEEE 754 number classes.
510+
/// Classifies the value into one of 10 IEEE 754 number classes using a hierarchical structure.
481511
///
482512
/// - Parameter value: The value to classify
483513
/// - Returns: The number class
484514
///
485515
/// Example:
486516
/// ```swift
487-
/// IEEE_754.Classification.numberClass(Float(3.14)) // .positiveNormal
488-
/// IEEE_754.Classification.numberClass(Float(-0.0)) // .negativeZero
489-
/// IEEE_754.Classification.numberClass(Float.infinity) // .positiveInfinity
517+
/// IEEE_754.Classification.numberClass(Float(3.14)) // .positive(.normal)
518+
/// IEEE_754.Classification.numberClass(Float(-0.0)) // .negative(.zero)
519+
/// IEEE_754.Classification.numberClass(Float.infinity) // .positive(.infinity)
490520
/// ```
491521
@inlinable
492522
public static func numberClass(_ value: Float) -> NumberClass {
493523
if value.isNaN {
494-
return value.isSignalingNaN ? .signalingNaN : .quietNaN
524+
return .nan(value.isSignalingNaN ? .signaling : .quiet)
495525
}
526+
527+
let kind: NumberClass.FiniteKind
496528
if value.isInfinite {
497-
return value.sign == .minus ? .negativeInfinity : .positiveInfinity
498-
}
499-
if value.isZero {
500-
return value.sign == .minus ? .negativeZero : .positiveZero
529+
kind = .infinity
530+
} else if value.isZero {
531+
kind = .zero
532+
} else if value.isSubnormal {
533+
kind = .subnormal
534+
} else {
535+
kind = .normal
501536
}
502-
if value.isSubnormal {
503-
return value.sign == .minus ? .negativeSubnormal : .positiveSubnormal
504-
}
505-
return value.sign == .minus ? .negativeNormal : .positiveNormal
537+
538+
return value.sign == .minus ? .negative(kind) : .positive(kind)
506539
}
507540
}

Tests/IEEE_754 Tests/IEEE_754.Classification Tests.swift

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -227,55 +227,55 @@ struct DoubleRadixTests {
227227
struct DoubleNumberClassTests {
228228
@Test func signalingNaN() {
229229
let snan = Double.signalingNaN
230-
#expect(IEEE_754.Classification.numberClass(snan) == .signalingNaN)
230+
#expect(IEEE_754.Classification.numberClass(snan) == .nan(.signaling))
231231
}
232232

233233
@Test func quietNaN() {
234234
let qnan = Double.nan
235-
#expect(IEEE_754.Classification.numberClass(qnan) == .quietNaN)
235+
#expect(IEEE_754.Classification.numberClass(qnan) == .nan(.quiet))
236236
}
237237

238238
@Test func negativeInfinity() {
239-
#expect(IEEE_754.Classification.numberClass(-Double.infinity) == .negativeInfinity)
239+
#expect(IEEE_754.Classification.numberClass(-Double.infinity) == .negative(.infinity))
240240
}
241241

242242
@Test func negativeNormal() {
243-
#expect(IEEE_754.Classification.numberClass(-3.14) == .negativeNormal)
243+
#expect(IEEE_754.Classification.numberClass(-3.14) == .negative(.normal))
244244
}
245245

246246
@Test func negativeSubnormal() {
247247
let negSubnorm = -Double.leastNonzeroMagnitude
248-
#expect(IEEE_754.Classification.numberClass(negSubnorm) == .negativeSubnormal)
248+
#expect(IEEE_754.Classification.numberClass(negSubnorm) == .negative(.subnormal))
249249
}
250250

251251
@Test func negativeZero() {
252-
#expect(IEEE_754.Classification.numberClass(-0.0) == .negativeZero)
252+
#expect(IEEE_754.Classification.numberClass(-0.0) == .negative(.zero))
253253
}
254254

255255
@Test func positiveZero() {
256-
#expect(IEEE_754.Classification.numberClass(0.0) == .positiveZero)
256+
#expect(IEEE_754.Classification.numberClass(0.0) == .positive(.zero))
257257
}
258258

259259
@Test func positiveSubnormal() {
260260
let posSubnorm = Double.leastNonzeroMagnitude
261-
#expect(IEEE_754.Classification.numberClass(posSubnorm) == .positiveSubnormal)
261+
#expect(IEEE_754.Classification.numberClass(posSubnorm) == .positive(.subnormal))
262262
}
263263

264264
@Test func positiveNormal() {
265-
#expect(IEEE_754.Classification.numberClass(3.14) == .positiveNormal)
265+
#expect(IEEE_754.Classification.numberClass(3.14) == .positive(.normal))
266266
}
267267

268268
@Test func positiveInfinity() {
269-
#expect(IEEE_754.Classification.numberClass(Double.infinity) == .positiveInfinity)
269+
#expect(IEEE_754.Classification.numberClass(Double.infinity) == .positive(.infinity))
270270
}
271271

272272
@Test(arguments: [
273-
(-Double.infinity, IEEE_754.Classification.NumberClass.negativeInfinity),
274-
(-100.0, .negativeNormal),
275-
(-0.0, .negativeZero),
276-
(0.0, .positiveZero),
277-
(100.0, .positiveNormal),
278-
(Double.infinity, .positiveInfinity)
273+
(-Double.infinity, IEEE_754.Classification.NumberClass.negative(.infinity)),
274+
(-100.0, .negative(.normal)),
275+
(-0.0, .negative(.zero)),
276+
(0.0, .positive(.zero)),
277+
(100.0, .positive(.normal)),
278+
(Double.infinity, .positive(.infinity))
279279
])
280280
func numberClassCases(value: Double, expected: IEEE_754.Classification.NumberClass) {
281281
#expect(IEEE_754.Classification.numberClass(value) == expected)
@@ -333,29 +333,29 @@ struct FloatIsFiniteTests {
333333
@Suite("IEEE_754.Classification - Float numberClass")
334334
struct FloatNumberClassTests {
335335
@Test(arguments: [
336-
(Float(-0.0), IEEE_754.Classification.NumberClass.negativeZero),
337-
(Float(0.0), .positiveZero),
338-
(Float(-3.14), .negativeNormal),
339-
(Float(3.14), .positiveNormal),
340-
(-Float.infinity, .negativeInfinity),
341-
(Float.infinity, .positiveInfinity)
336+
(Float(-0.0), IEEE_754.Classification.NumberClass.negative(.zero)),
337+
(Float(0.0), .positive(.zero)),
338+
(Float(-3.14), .negative(.normal)),
339+
(Float(3.14), .positive(.normal)),
340+
(-Float.infinity, .negative(.infinity)),
341+
(Float.infinity, .positive(.infinity))
342342
])
343343
func numberClassCases(value: Float, expected: IEEE_754.Classification.NumberClass) {
344344
#expect(IEEE_754.Classification.numberClass(value) == expected)
345345
}
346346

347347
@Test func quietNaN() {
348-
#expect(IEEE_754.Classification.numberClass(Float.nan) == .quietNaN)
348+
#expect(IEEE_754.Classification.numberClass(Float.nan) == .nan(.quiet))
349349
}
350350

351351
@Test func signalingNaN() {
352-
#expect(IEEE_754.Classification.numberClass(Float.signalingNaN) == .signalingNaN)
352+
#expect(IEEE_754.Classification.numberClass(Float.signalingNaN) == .nan(.signaling))
353353
}
354354

355355
@Test func subnormals() {
356356
let posSubnorm = Float.leastNonzeroMagnitude
357357
let negSubnorm = -Float.leastNonzeroMagnitude
358-
#expect(IEEE_754.Classification.numberClass(posSubnorm) == .positiveSubnormal)
359-
#expect(IEEE_754.Classification.numberClass(negSubnorm) == .negativeSubnormal)
358+
#expect(IEEE_754.Classification.numberClass(posSubnorm) == .positive(.subnormal))
359+
#expect(IEEE_754.Classification.numberClass(negSubnorm) == .negative(.subnormal))
360360
}
361361
}

0 commit comments

Comments
 (0)