Skip to main content

ScalarValue

Trait ScalarValue 

Source
pub trait ScalarValue:
    Copy
    + NumOps
    + NumAssignOps
    + MulAddExt<Output = Self>
    + Neg<Output = Self>
    + SplitFloatConsts<Self> {
    type CompensatedConstInt<const N: LargeInt>: SplatConst<Compensated<Self>>;
    type CompensatedConstRatio<const N: LargeInt, const D: LargeInt>: SplatConst<Compensated<Self>>;

    const SPLITTER: Self;
    const SPLIT_THRESH: Self;
    const SPLIT_DOWN: Self;
    const SPLIT_UP: Self;
    const SCALAR_ZERO: Self;
    const SCALAR_ONE: Self;
    const MAX_ERFINV_SERIES: Self;
    const ERF_CF_SPLIT: Self;

    // Required methods
    fn scalar_trunc(self) -> Self;
    fn rebalance_for_split(a: Self, b: Self) -> (Self, Self);

    // Provided methods
    fn two_sum(a: Self, b: Self) -> (Self, Self) { ... }
    fn two_diff(a: Self, b: Self) -> (Self, Self) { ... }
    fn veltkamp_split(a: Self) -> (Self, Self) { ... }
    fn two_prod(a: Self, b: Self) -> (Self, Self) { ... }
    fn square(a: Self) -> (Self, Self) { ... }
    fn two_quot(a: Self, b: Self) -> (Self, Self) { ... }
}
Expand description

Scalar values that can be used in compensated arithmetic.

This also applies to Vectors whose elements implement this trait.

This can be implemented for anything so long as a suitable Veltkamp’s splitting constant can be provided. It just doesn’t make much sense on anything but scalar-like floating point types.

If the type has native FMA support, as indicated by MulAddExt::HAS_NATIVE_FMA, the splitting constant is never used, so it can be a dummy value in that case.

Required Associated Constants§

Source

const SPLITTER: Self

for Veltkamp’s splitting

Source

const SPLIT_THRESH: Self

|a| above this overflows a * SPLITTER, so rebalance_for_split must bring it down first.

Need not be a power of two, and is not one for f64: that value is the largest double strictly below 2^996, which errs in the safe direction.

Source

const SPLIT_DOWN: Self

Exact power of two to scale a large operand down by before splitting.

Source

const SPLIT_UP: Self

Exact reciprocal of ScalarValue::SPLIT_DOWN, also a power of two.

Source

const SCALAR_ZERO: Self

The value zero. Named this way to avoid conflicts.

Source

const SCALAR_ONE: Self

The value one. Named this way to avoid conflicts.

Source

const MAX_ERFINV_SERIES: Self

Empirical maximum |x| for which the erf_inv Maclaurin series converges within 64 terms to full precision. This is only used when the special crate feature is enabled, for the erf_inv function.

Source

const ERF_CF_SPLIT: Self

|x| at which erf/erfc hand over from the erf series to the erfc continued fraction, when the policy asks for precision.

The series computes erf and gets erfc as 1 - erf, so erfc inherits erf’s ABSOLUTE error and loses log2(erf/erfc) bits of relative accuracy. The continued fraction computes erfc directly, with no cancellation, but needs far more iterations the smaller |x| gets: a measured 338 double-double Lentz steps at 1.5 against 202 at 2.0. Lowering this constant buys accuracy with time.

Per type, because the two widths disagree about where the trade pays. For f64 double-double the continued fraction holds a flat ~2-7e-31 relative from 1.5 upward, comfortably better than the series’ 6.9e-31 at 1.5 and 7.2e-30 at 1.821, so 1.5 wins. For f32 double-single the continued fraction has a much worse floor (a measured ~1.4-2.4e-12, roughly 400-700 ulp), and the series beats it at every point from 1.25 to 2.25, so f32 keeps the historical 2.

Only consulted when PrecisionPolicy is Best or above. Below that both types use 2 regardless. See erf_internal_p.

Required Associated Types§

Source

type CompensatedConstInt<const N: LargeInt>: SplatConst<Compensated<Self>>

Marker type for splatting a compile-time integer constant as Compensated<Self>.

Each concrete impl can choose the precision strategy: f32 uses a f64 intermediate to capture the rounding error in the error term; f64 stores zero error (would need f128 for better); Vector<R> delegates to the inner element and splats via VectorValue.

Source

type CompensatedConstRatio<const N: LargeInt, const D: LargeInt>: SplatConst<Compensated<Self>>

Marker type for splatting a compile-time rational constant N/D as Compensated<Self>.

Same precision strategy as CompensatedConstInt.

Required Methods§

Source

fn scalar_trunc(self) -> Self

Returns the value truncated to its integer component.

Named this way to avoid conflicts. Required for the Rem implementation.

Source

fn rebalance_for_split(a: Self, b: Self) -> (Self, Self)

Move an exact power of two from whichever operand is large enough to overflow veltkamp_split into the other one, leaving a * b unchanged.

Scaling the split’s output back up does not work: for a near MAX the split rounds hi up past MAX / scale, so scaling back overflows anyway. Rebalancing the inputs against each other never has to scale anything back.

The receiving operand cannot overflow. If |a| > THRESH and a * b is finite, then |b| < MAX / |a| <= MAX / THRESH, which is exactly the scale factor - so b * scale stays in range. When both operands are that large the true product is already infinite, and an infinite result is the correct answer.

§Residual limit

One case remains, and it is inherent to Dekker’s two_prod rather than to the guard: the split rounds hi up by up to a relative 2^-53, so a_hi * b_hi slightly exceeds a * b. If a * b is within that relative distance of MAX, that product overflows and the error term comes back infinite. The value is still correct. two_prod(MAX, 0.5) is fine; only two_prod(MAX, 1.0) is affected.

§Implementing

Required rather than defaulted, because a default that ignored SPLIT_THRESH would silently make those constants dead and hand the implementor a two_prod that returns NaN on large operands. It cannot be given a working default either: a generic body would need a lane-wise comparison and select, which this trait’s bounds do not provide.

A scalar implementation branches; a vector one selects per lane. See the f64 and Vector<R> implementations below.

Provided Methods§

Source

fn two_sum(a: Self, b: Self) -> (Self, Self)

Source

fn two_diff(a: Self, b: Self) -> (Self, Self)

Source

fn veltkamp_split(a: Self) -> (Self, Self)

Veltkamp’s splitting: a == hi + lo exactly, with each part narrow enough that products of the parts are themselves exact.

a * SPLITTER overflows once |a| passes MAX / SPLITTER - 8.3056e34 for f32, 1.3394e300 for f64 - so callers must bring a under that first. two_prod does it with rebalance_for_split.

SPLIT_THRESH sits at or below that point rather than exactly on it: for f32 it is 2^115, comfortably under, because the obvious 2^116 is 8.3077e34 and so lands just above the real limit. That near miss was a live bug.

Source

fn two_prod(a: Self, b: Self) -> (Self, Self)

Source

fn square(a: Self) -> (Self, Self)

Source

fn two_quot(a: Self, b: Self) -> (Self, Self)

2Quotient: (q, r) with q = RN(a / b) and a == q * b + r exactly.

r is a remainder, not a second word of the quotient: a / b == q + r / b.

Mostly used for q. two_quot(a, b).0 is a correctly-rounded division that survives algebraic-scalar, where a bare / may become x * RN(1/c) (up to 1.204 ulp measured). The unused remainder is dead code. See FloatVectorWithBits::two_quot.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl ScalarValue for f32

Source§

const SPLITTER: Self

Source§

const SPLIT_THRESH: Self = 4.153_837_5e34

Source§

const SPLIT_DOWN: Self = 1.220_703_1e-4

Source§

const SPLIT_UP: Self = 8192.0

Source§

const SCALAR_ZERO: Self = 0.0

Source§

const SCALAR_ONE: Self = 1.0

Source§

const MAX_ERFINV_SERIES: Self = 0.75

Source§

const ERF_CF_SPLIT: Self = 2.0

Source§

type CompensatedConstInt<const N: LargeInt> = F32CompensatedIntConst<N>

Source§

type CompensatedConstRatio<const N: LargeInt, const D: LargeInt> = F32CompensatedRatioConst<N, D>

Source§

fn rebalance_for_split(a: Self, b: Self) -> (Self, Self)

Source§

fn scalar_trunc(self) -> Self

Source§

impl ScalarValue for f64

Source§

const SPLITTER: Self

Source§

const SPLIT_THRESH: Self = 6.69692879491417e299

Source§

const SPLIT_DOWN: Self = 3.725_290_298_461_914e-9

Source§

const SPLIT_UP: Self = 268435456.0

Source§

const SCALAR_ZERO: Self = 0.0

Source§

const SCALAR_ONE: Self = 1.0

Source§

const MAX_ERFINV_SERIES: Self = 0.545

Source§

const ERF_CF_SPLIT: Self = 1.5

Source§

type CompensatedConstInt<const N: LargeInt> = F64CompensatedIntConst<N>

Source§

type CompensatedConstRatio<const N: LargeInt, const D: LargeInt> = F64CompensatedRatioConst<N, D>

Source§

fn rebalance_for_split(a: Self, b: Self) -> (Self, Self)

Source§

fn scalar_trunc(self) -> Self

Source§

impl<R: FloatRegister> ScalarValue for Vector<R>
where R::Element: ScalarValue,

Source§

const SPLITTER: Self

Source§

const SPLIT_THRESH: Self

Source§

const SPLIT_DOWN: Self

Source§

const SPLIT_UP: Self

Source§

const SCALAR_ZERO: Self = Self::ZERO

Source§

const SCALAR_ONE: Self = Self::ONE

Source§

const MAX_ERFINV_SERIES: Self

Source§

const ERF_CF_SPLIT: Self

Source§

type CompensatedConstInt<const N: LargeInt> = CompensatedVectorConst<<<R as Register>::Element as ScalarValue>::CompensatedConstInt<N>>

Source§

type CompensatedConstRatio<const N: LargeInt, const D: LargeInt> = CompensatedVectorConst<<<R as Register>::Element as ScalarValue>::CompensatedConstRatio<N, D>>

Source§

fn two_sum(a: Self, b: Self) -> (Self, Self)

Source§

fn two_diff(a: Self, b: Self) -> (Self, Self)

Source§

fn two_prod(a: Self, b: Self) -> (Self, Self)

Source§

fn square(a: Self) -> (Self, Self)

Source§

fn two_quot(a: Self, b: Self) -> (Self, Self)

Source§

fn rebalance_for_split(a: Self, b: Self) -> (Self, Self)

Source§

fn scalar_trunc(self) -> Self

Implementors§

Last built: 2026-09-08 21:35:55 UTC