Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  • sqrt uses a fixed-iteration Newton-Raphson (Babylonian) iteration: x_{n+1} = (x_n + self / x_n) / 2. self == 0 short-circuits to 0 rather than iterating (the formula would otherwise divide by zero on the first step), and negative inputs return 0 rather than NaN or a panic, since Scalar is an infallible trait that must also support future non-float implementors with no NaN representation.
  • sin and cos use a fixed-iteration Taylor series expansion around zero, after first reducing the input to [-pi, pi] by subtracting the nearest multiple of 2*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) when x’s type isn’t pinned to a concrete float — dot-call syntax (x.sqrt()) resolves to std’s inherent method on concrete f32/f64 values, not the trait method, and won’t compile at all in a generic T: Scalar context.
  • Scalar::sqrt of a negative number returns 0, not NaN — don’t rely on NaN propagation to detect a negative input; check the sign yourself if that matters to your code.