Scalars & Numeric Types
Every algorithm in rustebra is generic over a Scalar type rather than hard-coded to
f32 or f64. Scalar (defined in rustebra::scalar) is the minimal arithmetic surface
the rest of the crate depends on: the additive and multiplicative identities (zero,
one), the four basic operations (add, sub, mul, div), and the transcendental
functions needed by the algorithm layer (sqrt, sin, cos). f32 and f64 are the
two implementors provided today.
#![allow(unused)]
fn main() {
use rustebra::scalar::Scalar;
fn double<T: Scalar>(x: T) -> T {
x.add(x)
}
}
Note the call style for the transcendental methods: Scalar::sqrt(4.0f64) rather than
4.0f64.sqrt(). f64/f32 already have an inherent sqrt from std, which would shadow
the trait method if called with dot syntax, so code that needs to stay generic over T: Scalar (or that wants the trait’s specific implementation) calls it as an associated
function instead.
How sqrt, sin, and cos are computed
Scalar is implemented for f32/f64 without relying on std’s floating-point
intrinsics for these three methods, since the crate is no_std by default:
sqrtuses a fixed-iteration Newton-Raphson (Babylonian) iteration:x_{n+1} = (x_n + self / x_n) / 2.self == 0short-circuits to0rather than iterating (the formula would otherwise divide by zero on the first step), and negative inputs return0rather thanNaNor a panic, sinceScalaris an infallible trait that must also support future non-float implementors with noNaNrepresentation.sinandcosuse a fixed-iteration Taylor series expansion around zero, after first reducing the input to[-pi, pi]by subtracting the nearest multiple of2*pi.
In all three cases the iteration count is fixed rather than convergence-checked, so the
amount of work done is predictable and independent of the input — important in no_std
contexts where there’s no easy way to bound worst-case iteration count otherwise.
FloatTolerance
Comparing floating-point results for approximate equality requires a tolerance, and
rustebra doesn’t hard-code one into Scalar itself. Instead, FloatTolerance is a
separate trait (also implemented for f32/f64) that adds a single method, epsilon(),
reporting the type’s machine epsilon — the smallest positive value e such that
T::one().add(e) != T::one(). It’s kept separate from Scalar so that a hypothetical
future Scalar implementor with no meaningful notion of machine epsilon (e.g. a
fixed-point or exact-rational type) isn’t forced to implement it just to satisfy Scalar.
#![allow(unused)]
fn main() {
use rustebra::scalar::FloatTolerance;
assert_eq!(f64::epsilon(), f64::EPSILON);
}
Gotchas
- Call the transcendental methods as
Scalar::sqrt(x)/Scalar::sin(x)/Scalar::cos(x)whenx’s type isn’t pinned to a concrete float — dot-call syntax (x.sqrt()) resolves tostd’s inherent method on concretef32/f64values, not the trait method, and won’t compile at all in a genericT: Scalarcontext. Scalar::sqrtof a negative number returns0, notNaN— don’t rely onNaNpropagation to detect a negative input; check the sign yourself if that matters to your code.