float_next_after-1.0.0/.cargo_vcs_info.json0000644000000001360000000000100143560ustar { "git": { "sha1": "6f555d52667acda6237f88588a3d1f4e8a840c7e" }, "path_in_vcs": "" }float_next_after-1.0.0/.gitignore000064400000000000000000000005540072674642500151720ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk # Testing *.profraw # IDE's .idea .vscode float_next_after-1.0.0/.gitlab-ci.yml000064400000000000000000000026050072674642500156350ustar 00000000000000image: "rust:latest" default: before_script: - rustc --version - cargo --version stages: - test - formatting - security - report test-code: stage: test script: - cargo test --verbose lint-code: stage: formatting script: - rustup component add rustfmt - cargo fmt -- --check - rustup component add clippy - cargo clippy -- -D warnings audit-code: stage: security script: - cargo install cargo-audit - cargo audit coverage: image: "rustdocker/rust:nightly" stage: report variables: CARGO_INCREMENTAL: "0" RUSTFLAGS: "-Cinstrument-coverage" LLVM_PROFILE_FILE: "cargo-test-%p-%m.profraw" script: - rustup component add llvm-tools-preview - apt-get update && apt-get install -y python3 python3-pip - cargo test # generate html report - cargo install grcov - mkdir -p coverage/html - grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o coverage/html - grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o coverage/tests.lcov # output coverage summary for gitlab parsing - apt-get update && apt-get install -y lcov - lcov --summary coverage/tests.lcov - curl -Os https://uploader.codecov.io/latest/linux/codecov - chmod +x codecov - ./codecovfloat_next_after-1.0.0/CHANGES.md000064400000000000000000000023730072674642500145750ustar 00000000000000# Rust Float NextAfter Release Notes A native Rust next after float function, which is provided as a trait for f32 and f64 types. It steps to the next representable floating point, even if it is subnormal. See [`README.md`](./README.md) for usage instructions. ## Known issues ## Changes ### Version 1.0.0 Breaking changes: - The NextAfter trait is now provided for the types `f32` and `f64` and no longer provided as a generic constrained to the type `num_traits::Float`. - The library is now `#![no_std]`. New Features: - Removed dependence on the std library, the library is now `#![no_std]`. - Now uses macros to create the trait for `f32` and `f64` directly without any use of the generic contraint using `num_traits::Float`. - CI/CD test running on git push. Structural changes: - Tests are written with a macro now so no code needs to be repeated to run tests for `f32` and `f64`. - Code cleanup with: - 0 equality check integrated into `short_circuit_operands`. - Early return is used throughout. - Single check for infinite source number (it had been a separate check for positive and for negative infinity). Performance: - Removed one unneccesary creation of a stack variable. ### Version 0.7.0 Bug fixes: - Updated the examples in `README.md` float_next_after-1.0.0/Cargo.toml0000644000000021360000000000100123560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "float_next_after" version = "1.0.0" authors = ["Bronson Brown-deVost "] description = "A trait for native rust f64/f32 nextafter" homepage = "https://gitlab.com/bronsonbdevost/next_afterf" readme = "README.md" keywords = [ "float", "f64", "f32", "nextafter", "increment", ] categories = [ "mathematics", "algorithms", ] license = "MIT" repository = "https://gitlab.com/bronsonbdevost/next_afterf" [[bench]] name = "bench" harness = false [dev-dependencies.criterion] version = "0.4.0" features = ["html_reports"] [dev-dependencies.fastrand] version = "1.8.0" float_next_after-1.0.0/Cargo.toml.orig000064400000000000000000000011240072674642500160630ustar 00000000000000[package] name = "float_next_after" version = "1.0.0" authors = ["Bronson Brown-deVost "] description = "A trait for native rust f64/f32 nextafter" edition = "2018" license = "MIT" readme = "README.md" homepage = "https://gitlab.com/bronsonbdevost/next_afterf" repository = "https://gitlab.com/bronsonbdevost/next_afterf" keywords = ["float", "f64", "f32", "nextafter", "increment"] categories = ["mathematics", "algorithms"] [[bench]] name = "bench" harness = false [dev-dependencies] criterion = { version = "0.4.0", features = ["html_reports"] } fastrand = "1.8.0" float_next_after-1.0.0/LICENSE000064400000000000000000000021370072674642500142060ustar 00000000000000MIT License Copyright (c) 2020 Scripta Qumranica Electronica Created by Bronson Brown-deVost Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. float_next_after-1.0.0/README.md000064400000000000000000000044600072674642500144610ustar 00000000000000# float-next-after [![codecov](https://codecov.io/gl/bronsonbdevost/next_afterf/branch/main/graph/badge.svg?token=CK1N3IYKFG)](https://codecov.io/gl/bronsonbdevost/next_afterf) [![Documentation](https://docs.rs/float_next_after/badge.svg)](https://docs.rs/float_next_after) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitlab.com/bronsonbdevost/next_afterf/-/blob/main/LICENSE) [![Crates.io](https://img.shields.io/crates/v/float_next_after)](https://crates.io/crates/float_next_after) [![Crates.io](https://img.shields.io/crates/d/float_next_after)](https://crates.io/crates/float_next_after) A native Rust next after float function, which is provided as a trait for f32 and f64 types. It steps to the next representable floating point, even if it is subnormal. If a subnormal is undesired, users should handle that eventuality themselves. See [`CHANGES.md`](./CHANGES.md) for the versioned changelog. In specific edge cases the following decisions have been made: * self == y -> return y * self >= positive infinity -> return positive infinity * self <= negative infinity -> return negative infinity * self or y == NaN -> return NaN * self = -0.0 and y = 0.0 -> return positive 0.0 * self == -0.0 and y == positive infinity -> 5e-324 Please see the unit tests for the actual behavior in various other exceptional cases. This code uses the ToBits and FromBits functions from f32 and f64. Those both simply wrap `unsafe { mem::transmute(self) }` / `unsafe { mem::transmute(v) }` to convert a f32/f64 to u32/u64. The docs for those functions, however, claim that they are safe and that "the safety issues with sNaN were overblown!" PR's and other helpful input are welcome. ## Usage ```rust use float_next_after::NextAfter; // Large numbers let big_num = 16237485966.00000437586943_f64; let next = big_num.next_after(std::f64::INFINITY); assert_eq!(next, 16237485966.000006_f64); // Expected handling of 1.0 let one = 1_f64; let next = one.next_after(std::f64::INFINITY); assert_eq!(next, 1_f64 + std::f64::EPSILON); // Tiny (negative) numbers let zero = 0_f32; let next = zero.next_after(std::f32::NEG_INFINITY); assert_eq!(next, -0.000000000000000000000000000000000000000000001_f32); // Equal source/dest (even -0 == 0) let zero = 0_f64; let next = zero.next_after(-0_f64); assert_eq!(next, -0_f64); ``` float_next_after-1.0.0/benches/bench.rs000064400000000000000000000022450072674642500162350ustar 00000000000000use criterion::{ black_box, criterion_group, criterion_main, measurement::Measurement, BatchSize, BenchmarkGroup, Criterion, }; use fastrand::Rng; use float_next_after::NextAfter; fn get_rng() -> Rng { Rng::with_seed(0) } fn run( mut group: BenchmarkGroup, get: impl Fn(&Rng) -> T, up: T, down: T, ) { let mut run_one_way = |name, to| { let rng = get_rng(); group.bench_function(name, |b| { b.iter_batched( || get(&rng), |f| black_box(f).next_after(to), BatchSize::SmallInput, ) }); }; run_one_way("up", up); run_one_way("down", down); } fn f32(c: &mut Criterion) { let group = c.benchmark_group("f32"); let get_f32 = |rng: &Rng| f32::from_bits(rng.u32(0..=u32::MAX)); run(group, get_f32, f32::INFINITY, f32::NEG_INFINITY); } fn f64(c: &mut Criterion) { let group = c.benchmark_group("f64"); let get_f64 = |rng: &Rng| f64::from_bits(rng.u64(0..=u64::MAX)); run(group, get_f64, f64::INFINITY, f64::NEG_INFINITY); } criterion_group!(benches, f32, f64); criterion_main!(benches); float_next_after-1.0.0/src/lib.rs000064400000000000000000000303370072674642500151070ustar 00000000000000#![no_std] /// Returns the next representable float value in the direction of y /// /// This function is strict and will step to the very next representable floating point, /// even if that value is subnormal. /// /// Base assumptions: /// /// self == y -> return y /// /// self >= positive infinity -> return positive infinity /// /// self <= negative infinity -> return negative infinity /// /// self == NaN -> return NaN /// /// self == -0.0 and y == 0.0 -> return positive 0.0 /// /// self == -0.0 and y == positive infinity -> 5e-324 /// /// # Examples /// /// ``` /// use float_next_after::NextAfter; /// /// // Large numbers /// let big_num = 16237485966.00000437586943_f64; /// let next = big_num.next_after(std::f64::INFINITY); /// assert_eq!(next, 16237485966.000006_f64); /// /// // Expected handling of 1.0 /// let one = 1_f64; /// let next = one.next_after(std::f64::INFINITY); /// assert_eq!(next, 1_f64 + std::f64::EPSILON); /// /// // Tiny (negative) numbers /// let zero = 0_f32; /// let next = zero.next_after(std::f32::NEG_INFINITY); /// assert_eq!(next, -0.000000000000000000000000000000000000000000001_f32); /// /// // Equal source/dest (even -0 == 0) /// let zero = 0_f64; /// let next = zero.next_after(-0_f64); /// assert_eq!(next, -0_f64); /// ``` /// /// # Safety /// /// This trait uses the ToBits and FromBits functions from f32 and f64. /// Those both use unsafe { mem::transmute(self) } / unsafe { mem::transmute(v) } /// to convert a f32/f64 to u32/u64. The docs for those functions claim they are safe /// and that "the safety issues with sNaN were overblown!" /// pub trait NextAfter { #[must_use] fn next_after(self, y: Self) -> Self; } macro_rules! impl_next_after { ($float_type:ty, $module:ident, $minimum_non_zero:literal) => { use super::NextAfter; impl NextAfter for $float_type { fn next_after(self, y: Self) -> Self { // Check first if the input matches a fixed set of values // that receive a pre-calculated response in line with the // stated rules. if let Some(out) = short_circuit_operands(self, y) { return out; } // To get the next float, use trick fXX -> uXX +- 1 -> fXX // increment or decrement depending on same sign. let return_value = if (y > self) == (self > 0.0) { <$float_type>::from_bits(self.to_bits() + 1) } else { <$float_type>::from_bits(self.to_bits() - 1) }; // Note that following the IEEE standard both 0.0 and -0.0 are equal in rust if return_value != 0.0 { return return_value; } // At this point the return number must be zero // so make sure it returns with the input's original sign. copy_sign(return_value, self) } } #[inline] fn short_circuit_operands(x: $float_type, y: $float_type) -> Option<$float_type> { // If x and y are equal (also -0.0 == 0.0 in the IEEE standard and rust), // there is nothing further to do if y == x { // The policy is to return y in cases of equality, // this only matters when x = 0.0 and y = -0.0 (or the reverse) return Some(y); } // If x or y is NaN return NaN if x.is_nan() || y.is_nan() { return Some(core::$module::NAN); } if x.is_infinite() { return Some(x); } // If x is (+/-)0 and y is not 0 (see first condition), // return the hard coded value closest to zero and use // positive/negative sign to move it in the proper direction if x == 0.0 { return Some(copy_sign($minimum_non_zero, y)); } None } #[inline] fn copy_sign(x: $float_type, y: $float_type) -> $float_type { if x.is_sign_positive() == y.is_sign_positive() { x } else { -x } } }; } macro_rules! tests { ($float_type:ty, $module:ident, $smallest_pos:expr, $largest_neg:expr, $next_before_one:expr, $sequential_large_numbers:expr) => { #[cfg(test)] mod tests { use super::{copy_sign, NextAfter}; const POS_INFINITY: $float_type = core::$module::INFINITY; const NEG_INFINITY: $float_type = core::$module::NEG_INFINITY; const POS_ZERO: $float_type = 0.0; const NEG_ZERO: $float_type = -0.0; // Note: Not the same as fXX::MIN_POSITIVE, because that is only the min *normal* number. const SMALLEST_POS: $float_type = $smallest_pos; const LARGEST_NEG: $float_type = $largest_neg; const LARGEST_POS: $float_type = core::$module::MAX; const SMALLEST_NEG: $float_type = core::$module::MIN; const POS_ONE: $float_type = 1.0; const NEG_ONE: $float_type = -1.0; const NEXT_LARGER_THAN_ONE: $float_type = 1.0 + core::$module::EPSILON; const NEXT_SMALLER_THAN_ONE: $float_type = $next_before_one; const SEQUENTIAL_LARGE_NUMBERS: ($float_type, $float_type) = $sequential_large_numbers; const NAN: $float_type = core::$module::NAN; fn is_positive_zero(x: $float_type) -> bool { x.to_bits() == POS_ZERO.to_bits() } fn is_negative_zero(x: $float_type) -> bool { x.to_bits() == NEG_ZERO.to_bits() } #[test] fn test_copy_sign() { assert_eq!(copy_sign(POS_ONE, POS_ZERO), POS_ONE); assert_eq!(copy_sign(NEG_ONE, POS_ZERO), POS_ONE); assert_eq!(copy_sign(POS_ONE, NEG_ZERO), NEG_ONE); assert_eq!(copy_sign(NEG_ONE, NEG_ZERO), NEG_ONE); } #[test] fn verify_constants() { assert_ne!(POS_ZERO.to_bits(), NEG_ZERO.to_bits()); assert!(SMALLEST_POS > POS_ZERO); assert!(LARGEST_NEG < NEG_ZERO); assert!(!SMALLEST_POS.is_normal()); assert!(!LARGEST_NEG.is_normal()); } #[test] fn next_larger_than_0() { assert_eq!(POS_ZERO.next_after(POS_INFINITY), SMALLEST_POS); assert_eq!(NEG_ZERO.next_after(POS_INFINITY), SMALLEST_POS); } #[test] fn next_smaller_than_0() { assert_eq!(POS_ZERO.next_after(NEG_INFINITY), LARGEST_NEG); assert_eq!(NEG_ZERO.next_after(NEG_INFINITY), LARGEST_NEG); } #[test] fn step_towards_zero() { // For steps towards zero, the sign of the zero reflects the direction // from where zero was approached. assert!(is_positive_zero(SMALLEST_POS.next_after(POS_ZERO))); assert!(is_positive_zero(SMALLEST_POS.next_after(NEG_ZERO))); assert!(is_positive_zero(SMALLEST_POS.next_after(NEG_INFINITY))); assert!(is_negative_zero(LARGEST_NEG.next_after(NEG_ZERO))); assert!(is_negative_zero(LARGEST_NEG.next_after(POS_ZERO))); assert!(is_negative_zero(LARGEST_NEG.next_after(POS_INFINITY))); } #[test] fn special_case_signed_zeros() { // For a non-zero dest, stepping away from either POS_ZERO or NEG_ZERO // has a non-zero result. Only if the destination itself points to the // "other zero", the next_after call performs a zero sign switch. assert!(is_negative_zero(POS_ZERO.next_after(NEG_ZERO))); assert!(is_positive_zero(NEG_ZERO.next_after(POS_ZERO))); } #[test] fn nextafter_around_one() { assert_eq!(POS_ONE.next_after(POS_INFINITY), NEXT_LARGER_THAN_ONE); assert_eq!(POS_ONE.next_after(NEG_INFINITY), NEXT_SMALLER_THAN_ONE); assert_eq!(NEG_ONE.next_after(NEG_INFINITY), -NEXT_LARGER_THAN_ONE); assert_eq!(NEG_ONE.next_after(POS_INFINITY), -NEXT_SMALLER_THAN_ONE); } #[test] fn nextafter_for_big_positive_number() { let (lo, hi) = SEQUENTIAL_LARGE_NUMBERS; assert_eq!(lo.next_after(POS_INFINITY), hi); assert_eq!(hi.next_after(NEG_INFINITY), lo); assert_eq!(lo.next_after(hi), hi); assert_eq!(hi.next_after(lo), lo); } #[test] fn nextafter_for_small_negative_number() { let (lo, hi) = SEQUENTIAL_LARGE_NUMBERS; let (lo, hi) = (-lo, -hi); assert_eq!(lo.next_after(NEG_INFINITY), hi); assert_eq!(hi.next_after(POS_INFINITY), lo); assert_eq!(lo.next_after(hi), hi); assert_eq!(hi.next_after(lo), lo); } #[test] fn step_to_largest_is_possible() { let smaller = LARGEST_POS.next_after(NEG_INFINITY); assert_eq!(smaller.next_after(POS_INFINITY), LARGEST_POS); let smaller = SMALLEST_NEG.next_after(POS_INFINITY); assert_eq!(smaller.next_after(NEG_INFINITY), SMALLEST_NEG); } #[test] fn jump_to_infinity() { // Incrementing the max representable number has to go to infinity. assert_eq!(LARGEST_POS.next_after(POS_INFINITY), POS_INFINITY); assert_eq!(SMALLEST_NEG.next_after(NEG_INFINITY), NEG_INFINITY); } #[test] fn stays_at_infinity() { // Once infinity is reached, there is not going back to normal numbers assert_eq!(POS_INFINITY.next_after(NEG_INFINITY), POS_INFINITY); assert_eq!(NEG_INFINITY.next_after(POS_INFINITY), NEG_INFINITY); } #[test] fn returns_nan_for_any_nan_involved() { assert!(NAN.next_after(POS_ONE).is_nan()); assert!(POS_ONE.next_after(NAN).is_nan()); assert!(NAN.next_after(NAN).is_nan()); } #[test] fn returns_identity_for_equal_dest() { let values = [ POS_ZERO, NEG_ZERO, POS_ONE, NEG_ONE, SEQUENTIAL_LARGE_NUMBERS.0, SEQUENTIAL_LARGE_NUMBERS.1, POS_INFINITY, NEG_INFINITY, SMALLEST_POS, LARGEST_NEG, LARGEST_POS, SMALLEST_NEG, ]; for x in values.iter() { assert_eq!(x.next_after(*x), *x); } } #[test] fn can_successfully_roundtrip() { let values = [ POS_ONE, NEG_ONE, SEQUENTIAL_LARGE_NUMBERS.0, SEQUENTIAL_LARGE_NUMBERS.1, SMALLEST_POS, LARGEST_NEG, ]; for orig in values.to_vec() { assert_eq!(orig.next_after(POS_INFINITY).next_after(NEG_INFINITY), orig); assert_eq!(orig.next_after(NEG_INFINITY).next_after(POS_INFINITY), orig); let upper = orig.next_after(POS_INFINITY); let lower = orig.next_after(NEG_INFINITY); assert_eq!(orig.next_after(upper).next_after(lower), orig); assert_eq!(orig.next_after(lower).next_after(upper), orig); } } } }; } mod f64 { impl_next_after!(f64, f64, 5e-324); tests!( f64, f64, 5e-324, -5e-324, 0.999_999_999_999_999_9, (16_237_485_966.000_004, 16_237_485_966.000_006) ); } mod f32 { impl_next_after!(f32, f32, 1e-45); tests!( f32, f32, 1e-45, -1e-45, 0.999_999_94, (1.230_000_1e34, 1.230_000_3e34) ); }