Make predicate implementations generic with respect to any suitable floating point type #38
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR proposes making the implementation of the floating point predicate generic with respect to the floating-point type.
Motivation
Interest in an f128-implementation was shown in #29 (comment) . The current f64 implementation cannot solve predicates robustly for f128 and with this PR, f128 is supported when enabled (since the stable tool chain complains about it and I don't know how to make the stable tool chain just ignore it in Rust, it is currently commented out). While using f64 can evaluate predicates robustly for f32, there are platforms on which f32 ops are cheaper than f64, so having computation in f32 as an option could also be an improvement. It would work for f16 too but that may be a dubious choice for geometry due to low exponent range, so no test cases were included for that type. It will work for any other float type as well that satisifes the requirement and implements the newly added trait in this PR, which is a single line with the given macro.
Changes
Adds Ieee754Float trait with an implementation macro, to generate the necessary constants for any float type that has ::EPSILON (for error bound computations), ::MANTISSA_DIGITS (for splitter computations), and the necesary arithmetic ops. Adds *Generic versions of Coord/Coord32 and _generic versions of orient2d, orient3d, incircle and insphere that require the new trait. Changes all other methods (which I consider implementation details that should generally not be called directly imho) to generic code that requires the new trait. Adds orient2d tests for f32 and f128 (commented out to avoid compiler error with the stabled tool chain).
Design Choices
The function signatures of orient2d, orient3d, incircle and insphere, which should be the main API, remain unchanged, so that the default is converting to f64 and computing with that type. The actual computation is delegated to the _generic versions of those functions, which can also also be called with other types that implement the new trait. This choice is proposed because f64 is a good default that will work for f64, f32, f16, u8, ..., i8, up to i32, is good (not optimal, though) for those integer types, and mitigate potential range limitations of f32 and especially f16.
For public functions like *adapt or *exact, this PR changes the arguments from Into to requiring the new trait. I can change it, if that is undesirable.
Background
The original C implementation is generic by allowing
REALto be defined asfloatordouble. The current Rust implementation is forf64only.The underlying theory of the algorithms used requires at least 6 mantissa digits, round-to-nearest, and tie-break to even default rounding mode, as specified in IEEE754. This is satisfied by f16, f32, f64, f128 and 80-bit extended precision on x86 (there is no f80 in rust, though). It implicitly requires that the values are in such that no overflows or denormalisations occur, which requires for a d-th degree predicate that the absolute values of the coordinates are zero or roughly within the d-th root of the minimum and maximum positive values of the floating point type. d is 2, 4, 3, and 5 for orient2d, incircle, orient3d and insphere respectively, so this allows values up to ~10^19 for f32 for orient2d, up to 10^7 for insphere, etc., so this is reasonable for f32 and upwards.