ported legacy DoubleDouble with tests
.NET Test / .NET tests (push) Successful in 1m14s

This commit is contained in:
2026-09-13 22:19:23 +04:00
parent ec609b26f7
commit 082fd84c87
17 changed files with 2891 additions and 26 deletions
+103 -6
View File
@@ -6,15 +6,112 @@ precision than a single `double` while using a fixed-size representation,
rather than arbitrary-precision arithmetic.
> **Work in progress.** The public API is incomplete and may change. Numerical
> accuracy has not been validated, and the library is not ready for production use.
> contracts are covered by regression tests, not an exhaustive accuracy
> certification. The library is not ready for production use.
## Planned scope
## DoubleDouble core
- Double-double arithmetic, constants, comparisons, conversions, and formatting.
- Common functions including `Abs`, `Sqrt`, `Pow`, `Exp`, and `Log`.
- Correctness tests against higher-precision references and performance benchmarks.
`DoubleDouble` stores a normalized high/low pair. Use `new DoubleDouble(value)`
for a single `double`, or `DoubleDouble.FromComponents(high, low)` for arbitrary
components. The factory normalizes finite sums and canonicalizes NaN/infinity
with a positive-zero low component. The two-component constructor is internal
and performs no normalization or validation; it is reserved for trusted,
already-normalized results. The constants `PI`, `E`, and `LN2` include binary64
residuals checked against independently computed high-precision values.
These are development goals, not a list of currently supported features.
- Arithmetic: unary `+`/`-`, binary `+`, `-`, `*`, `/`, and both operand orders with
a `double`. Addition retains residuals under cancellation; multiplication uses
fused multiply-add; division uses residual corrections. Mixed `double` operators
use specialized scalar paths rather than promoting the scalar to `DoubleDouble`.
Their finite fast paths normalize once with a final sum transform; scalar
division uses one compensated quotient correction within the error contract below.
- Exponent boundaries: bounded `BigInteger` calculations avoid intermediate
overflow and underflow on the exceptional finite path. Ordinary arithmetic uses
floating-point transforms without allocations. The stored value remains two
doubles; this is not an arbitrary-precision API.
- Comparisons use both components. `Equals` treats NaNs as equal and signed zeros
as equal for collections. `CompareTo` orders NaN before other values. Numerical
equality and relational operators treat NaN as unordered, like `double`.
- Signed zero is preserved by single-value construction and unary negation.
`FromComponents` with a zero low input preserves the high zero's
sign. Exact cancellation of nonzero values yields positive zero. Arithmetic
special values follow binary64 rules.
Arithmetic is approximate double-double arithmetic, **not a promise of correctly
rounded 106-bit results**. The deterministic rational-oracle tests check a
conservative error bound of `2^-100` relative plus one minimum binary64 subnormal,
with exact component checks for selected representable cases. Near underflow,
extended precision necessarily decreases; overflow produces infinity. Performance
has not been benchmarked, including the allocating exponent-boundary path.
## Conversions and formatting
- Explicit conversions support `double`, `float`, `int`, `long`, and `decimal`
in both directions. Integer inputs are exact. Decimal inputs use their exact
coefficient and scale to compute the high component and its residual.
- Integer casts truncate the complete expansion toward zero and throw
`OverflowException` for nonfinite or out-of-range results. `IConvertible`
integer conversions instead round to nearest, ties to even, with range checks.
- Binary32 output rounds the complete expansion directly, including low-component
decisions at midpoints. Decimal output rounds to the greatest fitting scale up
to 28; nonfinite values and magnitudes above `decimal.MaxValue` throw.
- `IConvertible` reports `TypeCode.Object`, supports conversion to itself, and
treats only numerical zero as false. Char, DateTime, and enum conversions are
unsupported and throw `InvalidCastException`.
- `ToString` formats the exact component sum, supports culture-sensitive
`G`/`g`, `E`/`e`, and `F`/`f`, and rounds ties to even. Precision is bounded to
0999; other standard and custom formats throw `FormatException`. Default
`G32` is **not** shortest-round-trip formatting. NaN, infinities, and signed
zero are supported without converting through decimal.
Conversions, parsing, and formatting use allocating `BigInteger` intermediates
where needed to preserve precision; no additional dependency is required.
## Parsing
`Parse` and `TryParse` accept strings and `ReadOnlySpan<char>` and implement
`IParsable<DoubleDouble>` / `ISpanParsable<DoubleDouble>`. This initial parser
preserves high/low precision rather than parsing through `double` or `decimal`.
It converts an exact decimal coefficient/exponent into rounded high and residual
components, then normalizes the pair. It does not promise universally correctly
rounded 106-bit results or a general `ToString` round trip.
```csharp
using System.Globalization;
using Just.PreciseMath;
DoubleDouble value = DoubleDouble.Parse("9007199254740993", CultureInfo.InvariantCulture);
// value.High == 9007199254740992.0; value.Low == 1.0
bool success = DoubleDouble.TryParse("1.25e-2".AsSpan(), CultureInfo.InvariantCulture,
out DoubleDouble parsed);
```
- Supported grammar: optional sign, ASCII decimal digits with an optional decimal
separator, and optional `e`/`E` exponent with sign and digits. At least one
mantissa digit is required; `.5` and `1.` are accepted with invariant culture.
Surrounding whitespace is allowed; internal whitespace is not.
- Signs and the decimal separator come from the supplied culture; a null or
omitted provider uses the current culture. Culture-specific NaN and infinity
symbols are recognized case-insensitively. Signed zero is preserved.
- Group separators, currency, parentheses, hexadecimal notation, digit separators,
and `NumberStyles` overloads are not supported.
- Input is limited to **4096 characters**, including surrounding whitespace.
Huge exponents are bounded before constructing powers of ten. Well-formed
overflow succeeds with signed infinity; underflow rounds to a subnormal or
signed zero. A second rounding just below the overflow midpoint stays finite.
- `Parse` throws `ArgumentNullException` for a null string and `FormatException`
for invalid, unsupported, or oversized input. `TryParse` returns `false` and
positive `Zero` for those inputs.
## Deferred scope
The planned `PreciseMath` static class and its `Abs`, `Sqrt`, `Pow`, `Exp`, and
`Log` functions are not implemented. Broader generic-math interfaces, expanded
parsing/round-trip formatting, and performance benchmarks remain deferred.
Replacing allocating arithmetic boundary fallbacks is also deferred; the current
`BigInteger` paths remain in place. That optimization does not require removing
`BigInteger` from conversions, parsing, formatting, or independent test oracles.
## Build and test