Skip to content

Latest commit

 

History

History
21 lines (12 loc) · 3.5 KB

DESIGN_RATIONALE.md

File metadata and controls

21 lines (12 loc) · 3.5 KB

Design Rationale

Design Goals

The num-quaternion crate is created with the following design goals in mind:

  1. Run-time efficiency: The processor spends a minimal amount of time for a computation to finish. The implementation of num-quaternion minimizes both the latency and the reciprocal throughput of computations.
  2. Numerical accuracy: The functions in num-quaternion mostly have a maximum relative error of only a few ulps (units in the last place) or floating point epsilons. Accuracy is limited by the accuracy of the input data and the underlying transcendental function implementation.
  3. Correctness for all inputs: The crate guarantees intuitive and correct behavior in edge cases. The implementation generally checks for edge cases with a minimal number of conditions and branches, always optimizing the general case. With reasonable branch prediction, the checking overhead should generally be negligible.
  4. Intuitive interface: The crate provides an interface that is easy to understand and use. Quaternions can be used just like built-in floating point numbers or the complex numbers from the num-complex crate.

Error Handling & IEEE-754 Floating Point Values

TLDR: Use is_finite() to check for the success of floating point operations in num-quaternion functions. Don't use signaling NaNs.

Quaternions are normally composed of f32 or f64 values. Errors are reported by infinite and NaN return values. NaN values are infectious, meaning that if NaN values are fed into a function, then NaN values are returned accordingly. This ensures correct error propagation. Computations can be checked for success by using the is_finite() method on the final result, which ensures that neither infinite nor NaN values are present. The details of error conditions are documented for each function individually.

Client code should not rely on the floating point status flags for error handling with num-quaternion. The floating point environment is essentially global state with all the nasty problems that entails. Providing additional guarantees for the floating point flags would require additional computational costs, which are completely unnecessary because reporting errors through infinities and NaNs is already sufficient. Apart from that, Rust does not meaningfully support floating point exceptions.

The num-quaternion crate assumes that there are no signaling NaN inputs to functions. Let's discuss the background for why we make this assumption. The IEEE-754 standard on floating point numbers allows for signaling NaNs which cause signals to be raised when used in arithmetic operations. IEEE also guarantees that the result of any computation never creates any signaling NaN value, even if the inputs are signaling. There is some discussion on whether Rust should completely comply with this requirement, because it may prevent some optimizations: Replacing the expression x * 1.0 by x would not be valid if x is a signaling NaN value, since the output needs to be a quiet NaN value. Therefore, the assumption that there are no signaling NaN values allows certain optimizations. On the flip side, the assumption that there are no signaling NaN in any input is very strong, because signaling NaNs can only be produced by explicitly calling from_bits or some equivalent unsafe operation. Feel free to read more details about Rust and IEEE-754 floating point values here.