rust ffast-math (defunct, use lower)
add generic Float trait
bendn 2023-10-11
parent dba5033 · commit 824069a
-rw-r--r--Cargo.toml2
-rw-r--r--src/ffloat_safety.md5
-rw-r--r--src/ffloat_safety_noconstr.md4
-rw-r--r--src/ffloat_safety_notice.md3
-rw-r--r--src/generic_float.rs336
-rw-r--r--src/lib.rs52
-rw-r--r--src/refer.md4
7 files changed, 397 insertions, 9 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 84355f0..ab02e85 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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) })));
+}
diff --git a/src/lib.rs b/src/lib.rs
index ef8c7ba..8c4cd96 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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