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§
Sourceconst SPLIT_THRESH: Self
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.
Sourceconst SPLIT_DOWN: Self
const SPLIT_DOWN: Self
Exact power of two to scale a large operand down by before splitting.
Sourceconst SPLIT_UP: Self
const SPLIT_UP: Self
Exact reciprocal of ScalarValue::SPLIT_DOWN, also a power of two.
Sourceconst SCALAR_ZERO: Self
const SCALAR_ZERO: Self
The value zero. Named this way to avoid conflicts.
Sourceconst SCALAR_ONE: Self
const SCALAR_ONE: Self
The value one. Named this way to avoid conflicts.
Sourceconst MAX_ERFINV_SERIES: Self
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.
Sourceconst ERF_CF_SPLIT: Self
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§
Sourcetype CompensatedConstInt<const N: LargeInt>: SplatConst<Compensated<Self>>
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.
Sourcetype CompensatedConstRatio<const N: LargeInt, const D: LargeInt>: SplatConst<Compensated<Self>>
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§
Sourcefn scalar_trunc(self) -> Self
fn scalar_trunc(self) -> Self
Returns the value truncated to its integer component.
Named this way to avoid conflicts. Required for the Rem implementation.
Sourcefn rebalance_for_split(a: Self, b: Self) -> (Self, Self)
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§
fn two_sum(a: Self, b: Self) -> (Self, Self)
fn two_diff(a: Self, b: Self) -> (Self, Self)
Sourcefn veltkamp_split(a: Self) -> (Self, Self)
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.
fn two_prod(a: Self, b: Self) -> (Self, Self)
fn square(a: Self) -> (Self, Self)
Sourcefn two_quot(a: Self, b: Self) -> (Self, Self)
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".