rust ffast-math (defunct, use lower)
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/ffloat_safety.md | 5 | ||||
| -rw-r--r-- | src/ffloat_safety_noconstr.md | 4 | ||||
| -rw-r--r-- | src/ffloat_safety_notice.md | 3 | ||||
| -rw-r--r-- | src/generic_float.rs | 336 | ||||
| -rw-r--r-- | src/lib.rs | 52 | ||||
| -rw-r--r-- | src/refer.md | 4 |
7 files changed, 397 insertions, 9 deletions
@@ -1,6 +1,6 @@ [package] name = "umath" -version = "0.0.1" +version = "0.0.3" description = "ffast-math in rust" license = "MIT" repository = "https://github.com/bend-n/umath" diff --git a/src/ffloat_safety.md b/src/ffloat_safety.md new file mode 100644 index 0000000..6e5d3bc --- /dev/null +++ b/src/ffloat_safety.md @@ -0,0 +1,5 @@ +# Safety + +- You MUST NEVER call this function with [`NAN`] | [`INF`] +- You MUST NEVER make the produced [`FFloat`] [`NAN`] | [`INF`] +- You MUST NEVER combine this or any [`FFloat`] with any other {[`FFloat`], [`f32`], [`f64`]}, if it will produce [`NAN`] | [`INF`]
\ No newline at end of file diff --git a/src/ffloat_safety_noconstr.md b/src/ffloat_safety_noconstr.md new file mode 100644 index 0000000..cec988e --- /dev/null +++ b/src/ffloat_safety_noconstr.md @@ -0,0 +1,4 @@ +# Safety + +- You MUST NEVER turn the produced [`FFloat`] into [`NAN`] | [`INF`] +- You MUST NEVER combine this [`FFloat`] with another {[`FFloat`], [`f32`], [`f64`]}, to produce a [`NAN`] | [`INF`]
\ No newline at end of file diff --git a/src/ffloat_safety_notice.md b/src/ffloat_safety_notice.md new file mode 100644 index 0000000..6170d88 --- /dev/null +++ b/src/ffloat_safety_notice.md @@ -0,0 +1,3 @@ +Please note that calling this function may (although its dependent on the function) incur UB. + +These functions are not marked `unsafe`, as the entire [`FFloat`] type is essentially unsafe.
\ No newline at end of file diff --git a/src/generic_float.rs b/src/generic_float.rs new file mode 100644 index 0000000..752441c --- /dev/null +++ b/src/generic_float.rs @@ -0,0 +1,336 @@ +//! provides generic float traits. +//! this is the best way to make a function possibly take a [`FFloat`]. +//! ``` +//! # use umath::*; +//! /// this function can take anything that implements Float, and "works with" a f32: it can be added to a f32, it can be created from a f32, etc. +//! /// with no external implementations, this can take either f32 or FFloat<f32>. +//! fn takes_float<F: Float<f32>>(f: F) {} +//! ``` +use crate::{FFloat, FastFloat}; +use core::ops::{ + Add, AddAssign, Deref, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, +}; +#[cfg(doc)] +use std::f32::{INFINITY as INF, NAN}; + +macro_rules! simp { + ($doc:literal trait $trat:ident with $($name:ident),+) => { + #[doc = $doc] + pub trait $trat { + $( + #[doc = concat!("Refer to [`f32::", stringify!($name), "`]")] + fn $name(self) -> Self; + )+ + } + + impl $trat for f32 { $(fn $name(self) -> Self { self.$name() })+ } + impl $trat for f64 { $(fn $name(self) -> Self { self.$name() })+ } + impl<T: FastFloat + Trig + Rounding> $trat for FFloat<T> { $(fn $name(self) -> Self { unsafe { FFloat::new(self.deref().$name()) } })+ } + }; +} + +simp!["Trigonometry functions" trait Trig with sin, asin, sinh, asinh, cos, acos, cosh, acosh, tan, atan, tanh, atanh]; +simp!["Rounding functions" trait Rounding with floor, ceil, round]; + +macro_rules! ctor { + ($for:ty) => { + impl Constructors for $for { + /// Returns 0. This function is safe to call. + unsafe fn zero() -> $for { + 0.0 + } + + /// Returns 1. This function is safe to call. + unsafe fn one() -> $for { + 1.0 + } + + #[doc = concat!("Returns [`", stringify!($for), "::MIN`]. This function is safe to call")] + unsafe fn min() -> $for { + <$for>::MIN + } + + #[doc = concat!("Returns [`", stringify!($for), "::MAX`]. This function is safe to call")] + unsafe fn max() -> $for { + <$for>::MAX + } + } + }; +} + +ctor!(f32); +ctor!(f64); + +/// Float constructors. +pub trait Constructors { + /// Returns 0. + #[doc = include_str!("refer.md")] + unsafe fn zero() -> Self; + + /// Returns 1. + #[doc = include_str!("refer.md")] + unsafe fn one() -> Self; + + /// Returns the minimum value for this float. + #[doc = include_str!("refer.md")] + unsafe fn min() -> Self; + + /// Returns the maximum value for this float. + #[doc = include_str!("refer.md")] + unsafe fn max() -> Self; +} + +/// Generic float trait, implemented by {[`FFloat`], [`f32`], [`f64`]}. +/// The main purpose of this is to be taken (generically) by optionally fast functions. +/// +/// If there is a method you would like to see on this trait, please open a issue. +/// +/// Do note that the implementations of these functions are provided by std. +/// These functions are not likely to be faster than the std counterparts, unless the implementation is software provided and can benefit from fast math. +/// +/// # Safety +/// +/// Please note that calling these functions on a [`FFloat`] _may_ incur UB. +/// These functions are not marked `unsafe`, as the entire [`FFloat`] type is essentially unsafe. +/// Calling these functions on a [`f32`] is perfectly safe, even the `unsafe` marked functions (although theres not much point in doing so). +pub trait Float<F>: + PartialEq + + PartialOrd + + PartialOrd<F> + + Copy + + Trig + + Rounding + + Constructors + + Add<Self, Output = Self> + + Add<F, Output = Self> + + Sub<Self, Output = Self> + + Sub<F, Output = Self> + + Mul<Self, Output = Self> + + Mul<F, Output = Self> + + Rem<Self, Output = Self> + + Rem<F, Output = Self> + + Div<Self, Output = Self> + + Neg<Output = Self> + + Div<F, Output = Self> + + AddAssign<Self> + + AddAssign<F> + + SubAssign<Self> + + SubAssign<F> + + MulAssign<Self> + + MulAssign<F> + + DivAssign<Self> + + DivAssign<F> + + RemAssign<Self> + + RemAssign<F> +where + Self: Sized, +{ + /// Returns a new [`Self`] from the float. + #[doc = include_str!("refer.md")] + unsafe fn new(from: F) -> Self; + + /// Returns this float + fn take(self) -> F; + + /// Refer to [`f32::trunc`] + fn trunc(self) -> Self; + + /// Refer to [`f32::fract`] + fn fract(self) -> Self; + + /// Refer to [`f32::abs`] + fn abs(self) -> Self; + + /// Refer to [`f32::powi`] + fn powi(self, n: i32) -> Self; + + /// Refer to [`f32::powf`] + fn powf(self, n: Self) -> Self; + + /// Refer to [`f32::sqrt`] + fn sqrt(self) -> Self; + + /// Refer to [`f32::cbrt`] + fn cbrt(self) -> Self; + + /// Refer to [`f32::hypot`] + fn hypot(self, other: Self) -> Self; + + /// Refer to [`f32::exp2`] + fn exp2(self) -> Self; + + /// Refer to [`f32::ln`] + fn ln(self) -> Self; + + /// Refer to [`f32::log`] + fn log(self, base: Self) -> Self; + + /// Refer to [`f32::min`] + fn min(self, other: Self) -> Self; + + /// Refer to [`f32::max`] + fn max(self, other: Self) -> Self; +} + +macro_rules! impf { + ($for:ty) => { + impl Float<$for> for $for { + /// Returns the input value. This function is safe to call. + unsafe fn new(from: $for) -> $for { + from + } + fn take(self) -> $for { + self + } + fn trunc(self) -> $for { + self.trunc() + } + fn fract(self) -> $for { + self.fract() + } + fn abs(self) -> $for { + self.abs() + } + fn powi(self, n: i32) -> $for { + self.powi(n) + } + fn powf(self, n: $for) -> $for { + self.powf(n) + } + fn sqrt(self) -> $for { + self.sqrt() + } + fn cbrt(self) -> $for { + self.cbrt() + } + fn hypot(self, other: Self) -> $for { + self.hypot(other) + } + fn exp2(self) -> $for { + self.exp2() + } + fn ln(self) -> $for { + self.ln() + } + fn log(self, base: Self) -> Self { + self.log(base) + } + fn min(self, other: Self) -> Self { + self.min(other) + } + fn max(self, other: Self) -> Self { + self.max(other) + } + } + }; +} + +impf!(f32); +impf!(f64); + +impl<F: FastFloat + Constructors> Constructors for FFloat<F> { + /// Create a new [`FFloat`] representing `0.0`. + #[doc = include_str!("ffloat_safety_noconstr.md")] + unsafe fn zero() -> Self { + Self::new(F::zero()) + } + /// Create a new [`FFloat`] representing `1.0`. + #[doc = include_str!("ffloat_safety_noconstr.md")] + unsafe fn one() -> Self { + Self::new(F::one()) + } + /// Create a new [`FFloat`] representing the minimum value for the inner float.. + #[doc = include_str!("ffloat_safety_noconstr.md")] + unsafe fn min() -> Self { + Self::new(F::min()) + } + /// Create a new [`FFloat`] representing the maximum value for the inner float.. + #[doc = include_str!("ffloat_safety_noconstr.md")] + unsafe fn max() -> Self { + Self::new(F::max()) + } +} + +macro_rules! reuse { + (fn $name:ident) => { + #[doc = concat!("Refer to [`f32::", stringify!($name), "`]")] + #[doc = include_str!("ffloat_safety_notice.md")] + fn $name(self) -> Self { + self.check(); + unsafe { Self::new(self.0.$name()) } + } + }; +} + +impl<F: FastFloat + Float<F>> Float<F> for FFloat<F> { + /// Create a new [`FFloat`] from your {[`f32`], [`f64`]} + #[doc = include_str!("ffloat_safety.md")] + unsafe fn new(from: F) -> Self { + Self::new(from) + } + + fn take(self) -> F { + self.0 + } + + reuse!(fn trunc); + reuse!(fn fract); + reuse!(fn abs); + + /// Refer to [`f32::powi`] + #[doc = include_str!("ffloat_safety_notice.md")] + fn powi(self, n: i32) -> Self { + unsafe { Self::new(self.0.powi(n)) } + } + + /// Refer to [`f32::powf`] + #[doc = include_str!("ffloat_safety_notice.md")] + fn powf(self, n: Self) -> Self { + self.check(); + unsafe { Self::new(self.0.powf(*n)) } + } + + reuse!(fn sqrt); + reuse!(fn cbrt); + /// Refer to [`f32::hypot`] + #[doc = include_str!("ffloat_safety_notice.md")] + fn hypot(self, other: Self) -> Self { + self.check(); + unsafe { Self::new(self.0.hypot(*other)) } + } + reuse!(fn exp2); + reuse!(fn ln); + + /// Refer to [`f32::log`] + #[doc = include_str!("ffloat_safety_notice.md")] + fn log(self, base: Self) -> Self { + self.check(); + unsafe { Self::new(self.0.log(*base)) } + } + + /// Refer to [`f32::min`] + #[doc = include_str!("ffloat_safety_notice.md")] + fn min(self, other: Self) -> Self { + self.check(); + unsafe { Self::new(self.0.min(*other)) } + } + + /// Refer to [`f32::max`] + #[doc = include_str!("ffloat_safety_notice.md")] + fn max(self, other: Self) -> Self { + self.check(); + unsafe { Self::new(self.0.max(*other)) } + } +} + +#[test] +fn usable() { + fn cos<F: Float<f32>>(x: F) -> F { + let mut y = x * (1.0 / 6.283); + y -= (y + 0.25).floor() + 0.25; + y *= (y.abs() - 0.5) * 16.0; + return y; + } + assert!((0.995..0.996).contains(&cos(0.1))); + assert!((0.995..0.996).contains(&*cos(unsafe { FFloat::new(0.1) }))); +} @@ -8,8 +8,8 @@ //! # } //! ``` #![feature(core_intrinsics)] -#![warn(clippy::pedantic, clippy::dbg_macro, clippy::use_self, missing_docs)] -#![cfg_attr(not(doc), no_std)] +#![warn(clippy::pedantic, clippy::dbg_macro, missing_docs)] +#![allow(clippy::return_self_not_must_use)] use core::cmp::{Ordering, PartialEq, PartialOrd}; use core::ops::{ Add as add, AddAssign as add_assign, Deref, DerefMut, Div as div, DivAssign as div_assign, @@ -18,11 +18,15 @@ use core::ops::{ }; #[cfg(doc)] use std::f32::{INFINITY as INF, NAN}; +use std::hash::Hash; +pub mod generic_float; mod r#trait; +#[doc(inline)] +pub use generic_float::Float; use r#trait::FastFloat; -/// Float wrapper that uses `ffast-math`. This float also implements [`Ord`], as it is not allowed to be [`NAN`]. +/// Float wrapper that uses `ffast-math`. This float also implements [`Ord`], [`Hash`], and [`Eq`], as it is not allowed to be [`NAN`]. /// /// `FFloat<F>` is guaranteed to have the same memory layout and ABI as F. /// ``` @@ -32,6 +36,10 @@ use r#trait::FastFloat; /// assert_eq!(*result, 1136943.0); /// # } /// ``` +/// +/// ## Safety Notice (for transmuters) +/// +/// A [`FFloat`] is _never_ allowed to be [`NAN`] | [`INF`]. #[repr(transparent)] #[derive(Copy, Clone, PartialEq)] pub struct FFloat<T>(T); @@ -50,11 +58,7 @@ impl<T: FastFloat> core::fmt::Display for FFloat<T> { impl<T: FastFloat> FFloat<T> { /// Create a new [`FFloat`] from your {[`f32`], [`f64`]}. - /// # Safety - /// - /// - You MUST NEVER call this function with [`NAN`] | [`INF`] - /// - You MUST NEVER make it [`NAN`] | [`INF`] - /// - You MUST NEVER combine a [`FFloat`] with a {[`FFloat`], [`f32`], [`f64`]}, to produce a [`NAN`] | [`INF`] + #[doc = include_str!("ffloat_safety.md")] /// ``` /// # use umath::FFloat; /// // SAFETY: i have verified that 7.0 is infact, not NAN or INF. @@ -66,6 +70,11 @@ impl<T: FastFloat> FFloat<T> { new } + /// Checks if somebody else made a mistake, cause UB or panic if so. + /// # Safety + /// + /// This can never cause UB unless someone else made a mistake, therefore ub has already occured. + #[inline(always)] fn check(self) { if self.bad() { if cfg!(debug_assertions) { @@ -206,12 +215,39 @@ impl<T: FastFloat> Ord for FFloat<T> { } } +impl Hash for FFloat<f32> { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.check(); + state.write_u32((self.0 + 0.0).to_bits()); + } +} + +impl Hash for FFloat<f64> { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.check(); + state.write_u64((self.0 + 0.0).to_bits()); + } +} + #[cfg(test)] mod tests { + use std::collections::HashMap; + use super::*; #[test] fn it_works() { let result = unsafe { FFloat::new(2.0) + FFloat::new(2.0) }; assert_eq!(*result, 4.0); } + + #[test] + fn hashing() { + let mut map = HashMap::new(); + map.insert(FFloat(2.0), "hi"); + map.insert(FFloat(7.0), "bye"); + map.insert(FFloat(-0.0), "edge"); + assert!(map[&FFloat(2.0)] == "hi"); + assert!(map[&FFloat(7.0)] == "bye"); + assert!(map[&FFloat(0.0)] == "edge"); + } } diff --git a/src/refer.md b/src/refer.md new file mode 100644 index 0000000..f7d8076 --- /dev/null +++ b/src/refer.md @@ -0,0 +1,4 @@ +# Safety + +Refer to [`Self`]'s safety documentation. +This function is unsafe only when used with a [`FFloat`]; Calling it with a [`f64`] or a [`f32`] is safe.
\ No newline at end of file |