semantic design
Design goal
semantic answers one question independently of representation: which
number does this datum denote? A binary float, an IEEE decimal and an
interval endpoint are mapped to exact rationals, signed infinities, NaN and
closed intervals, so that values computed by different packages, at different
precisions and in different radices, can be compared exactly. Checked errors
are mapped to a small common vocabulary for the same purpose. The package is a
boundary for tests, diagnostics and protocols; it performs no arithmetic. The
API page lists the items and the
tutorial shows them in use.
Mathematical background
Floating-point numbers are rationals
A finite binary float with sign bit , integer coefficient and exponent denotes , and a finite decimal denotes . Writing , both have the shape with an integer , a radix and an integer , which is a rational number:
The finite binary values are therefore exactly the dyadic rationals
and the
finite decimal values are exactly , both
subrings of . This is the map ExactRational::from_scaled_integer
computes, with evaluated exactly in BigInt.
The reduced form is canonical
Every rational has exactly one representation with
Existence. From any with , multiply numerator and denominator by and divide both by . Uniqueness. Suppose with both pairs reduced and . Then
ExactRational::new establishes exactly this form (sign moved to the
numerator, Euclid’s algorithm for the gcd, zero normalized to ), and the
fields are private, so every ExactRational is reduced. Consequently the
derived structural equality on the two BigInt fields is equality of
rational numbers: .
Comparing values of different radices
Equality of two projected values is decided by the canonical form. Ordering is not provided by the package, but the representation makes it a two-multiplication test: for reduced and with ,
which is what the tutorial
implements over numerator() and denominator().
The canonical denominators also explain which decimals have a binary
representation. A reduced binary value has denominator ; a reduced
decimal value reduces to a denominator . By uniqueness
of the reduced form, a decimal value equals some finite binary float (of
sufficient precision) if and only if its reduced denominator has no factor
. One tenth reduces to , so no binary float equals it, and the
projection of binary64 0.1 is , a different
number.11 Goldberg, “What every computer scientist should know about
floating-point arithmetic”, ACM Computing Surveys 23(1), 1991, section
on base conversion; Knuth, TAOCP vol. 2, section 4.4.
///|
test "a decimal has a binary equal iff its denominator has no factor 5" {
let denominator = fn(s : @semantic.SemanticScalar) {
match s {
Rational(q) => q.denominator().to_string()
_ => "none"
}
}
let d = fn(text : String) {
@semantic.SemanticScalar::from_decimal(@decimal.Decimal::from_string(text).unwrap())
}
let b = fn(x : Double) {
@semantic.SemanticScalar::from_bin_float(@bin_float.BinFloat::from_double(x))
}
inspect(denominator(d("0.375")), content="8")
inspect(d("0.375") == b(0.375), content="true")
inspect(denominator(d("0.1")), content="10")
inspect(denominator(b(0.1)), content="36028797018963968")
}
Intervals as pairs of extended rationals
A non-empty BallFloat denotes for endpoints in , with an infinite endpoint meaning an unbounded side, as in IEEE
1788-2015 set-based intervals.22 IEEE 1788-2015, Standard for Interval Arithmetic, clause 7
(set-based flavor): intervals are closed connected subsets of
, possibly unbounded or empty. SemanticInterval::from_ball_float
projects and independently with from_bin_float, so the pair
determines exactly. The empty interval is stored by ball_float with
and , and the projection keeps that reversed pair;
in the extended order, lower > upper holds exactly for the empty set. A
membership test for a rational is then two comparisons of the
kind derived above.
Design decisions
Exact rationals, not a common floating format
Problem. Comparing a binary and a decimal value needs a common domain.
Options. (a) Convert both to Double or to a wide binary float. (b) Convert
both to a decimal string. (c) Project both to .
Choice: (c). Conversion to a float rounds, so two different values may
compare equal after conversion (binary64 0.1 and decimal 0.1 both round to
the same Double). A string comparison depends on formatting and cohort.
and both embed into without
loss, and the reduced form makes equality a field comparison. The price is
size: has about bits.
What the projection forgets
The projection keeps only the denoted value. It drops precision, decimal quantum (cohort), the sign of zero, NaN payload, sign and signalling state, interval decorations, and all flags and context state. These are properties of representations and of computations, and the concrete packages expose them. Keeping any of them would make two equal numbers from different packages compare unequal, which defeats the purpose of the package.
NaN as a single value with structural equality
SemanticScalar derives Eq, so NaN == NaN. The type is a model in which
“this computation produced no number” is one outcome among others, and tests
need to assert that two packages both produce it. IEEE comparison, where NaN is
unordered with itself, stays in the concrete packages and in
PartialOrder.
A separate error vocabulary
ArithmeticError carries a message and, for certification failures, a detail
record with precisions and refinement counts that differ between packages for
the same mathematical failure. SemanticError keeps only the kind, so
semantic_scalar_result makes the outcome comparable across packages. The
mapping tests the kind predicates in a fixed order and falls back to
UnsupportedOperation; since every ArithmeticErrorKind constructor has its
own predicate, each kind maps to the SemanticError of the same name.
Projections as plain functions
semantic_scalar_result takes the projection as an argument instead of
dispatching on a trait. With and
, the function is
the coproduct of the two maps, applying on the left summand and the error map on the right. It satisfies the functor law , which lets callers reuse one projection for every pipeline.
Correctness / invariants
- Reduced form. Every
ExactRationalsatisfies , and ;newaborts on . - Exactness. For finite , and ; nothing is rounded.
- Soundness and completeness of equality. For finite , of any of the two supported scalar types, the projections are equal if and only if ; this is the uniqueness of the reduced form.
- Class preservation. Infinities project to
Infinitywith the same sign, every NaN projects toNaN, finite values toRational. - Intervals.
from_ball_floatis the pair of endpoint projections; the entire line gives , the empty interval . - Cost.
from_scaled_integerperforms one exact power and, for negative exponents, one gcd; both are polynomial in the bit length of the operands.
Alternatives rejected
- Implementing arithmetic on
ExactRational. Exact rational arithmetic is a different library; here it would invite using the projection as a number type, which it is not. - An
Ord-style instance onSemanticScalar. NaN and the reversed empty interval have no place in a total order; callers order rationals explicitly. - Keeping the signed zero. It would make
-0.0and decimal0unequal, although they denote the same number. - A projection for
@decimal_gda.Decimal. Not provided on the current branch; GDA values reach this package through their string form or through@decimal.Decimal.
Boundaries
- No arithmetic, rounding, parsing, formatting, interchange encoding or interval tightening.
- No ordering of semantic values; equality only.
- Representation details (precision, quantum, signed zero, NaN payload and signalling, decorations, flags, context) are deliberately not preserved.
- Only
BinFloat,@decimal.DecimalandBallFloathave projections. - Projections of values with very large exponents are exact and therefore large; the package does not guard against that cost.
Footnotes
-
Goldberg, “What every computer scientist should know about floating-point arithmetic”, ACM Computing Surveys 23(1), 1991, section on base conversion; Knuth, TAOCP vol. 2, section 4.4. ↩
-
IEEE 1788-2015, Standard for Interval Arithmetic, clause 7 (set-based flavor): intervals are closed connected subsets of , possibly unbounded or empty. ↩