Skip to content

Commit 919ee6b

Browse files
committed
Add elegant Swift API wrappers for CIEEE754 functionality
Created type-safe Swift wrappers that provide idiomatic APIs for all CIEEE754 features: **IEEE_754.RoundingControl** (new file) - Mode enum: .toNearest, .downward, .upward, .towardZero - get() -> Mode: Query current rounding mode - set(_:) throws: Set rounding mode - withMode(_:_:): Scoped rounding mode execution - Fully documented with examples **IEEE_754.Exceptions** (enhanced) - Integrated C thread-local exception storage - raise/testFlag/clear/clearAll now bridge to C TLS when available - Added FPUState struct for hardware exception state - testFPU() -> FPUState: Query hardware FPU exceptions - clearFPU(): Clear hardware FPU exceptions **IEEE_754.Comparison.Signaling** (new) - 12 signaling comparison functions (6 Double + 6 Float) - equal, less, lessEqual, greater, greaterEqual, notEqual - Raises invalid exception on NaN operands - Consistent API with quiet comparisons **Design principles:** - Foundation-free (uses CIEEE754 when available) - Type-safe enums instead of raw C integers - Throwing APIs instead of error codes - Comprehensive documentation with examples - Graceful degradation when CIEEE754 unavailable All wrappers build successfully and integrate seamlessly with existing IEEE_754 API.
1 parent 02a5fad commit 919ee6b

File tree

4 files changed

+687
-5
lines changed

4 files changed

+687
-5
lines changed

Sources/IEEE_754/IEEE_754.Comparison.swift

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
import Standards
88

9+
#if canImport(CIEEE754)
10+
import CIEEE754
11+
#endif
12+
913
// MARK: - IEEE 754 Comparison Operations
1014

1115
extension IEEE_754 {
@@ -427,3 +431,188 @@ extension IEEE_754.Comparison {
427431
lhs.magnitude.isTotallyOrdered(belowOrEqualTo: rhs.magnitude)
428432
}
429433
}
434+
435+
// MARK: - Signaling Comparisons
436+
437+
#if canImport(CIEEE754)
438+
extension IEEE_754.Comparison {
439+
/// Signaling comparison operations
440+
///
441+
/// Implements IEEE 754-2019 Section 5.6.1 signaling comparison predicates.
442+
///
443+
/// ## Overview
444+
///
445+
/// Signaling comparisons differ from quiet comparisons in that they raise
446+
/// the invalid operation exception when either operand is NaN (quiet or signaling).
447+
///
448+
/// This is useful for detecting unexpected NaN values in computational pipelines,
449+
/// as the exception can be caught and handled.
450+
///
451+
/// ## Example
452+
///
453+
/// ```swift
454+
/// IEEE_754.Exceptions.clearAll()
455+
///
456+
/// let result = IEEE_754.Comparison.Signaling.equal(Double.nan, 3.14)
457+
/// // result = false
458+
/// // IEEE_754.Exceptions.invalidOperation = true
459+
/// ```
460+
///
461+
/// ## See Also
462+
///
463+
/// - IEEE 754-2019 Section 5.6.1: Signaling comparison predicates
464+
/// - IEEE 754-2019 Section 7.2: Invalid operation exception
465+
@frozen
466+
public enum Signaling {
467+
// MARK: - Double (binary64) Comparisons
468+
469+
/// Signaling equality comparison for Double
470+
///
471+
/// - Parameters:
472+
/// - lhs: Left-hand value
473+
/// - rhs: Right-hand value
474+
/// - Returns: true if lhs == rhs, false otherwise (including NaN)
475+
///
476+
/// If either operand is NaN, raises invalid exception and returns false.
477+
public static func equal(_ lhs: Double, _ rhs: Double) -> Bool {
478+
ieee754_signaling_equal(lhs, rhs) != 0
479+
}
480+
481+
/// Signaling less than comparison for Double
482+
///
483+
/// - Parameters:
484+
/// - lhs: Left-hand value
485+
/// - rhs: Right-hand value
486+
/// - Returns: true if lhs < rhs
487+
///
488+
/// If either operand is NaN, raises invalid exception and returns false.
489+
public static func less(_ lhs: Double, _ rhs: Double) -> Bool {
490+
ieee754_signaling_less(lhs, rhs) != 0
491+
}
492+
493+
/// Signaling less than or equal comparison for Double
494+
///
495+
/// - Parameters:
496+
/// - lhs: Left-hand value
497+
/// - rhs: Right-hand value
498+
/// - Returns: true if lhs <= rhs
499+
///
500+
/// If either operand is NaN, raises invalid exception and returns false.
501+
public static func lessEqual(_ lhs: Double, _ rhs: Double) -> Bool {
502+
ieee754_signaling_less_equal(lhs, rhs) != 0
503+
}
504+
505+
/// Signaling greater than comparison for Double
506+
///
507+
/// - Parameters:
508+
/// - lhs: Left-hand value
509+
/// - rhs: Right-hand value
510+
/// - Returns: true if lhs > rhs
511+
///
512+
/// If either operand is NaN, raises invalid exception and returns false.
513+
public static func greater(_ lhs: Double, _ rhs: Double) -> Bool {
514+
ieee754_signaling_greater(lhs, rhs) != 0
515+
}
516+
517+
/// Signaling greater than or equal comparison for Double
518+
///
519+
/// - Parameters:
520+
/// - lhs: Left-hand value
521+
/// - rhs: Right-hand value
522+
/// - Returns: true if lhs >= rhs
523+
///
524+
/// If either operand is NaN, raises invalid exception and returns false.
525+
public static func greaterEqual(_ lhs: Double, _ rhs: Double) -> Bool {
526+
ieee754_signaling_greater_equal(lhs, rhs) != 0
527+
}
528+
529+
/// Signaling not equal comparison for Double
530+
///
531+
/// - Parameters:
532+
/// - lhs: Left-hand value
533+
/// - rhs: Right-hand value
534+
/// - Returns: true if lhs != rhs
535+
///
536+
/// If either operand is NaN, raises invalid exception and returns true
537+
/// (since NaN is not equal to anything, including itself).
538+
public static func notEqual(_ lhs: Double, _ rhs: Double) -> Bool {
539+
ieee754_signaling_not_equal(lhs, rhs) != 0
540+
}
541+
542+
// MARK: - Float (binary32) Comparisons
543+
544+
/// Signaling equality comparison for Float
545+
///
546+
/// - Parameters:
547+
/// - lhs: Left-hand value
548+
/// - rhs: Right-hand value
549+
/// - Returns: true if lhs == rhs, false otherwise (including NaN)
550+
///
551+
/// If either operand is NaN, raises invalid exception and returns false.
552+
public static func equal(_ lhs: Float, _ rhs: Float) -> Bool {
553+
ieee754_signaling_equal_f(lhs, rhs) != 0
554+
}
555+
556+
/// Signaling less than comparison for Float
557+
///
558+
/// - Parameters:
559+
/// - lhs: Left-hand value
560+
/// - rhs: Right-hand value
561+
/// - Returns: true if lhs < rhs
562+
///
563+
/// If either operand is NaN, raises invalid exception and returns false.
564+
public static func less(_ lhs: Float, _ rhs: Float) -> Bool {
565+
ieee754_signaling_less_f(lhs, rhs) != 0
566+
}
567+
568+
/// Signaling less than or equal comparison for Float
569+
///
570+
/// - Parameters:
571+
/// - lhs: Left-hand value
572+
/// - rhs: Right-hand value
573+
/// - Returns: true if lhs <= rhs
574+
///
575+
/// If either operand is NaN, raises invalid exception and returns false.
576+
public static func lessEqual(_ lhs: Float, _ rhs: Float) -> Bool {
577+
ieee754_signaling_less_equal_f(lhs, rhs) != 0
578+
}
579+
580+
/// Signaling greater than comparison for Float
581+
///
582+
/// - Parameters:
583+
/// - lhs: Left-hand value
584+
/// - rhs: Right-hand value
585+
/// - Returns: true if lhs > rhs
586+
///
587+
/// If either operand is NaN, raises invalid exception and returns false.
588+
public static func greater(_ lhs: Float, _ rhs: Float) -> Bool {
589+
ieee754_signaling_greater_f(lhs, rhs) != 0
590+
}
591+
592+
/// Signaling greater than or equal comparison for Float
593+
///
594+
/// - Parameters:
595+
/// - lhs: Left-hand value
596+
/// - rhs: Right-hand value
597+
/// - Returns: true if lhs >= rhs
598+
///
599+
/// If either operand is NaN, raises invalid exception and returns false.
600+
public static func greaterEqual(_ lhs: Float, _ rhs: Float) -> Bool {
601+
ieee754_signaling_greater_equal_f(lhs, rhs) != 0
602+
}
603+
604+
/// Signaling not equal comparison for Float
605+
///
606+
/// - Parameters:
607+
/// - lhs: Left-hand value
608+
/// - rhs: Right-hand value
609+
/// - Returns: true if lhs != rhs
610+
///
611+
/// If either operand is NaN, raises invalid exception and returns true
612+
/// (since NaN is not equal to anything, including itself).
613+
public static func notEqual(_ lhs: Float, _ rhs: Float) -> Bool {
614+
ieee754_signaling_not_equal_f(lhs, rhs) != 0
615+
}
616+
}
617+
}
618+
#endif

Sources/IEEE_754/IEEE_754.Exceptions.swift

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
import Synchronization
88

9+
#if canImport(CIEEE754)
10+
import CIEEE754
11+
#endif
12+
913
// MARK: - IEEE 754 Exception Handling
1014

1115
extension IEEE_754 {
@@ -249,9 +253,21 @@ extension IEEE_754.Exceptions {
249253
/// ```
250254
///
251255
/// Note: This operation is thread-safe.
252-
@inlinable
253256
public static func raise(_ flag: Flag) {
254257
sharedState.set(flag)
258+
259+
#if canImport(CIEEE754)
260+
// Also raise in C thread-local storage for consistency
261+
let cFlag: IEEE754ExceptionFlag
262+
switch flag {
263+
case .invalid: cFlag = IEEE754_EXCEPTION_INVALID
264+
case .divisionByZero: cFlag = IEEE754_EXCEPTION_DIVBYZERO
265+
case .overflow: cFlag = IEEE754_EXCEPTION_OVERFLOW
266+
case .underflow: cFlag = IEEE754_EXCEPTION_UNDERFLOW
267+
case .inexact: cFlag = IEEE754_EXCEPTION_INEXACT
268+
}
269+
ieee754_raise_exception(cFlag)
270+
#endif
255271
}
256272

257273
/// Test if an exception flag is raised
@@ -267,9 +283,21 @@ extension IEEE_754.Exceptions {
267283
/// print("Overflow occurred")
268284
/// }
269285
/// ```
270-
@inlinable
271286
public static func testFlag(_ flag: Flag) -> Bool {
272-
sharedState.get(flag)
287+
#if canImport(CIEEE754)
288+
// Check C thread-local storage (preferred if available)
289+
let cFlag: IEEE754ExceptionFlag
290+
switch flag {
291+
case .invalid: cFlag = IEEE754_EXCEPTION_INVALID
292+
case .divisionByZero: cFlag = IEEE754_EXCEPTION_DIVBYZERO
293+
case .overflow: cFlag = IEEE754_EXCEPTION_OVERFLOW
294+
case .underflow: cFlag = IEEE754_EXCEPTION_UNDERFLOW
295+
case .inexact: cFlag = IEEE754_EXCEPTION_INEXACT
296+
}
297+
return ieee754_test_exception(cFlag) != 0 || sharedState.get(flag)
298+
#else
299+
return sharedState.get(flag)
300+
#endif
273301
}
274302

275303
/// Clear an exception flag
@@ -282,9 +310,21 @@ extension IEEE_754.Exceptions {
282310
/// ```swift
283311
/// IEEE_754.Exceptions.clear(.overflow)
284312
/// ```
285-
@inlinable
286313
public static func clear(_ flag: Flag) {
287314
sharedState.clear(flag)
315+
316+
#if canImport(CIEEE754)
317+
// Also clear in C thread-local storage
318+
let cFlag: IEEE754ExceptionFlag
319+
switch flag {
320+
case .invalid: cFlag = IEEE754_EXCEPTION_INVALID
321+
case .divisionByZero: cFlag = IEEE754_EXCEPTION_DIVBYZERO
322+
case .overflow: cFlag = IEEE754_EXCEPTION_OVERFLOW
323+
case .underflow: cFlag = IEEE754_EXCEPTION_UNDERFLOW
324+
case .inexact: cFlag = IEEE754_EXCEPTION_INEXACT
325+
}
326+
ieee754_clear_exception(cFlag)
327+
#endif
288328
}
289329

290330
/// Clear all exception flags
@@ -295,9 +335,13 @@ extension IEEE_754.Exceptions {
295335
/// ```swift
296336
/// IEEE_754.Exceptions.clearAll()
297337
/// ```
298-
@inlinable
299338
public static func clearAll() {
300339
sharedState.clearAll()
340+
341+
#if canImport(CIEEE754)
342+
// Also clear C thread-local storage
343+
ieee754_clear_all_exceptions()
344+
#endif
301345
}
302346

303347
/// Test if any exception is raised
@@ -390,4 +434,85 @@ extension IEEE_754.Exceptions {
390434
public static var inexact: Bool {
391435
testFlag(.inexact)
392436
}
437+
438+
// MARK: - Hardware FPU Exception Detection
439+
440+
#if canImport(CIEEE754)
441+
/// Hardware FPU exception state
442+
///
443+
/// Represents the current hardware floating-point unit exception flags.
444+
/// These are detected from the actual FPU status register.
445+
///
446+
/// ## Overview
447+
///
448+
/// Unlike the thread-local software exception flags, FPU exceptions
449+
/// represent the actual hardware state. These flags are set automatically
450+
/// by the CPU during floating-point operations.
451+
///
452+
/// ## Example
453+
///
454+
/// ```swift
455+
/// IEEE_754.Exceptions.clearFPU()
456+
/// _ = 1.0 / 0.0 // Raises hardware division by zero
457+
///
458+
/// let fpuState = IEEE_754.Exceptions.testFPU()
459+
/// print(fpuState.divisionByZero) // true
460+
/// ```
461+
public struct FPUState: Sendable, Equatable {
462+
public let invalid: Bool
463+
public let divisionByZero: Bool
464+
public let overflow: Bool
465+
public let underflow: Bool
466+
public let inexact: Bool
467+
468+
internal init(cState: IEEE754Exceptions) {
469+
self.invalid = cState.invalid != 0
470+
self.divisionByZero = cState.divByZero != 0
471+
self.overflow = cState.overflow != 0
472+
self.underflow = cState.underflow != 0
473+
self.inexact = cState.inexact != 0
474+
}
475+
}
476+
477+
/// Test hardware FPU exception flags
478+
///
479+
/// Queries the hardware floating-point unit for exception flags.
480+
///
481+
/// - Returns: Current FPU exception state
482+
///
483+
/// ## Example
484+
///
485+
/// ```swift
486+
/// let fpuState = IEEE_754.Exceptions.testFPU()
487+
/// if fpuState.overflow {
488+
/// print("FPU overflow detected")
489+
/// }
490+
/// ```
491+
///
492+
/// ## See Also
493+
///
494+
/// - `clearFPU()`: Clear hardware FPU exception flags
495+
public static func testFPU() -> FPUState {
496+
let cState = ieee754_test_fpu_exceptions()
497+
return FPUState(cState: cState)
498+
}
499+
500+
/// Clear hardware FPU exception flags
501+
///
502+
/// Clears all exception flags in the hardware floating-point unit.
503+
///
504+
/// ## Example
505+
///
506+
/// ```swift
507+
/// IEEE_754.Exceptions.clearFPU()
508+
/// // All hardware FPU exception flags are now clear
509+
/// ```
510+
///
511+
/// ## See Also
512+
///
513+
/// - `testFPU()`: Query hardware FPU exception flags
514+
public static func clearFPU() {
515+
ieee754_clear_fpu_exceptions()
516+
}
517+
#endif
393518
}

0 commit comments

Comments
 (0)