Represents a floating-point value with tracked numerical error.
The FloatWithError class wraps a float value together with an estimate of its accumulated absolute error. This allows tracking precision loss through chains of arithmetic operations, which is particularly important in unit conversion where multiple multiplications and divisions can compound rounding errors.
Each operation (addition, subtraction, multiplication, division, inversion, exponentiation) accumulates error based on the precision limits of IEEE 754 double-precision floating-point arithmetic. The relativeError property provides a normalised measure of precision loss.
Implements Stringable, formatting as "value ± absoluteError".
- Immutable value objects (all operations return new instances)
- Tracks absolute error through arithmetic operations
- Supports addition, subtraction, negation, multiplication, division, inversion, and exponentiation
- Allows comparison of conversion paths by precision
- Integer values start with zero error
private(set) float $valueThe floating-point numeric value.
private(set) float $absoluteErrorThe absolute error estimate. This represents the maximum expected deviation from the true value due to floating-point precision limits.
public float $relativeError { get; }The relative error as a proportion of the absolute value. Calculated as absoluteError / |value|. Returns INF when the value is zero but error is non-zero. Returns 0.0 when both value and error are zero.
This property is useful for comparing the precision of different conversion paths.
public function __construct(float $value, ?float $error = null)Create a new FloatWithError instance.
Parameters:
$value(float) - The numeric value$error(?float) - The absolute error estimate (default: null)
Behavior:
- If
$erroris null and$valueis an exact integer, the error is 0.0 (integers are exact) - If
$erroris null and$valueis a non-integer float, error is estimated as half the ULP of the value - If
$erroris provided, it is used directly
Examples:
// Integer with no error
$exact = new FloatWithError(1000);
echo $exact->absoluteError; // 0.0
// Float with estimated error
$approx = new FloatWithError(3.14159);
echo $approx->absoluteError; // Small value based on float precision
// Explicit error
$measured = new FloatWithError(100.0, 0.5);
echo $measured->absoluteError; // 0.5public function isExactInt(): boolCheck if the value represents an exact mathematical integer (no fractional part and zero error).
Returns:
bool- True if the value has no fractional component and zero absolute error
Examples:
$int = new FloatWithError(42.0);
$int->isExactInt(); // true
$float = new FloatWithError(42.5);
$float->isExactInt(); // falsepublic function neg(): selfNegate this value. Error magnitude is unchanged.
Returns:
FloatWithError- A new instance with the negated value and the same error.
public function inv(): selfReturn the multiplicative inverse (1/value). Relative error is preserved.
Returns:
FloatWithError- A new instance with the inverted value and propagated error.
Throws:
DivisionByZeroError- If the value is zero.
public function add(float|self $other): selfAdd another value to this one, accumulating errors.
Parameters:
$other(float|self) - The value to add
Returns:
FloatWithError- A new instance with the sum and combined error
Behavior:
- Absolute errors add directly
- Adding a FloatWithError combines both error estimates
public function sub(float|self $other): selfSubtract another value from this one, accumulating errors.
Parameters:
$other(float|self) - The value to subtract
Returns:
FloatWithError- A new instance with the difference and combined error
Behavior:
- Absolute errors add directly (subtraction has the same error propagation as addition)
public function mul(float|self $other): selfMultiply this value by another, accumulating errors.
Parameters:
$other(float|self) - The value to multiply by
Returns:
FloatWithError- A new instance with the product and combined error
Behavior:
- Relative errors add in multiplication
- Multiplying by an exact integer preserves precision
- Multiplying by a FloatWithError combines both error estimates
Examples:
$a = new FloatWithError(100.0);
$b = new FloatWithError(0.3048); // feet to meters
$result = $a->mul($b);
echo $result->value; // 30.48
echo $result->relativeError; // Combined relative errorpublic function div(float|self $other): selfDivide this value by another, accumulating errors.
Parameters:
$other(float|self) - The value to divide by
Returns:
FloatWithError- A new instance with the quotient and combined error
Throws:
DivisionByZeroError- If dividing by zero
Examples:
$distance = new FloatWithError(1000.0);
$time = new FloatWithError(60.0);
$speed = $distance->div($time);
echo $speed->value; // 16.666...public function pow(int $exponent): selfRaise this value to an integer power.
Parameters:
$exponent(int) - The exponent to raise to
Returns:
FloatWithError- A new instance with the result and propagated error
Throws:
DivisionByZeroError- If the base is zero and the exponent is negative
Behavior:
- Relative error multiplies by |exponent|
- Negative exponents are supported (equivalent to 1/value^|exponent|)
- Exponent of 0 returns 1.0 with zero error
- Exponent of 1 returns same value with propagated error
Examples:
$factor = new FloatWithError(1000.0); // km to m
$squared = $factor->pow(2); // km2 to m2
echo $squared->value; // 1000000.0public function __toString(): stringConvert to a string representation showing value and absolute error.
Returns:
string- Formatted as"value ± absoluteError"(e.g."3.14159 ± 2.22e-16")
use OceanMoon\Quantities\Internal\FloatWithError;
// Direct conversion factor (high precision)
$direct = new FloatWithError(0.3048); // feet to meters
// Indirect via inches (lower precision)
$ftToIn = new FloatWithError(12);
$inToCm = new FloatWithError(2.54);
$cmToM = new FloatWithError(0.01);
$indirect = $ftToIn->mul($inToCm)->mul($cmToM);
// Compare precision
if ($direct->relativeError < $indirect->relativeError) {
echo "Direct conversion is more precise";
}use OceanMoon\Quantities\Internal\FloatWithError;
// Conversion from yards to meters
$ydToFt = new FloatWithError(3); // 3 feet per yard (exact)
$ftToM = new FloatWithError(0.3048); // feet to meters
$ydToM = $ydToFt->mul($ftToM);
echo $ydToM->value; // 0.9144
echo $ydToM->relativeError; // Error from ftToM only (ydToFt was exact)- Conversion - Uses FloatWithError for conversion factors
- Converter - Selects conversion paths based on error