436 lines
26 KiB
Markdown
436 lines
26 KiB
Markdown
# Just.PreciseMath
|
||
|
||
Extended-precision floating-point arithmetic for .NET using double-double
|
||
representations: a high/low pair of `double` values. The goal is to retain more
|
||
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
|
||
> contracts are covered by regression tests, not an exhaustive accuracy
|
||
> certification. The library is not ready for production use.
|
||
|
||
## DoubleDouble core
|
||
|
||
`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. Mathematical constants use precomputed high/low pairs
|
||
checked against independently computed high-precision values; accessing them does
|
||
not perform double-double arithmetic or allocate on the heap.
|
||
|
||
- 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`.
|
||
Division retains the complete first remainder and applies two quotient corrections,
|
||
normalizing before the final correction. Mixed addition and subtraction also retain
|
||
intermediate sum residuals through final normalization. Mixed addition, subtraction,
|
||
and division produce the same component bits as their `DoubleDouble` operations
|
||
with the scalar represented as a zero-low pair.
|
||
- Exponent boundaries: bounded `BigInteger` calculations avoid intermediate
|
||
overflow and underflow on the exceptional finite path. Ordinary arithmetic uses
|
||
floating-point transforms without allocations, although sparse division correction
|
||
products can also reach a boundary path. 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
|
||
of the allocating exponent-boundary path is not covered by the basic benchmarks.
|
||
|
||
Named value constants include `Zero`, `NegativeZero`, `One`, `NegativeOne`, `NaN`,
|
||
`PositiveInfinity`, `NegativeInfinity`, and `Epsilon`. `Epsilon` is the smallest
|
||
positive representable value, `2^-1074` (the same as `double.Epsilon`), **not** a
|
||
relative-error tolerance or machine epsilon. These constants have a positive-zero
|
||
low component; `NegativeZero` preserves the high sign bit while comparing and
|
||
hashing equal to `Zero`.
|
||
|
||
## Predefined mathematical constants
|
||
|
||
All constants below are static `DoubleDouble` properties. Each stores the nearest
|
||
binary64 high component followed by the nearest binary64 residual, rather than
|
||
calculating a ratio, root, or logarithm on access. Names use PascalCase, including
|
||
`Pi`, `E`, and `Ln2`.
|
||
|
||
`DoubleDouble` implements `IFloatingPointConstants<DoubleDouble>` for generic
|
||
access to `E`, `Pi`, and `Tau`; this does not imply support for the full
|
||
`IFloatingPointIeee754<DoubleDouble>` interface.
|
||
|
||
| Group | Properties and values |
|
||
|---|---|
|
||
| Circle and common angles | `Pi` (π), `Tau` (2π), `PiOver2`, `PiOver3`, `PiOver4`, `PiOver6` |
|
||
| Angular conversion | `DegToRad` (π/180), `RadToDeg` (180/π), `InvPi` (1/π), `InvTau` (1/(2π), radians to turns) |
|
||
| Exponential and logarithmic | `E`, `InvE` (1/e), `Ln2` (ln 2), `Ln10` (ln 10) |
|
||
| Log-base conversion | `Log2E` (1/ln 2), `Log10E` (1/ln 10), `Log2Of10` (ln 10/ln 2), `Log10Of2` (ln 2/ln 10) |
|
||
| Roots and geometry | `Sqrt2`, `Sqrt3`, `Sqrt5`, `InvSqrt2`, `InvSqrt3`, `GoldenRatio` ((1+√5)/2) |
|
||
| Gaussian and error-function factors | `SqrtPi`, `InvSqrtPi`, `TwoInvSqrtPi` (2/√π), `SqrtTau` (√(2π)), `InvSqrtTau` (1/√(2π)) |
|
||
|
||
Multiply by conversion factors instead of recomputing them:
|
||
|
||
```csharp
|
||
using Just.PreciseMath;
|
||
|
||
DoubleDouble degrees = new(180.0);
|
||
DoubleDouble radians = degrees * DoubleDouble.DegToRad;
|
||
DoubleDouble convertedDegrees = radians * DoubleDouble.RadToDeg;
|
||
DoubleDouble quarterTurn = DoubleDouble.PiOver2;
|
||
```
|
||
|
||
The factors avoid deriving constants at runtime; the multiplication itself remains
|
||
approximate DD arithmetic, so conversions are not guaranteed exact round trips.
|
||
The list is mathematical and dimensionless, not a table of unit-dependent physical
|
||
constants. The natural logarithm function is provided separately by `DDMath.Log`.
|
||
|
||
## Mathematical functions
|
||
|
||
Root functions are implemented on `DoubleDouble` in `DoubleDouble.RootFunctions.cs`:
|
||
`Sqrt`, `Cbrt`, `Hypot`, and `RootN` complete `IRootFunctions<DoubleDouble>`.
|
||
The additional `InvSqrt` helper and three-argument `Hypot` overload live alongside
|
||
them. Exponential functions live in `DoubleDouble.ExponentialFunctions.cs`:
|
||
`Exp`, `Exp2`, `Exp10`, `ExpM1`, `Exp2M1`, and `Exp10M1` complete
|
||
`IExponentialFunctions<DoubleDouble>`, including cancellation-safe overrides of
|
||
the interface's default minus-one methods. `DDMath` exposes thin forwarding
|
||
wrappers for roots and exponentials; logarithm, power, and reciprocal kernels
|
||
remain in `DDMath`.
|
||
|
||
The `DDMath` static class provides:
|
||
|
||
- `Abs(DoubleDouble)`: preserves both components, maps either signed zero to
|
||
positive zero and either infinity to positive infinity, and returns canonical NaN.
|
||
It shares the existing `DoubleDouble.Abs` implementation.
|
||
- `Reciprocal(DoubleDouble)`: returns exactly the same high and low component bits
|
||
as `1.0 / value`. It specializes scalar/DD division for a numerator of one,
|
||
omitting redundant numerator checks and sharing the finite scalar-numerator kernel,
|
||
including both residual corrections and normalization. Signed zeros map to signed infinities,
|
||
signed infinities to signed zeros, and NaN to canonical NaN. The allocating exact boundary
|
||
path handles extreme exponents; finite overflow produces signed infinity.
|
||
No speedup over scalar/DD division has been measured.
|
||
- `Sqrt(DoubleDouble)`: uses power-of-two scaling and an FMA-based Newton correction
|
||
to retain extended precision, including for subnormal inputs, without squaring
|
||
an unscaled estimate near the exponent limits. Signed zero and positive infinity
|
||
are preserved; negative nonzero inputs and NaN return canonical NaN.
|
||
- `InvSqrt(DoubleDouble)`: computes the reciprocal square root with power-of-two
|
||
scaling and a compensated Newton step, avoiding double-double division and its
|
||
allocating boundary paths. The estimate's squared-product residual is retained
|
||
with FMA. Signed zeros map to correspondingly signed infinities; positive infinity
|
||
maps to positive zero; negative nonzero inputs and NaN return canonical NaN.
|
||
This is a dedicated algorithm, not a claim of measured speedup over `1.0 / Sqrt(x)`.
|
||
- `Cbrt(DoubleDouble)`: real cube root using a scaled binary64 seed and compensated
|
||
Newton corrections. Signed zeros and infinities are preserved; NaN is canonical.
|
||
An exponent-tracked low-component correction avoids losing representable sparse
|
||
residuals during scaling.
|
||
- `Hypot(DoubleDouble, DoubleDouble)`: computes `sqrt(x²+y²)` with bounded scaled
|
||
squares and complete-expansion rescaling at exponent boundaries. Widely separated
|
||
operands use a small correction without squaring an underflow-prone ratio.
|
||
Results are nonnegative, including positive zero. Either infinity takes precedence
|
||
over NaN; otherwise NaN propagates. At the upper boundary, exact squared-input
|
||
comparisons determine overflow; an overflowing approximation of a finite root
|
||
is clamped to the largest finite component pair. Overflow produces positive infinity.
|
||
- `Hypot(DoubleDouble, DoubleDouble, DoubleDouble)`: the 3D Euclidean norm
|
||
`sqrt(x²+y²+z²)`, with the same special-value and approximate-accuracy contracts.
|
||
Coordinates are ordered by magnitude before evaluating scaled squares, so signs
|
||
and permutations give identical component bits. Widely separated coordinates
|
||
contribute combined corrections without prematurely underflowing their squares.
|
||
A zero coordinate reduces to the two-argument overload; exact overflow checks
|
||
include all three original inputs. This overload is a convenience API, not an
|
||
additional member of `IRootFunctions`.
|
||
- `RootN(DoubleDouble, int)`: real nth root for positive degrees, reciprocal root
|
||
for negative degrees, supporting the complete `int` range. Degree zero always
|
||
returns NaN; negative nonzero inputs require an odd degree. Odd degrees retain
|
||
the input sign, while even degrees map either signed zero to positive zero for
|
||
positive degrees and positive infinity for negative degrees. Negative degrees
|
||
exchange zero and infinity. Unlike `Sqrt(-0)`, `RootN(-0, 2)` is positive zero.
|
||
Degrees ±1, ±2 and ±3 reuse identity/reciprocal and existing root kernels; other
|
||
degrees refine a binary64 seed using integer powers with separately tracked
|
||
exponents, then incorporate the input low without premature underflow.
|
||
- `Pow(DoubleDouble, int)`: exponentiation by squaring with a separately tracked
|
||
binary exponent. Supports the full `int` domain, including `int.MinValue`, and
|
||
reciprocates a bounded significand before final scaling for negative exponents.
|
||
Any value to power zero is one (including NaN and signed zero). Other NaNs
|
||
propagate; zero/infinity signs follow integer-power parity and exponent sign.
|
||
- `Pow(DoubleDouble, double)` and `Pow(DoubleDouble, DoubleDouble)`: arbitrary
|
||
real powers, retaining the base's low component and, for the DD overload, the
|
||
exponent's low component. For finite exponents, negative finite bases require integers;
|
||
integrality and odd/even parity use the complete exponent, even above `2^53`.
|
||
Integer-valued exponents within `int` range reuse the integer implementation.
|
||
Other finite cases share `Log`'s range-scaled finite kernel, followed by `Exp`.
|
||
Near-one bases retain tiny low components before multiplication by large powers.
|
||
- `Exp(DoubleDouble)`: binary range reduction with three components of `ln(2)`,
|
||
followed by a [12/12] Padé approximation and power-of-two scaling. Both input
|
||
components affect range boundaries; representable subnormals are retained.
|
||
Either zero maps to one, negative infinity to positive zero, positive infinity
|
||
to positive infinity, and NaN to canonical NaN.
|
||
- `Exp2(DoubleDouble)`: reduces the argument in base two before evaluating a
|
||
bounded natural exponential. Integer powers from -1074 through 1023 are exact.
|
||
An exponent-tracked correction preserves sparse lows that would otherwise round
|
||
prematurely before final scaling. The exact half-minimum-subnormal threshold
|
||
uses both input components. Special-value behavior is the same as `Exp`.
|
||
- `Exp10(DoubleDouble)`: subtracts three split `log10(2)` products before converting
|
||
the bounded remainder to a natural exponent, avoiding large-argument amplification
|
||
of a rounded `ln(10)` product. Special-value behavior is the same as `Exp`.
|
||
- `ExpM1(DoubleDouble)`, `Exp2M1(DoubleDouble)`, and `Exp10M1(DoubleDouble)`:
|
||
compute the corresponding exponential minus one, using a direct series near
|
||
zero to avoid cancellation. Signed zeros are preserved, negative infinity maps
|
||
to negative one, positive infinity to positive infinity, and NaN to canonical NaN.
|
||
Their tested relative error is measured against the minus-one result itself,
|
||
not against the exponential before subtraction.
|
||
- `Log(DoubleDouble)`: natural logarithm using binary range reduction and a
|
||
centered atanh series. The bounded mantissa avoids denominator overflow for
|
||
large inputs; a separate near-one path retains even minimum-subnormal low
|
||
components. Either zero maps to negative infinity, one to positive zero,
|
||
positive infinity to itself, and negative nonzero inputs or NaN to canonical NaN.
|
||
|
||
```csharp
|
||
using Just.PreciseMath;
|
||
|
||
DoubleDouble root = DoubleDouble.Sqrt(new DoubleDouble(2.0));
|
||
DoubleDouble inverseRoot = DoubleDouble.InvSqrt(new DoubleDouble(2.0));
|
||
DoubleDouble cubeRoot = DoubleDouble.Cbrt(new DoubleDouble(-8.0));
|
||
DoubleDouble distance = DoubleDouble.Hypot(new DoubleDouble(3.0), new DoubleDouble(4.0));
|
||
DoubleDouble norm3D = DoubleDouble.Hypot(new DoubleDouble(2.0), new DoubleDouble(3.0), new DoubleDouble(6.0));
|
||
DoubleDouble fifthRoot = DoubleDouble.RootN(new DoubleDouble(2.0), 5);
|
||
DoubleDouble reciprocal = DDMath.Reciprocal(new DoubleDouble(3.0));
|
||
DoubleDouble magnitude = DDMath.Abs(-root);
|
||
DoubleDouble smallPower = DDMath.Pow(new DoubleDouble(2.0), -1024);
|
||
DoubleDouble exponential = DoubleDouble.Exp(new DoubleDouble(1.0));
|
||
DoubleDouble binaryPower = DoubleDouble.Exp2(new DoubleDouble(-1000.0));
|
||
DoubleDouble decimalPower = DoubleDouble.Exp10(new DoubleDouble(0.5));
|
||
DoubleDouble tinyChange = DoubleDouble.ExpM1(new DoubleDouble(1e-30));
|
||
DoubleDouble logarithm = DDMath.Log(new DoubleDouble(10.0));
|
||
DoubleDouble fractionalPower = DDMath.Pow(new DoubleDouble(2.0), 0.5);
|
||
DoubleDouble preciseExponent = DoubleDouble.FromComponents(0.5, 1e-30);
|
||
DoubleDouble precisePower = DDMath.Pow(new DoubleDouble(2.0), preciseExponent);
|
||
```
|
||
|
||
Reciprocal tests check bitwise equivalence with `1.0 / value` and independently
|
||
check exact rational error against `2^-100` relative plus one minimum binary64
|
||
subnormal. They sample every binary64 exponent, both signs, dense and sparse lows,
|
||
binade neighbors, and special values; selected powers of two and sparse corrections
|
||
also have exact component checks. This preserves division's approximate-accuracy
|
||
contract, not a guarantee of correctly rounded results.
|
||
|
||
Square-root and inverse-square-root tests compare the exact component sum against
|
||
a `2^-100` relative error bound using integer inequalities. They include samples at every binary64
|
||
exponent, boundary neighbors, both signs of the low component, and exact binary
|
||
squares/powers of four. Square-root tests also bracket exact root-rounding midpoints;
|
||
both suites exercise half-ulp low-component normalization boundaries.
|
||
This is a tested approximate-accuracy contract, not exhaustive coverage
|
||
of all component pairs or a guarantee of correctly rounded results.
|
||
|
||
Cube-root and nth-root tests check `2^-100` relative error for finite nonzero
|
||
inputs with degree magnitude at least two. Small degrees use exact integer-power
|
||
inequalities; large degrees use independently generated decimal references checked
|
||
at 450 and 650 digits, including `int.MinValue` and `int.MaxValue`.
|
||
`RootN(x, -1)` retains the reciprocal contract, including its subnormal error floor.
|
||
Hypotenuse tests use exact squared inequalities with `2^-100` relative error plus
|
||
one minimum binary64 subnormal. Tests also check exact binary cases, sparse lows,
|
||
special-value signs, facade component-bit agreement, and constrained generic dispatch.
|
||
The 2D and 3D hypotenuse overflow tests bracket the exact threshold, including scaled
|
||
55/48/73 and 1/2/2/3 Pythagorean cases at the midpoint. Three-coordinate tests include
|
||
all signs and permutations of boundary cases and sparse corrections, including
|
||
corrections that only become representable when combined. Finite components remain approximate,
|
||
not universally correctly rounded, including near underflow.
|
||
|
||
Logarithm tests compare exact component sums with independently generated
|
||
120-digit decimal references, requiring agreement at 450 and 650 digits. They
|
||
check `2^-100` relative error plus one minimum binary64 subnormal, with an explicit
|
||
reference-rounding allowance. Powers of two cover every finite binary64 exponent;
|
||
additional tests cover extreme magnitudes, near-one cancellation, sparse lows of
|
||
either sign, and range-reduction transitions. This is sampled approximate accuracy,
|
||
not a universal error proof or a correct-rounding guarantee.
|
||
|
||
Exponential-family tests use independent high-precision decimal references evaluated at
|
||
two precisions, with exact integer comparisons of the stored component sum. They
|
||
check `2^-100` relative error plus one minimum binary64 subnormal, with a separately
|
||
bounded reference-rounding allowance. The base-two/base-ten and minus-one fixtures
|
||
are checked at 450/650 digits, including adjacent lows at true range boundaries,
|
||
series transitions, and tiny inputs. Additional component checks pin sparse
|
||
base-two corrections; constrained generic calls and facade calls match direct
|
||
calls bit-for-bit. Integer-power tests use exact rational
|
||
references and high-precision fixtures for large exponents; their tested absolute
|
||
error bound is `|exact result| * (|exponent| + 1) * 2^-100 + 2^-1074`, not uniform
|
||
relative accuracy independent of the exponent. These functions are approximate;
|
||
precision decreases near underflow and approximation can affect results extremely
|
||
close to a rounding boundary. Final scaling uses the existing allocating exact
|
||
boundary machinery where necessary. No performance measurements are claimed.
|
||
|
||
For real powers, `x^±0 = 1` and `1^y = 1`, including NaN in the other operand;
|
||
other NaNs propagate. Infinite exponents compare the complete `|x|` with one,
|
||
with `(-1)^±∞ = 1`. Zero and infinite bases yield a negative sign only for odd
|
||
integer exponents; negative powers exchange zero and infinity. Finite negative
|
||
bases with non-integer finite exponents return canonical NaN.
|
||
|
||
Real-power reference tests check `2^-90` relative error plus one minimum binary64
|
||
subnormal for the logarithm/Exp path, with an explicit reference-rounding allowance.
|
||
Integer dispatch retains the exponent-dependent bound above. These are tested
|
||
approximate-accuracy contracts, not universal error proofs or correct-rounding
|
||
guarantees. Reference inputs are exact component sums; logarithms/exponentials
|
||
are independently evaluated at 450 and 650 decimal digits.
|
||
|
||
**Known real-power limitations:** extremely close to the range boundaries, the
|
||
logarithm/Exp path can return infinity for a mathematically finite result, or a
|
||
minimum subnormal where zero is expected. The latter can also break monotonicity
|
||
across integer-exponent dispatch. These issues remain unresolved; passing the
|
||
sampled error bounds does not guarantee correct range decisions for every input.
|
||
|
||
## 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
|
||
0–999; 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.
|
||
- `TryFormat(Span<char>, ...)` implements `ISpanFormattable` with the same formats.
|
||
It currently allocates via `ToString`; insufficient space returns `false`, writes
|
||
zero characters, and leaves the destination unchanged.
|
||
|
||
`DoubleDouble` implements `ISignedNumber<DoubleDouble>`, including the inherited
|
||
`INumberBase` contracts: binary radix, classification, absolute value, magnitude
|
||
selection, increment/decrement, and generic numeric conversions. Integer/parity
|
||
tests and magnitude comparisons retain both components. Magnitude ties prefer
|
||
positive values for maximum and negative values for minimum, including signed zero;
|
||
the `Number` variants prefer a number over NaN.
|
||
|
||
`CreateChecked`, `CreateSaturating`, and `CreateTruncating` support built-in numeric
|
||
types and `BigInteger`. Floating overflow produces signed infinity in all modes.
|
||
Finite integer output truncates the exact sum, then throws on overflow, clamps,
|
||
or retains the low destination-width bits, respectively. Decimal nonchecked output
|
||
clamps out-of-range values and maps NaN to zero. These policies are distinct from
|
||
the existing casts and `IConvertible` conversions above.
|
||
|
||
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);
|
||
```
|
||
|
||
- Provider-only finite 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. The additional alias `inf` accepts an
|
||
optional culture-specific sign (`inf`, `+inf`, `-inf` with invariant culture).
|
||
Exact custom special symbols take precedence over the alias. Special values accept
|
||
surrounding whitespace and signs even with `NumberStyles.None`; ordinary finite
|
||
numbers still obey the supplied style flags. Signed zero is preserved.
|
||
- Provider-only overloads reject grouping, currency, and parentheses. Explicit
|
||
`NumberStyles` overloads support decimal flags through `NumberStyles.Any`, including
|
||
grouping, currency, parentheses, and trailing signs; group sizes are not validated.
|
||
Hexadecimal, binary, and undefined style flags throw `ArgumentException`, including
|
||
in `TryParse`. Hexadecimal notation and programming-language digit separators
|
||
remain unsupported.
|
||
- Input is limited to **2048 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
|
||
|
||
Natural `DDMath.Log`, the complete exponential family on `DoubleDouble`, and all
|
||
three `DDMath.Pow` overloads are implemented.
|
||
Logarithms in other bases, generic-math interfaces beyond `ISignedNumber`,
|
||
`IFloatingPointConstants`, `IRootFunctions`, and `IExponentialFunctions`, additional text formats/general
|
||
round-trip formatting, and non-arithmetic 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
|
||
|
||
Requires the .NET 10 SDK in the `10.0.1xx` feature band, as selected by `global.json`.
|
||
Run from the repository root:
|
||
|
||
```sh
|
||
dotnet restore Just.PreciseMath.slnx --locked-mode
|
||
dotnet build Just.PreciseMath.slnx -c Release --no-restore
|
||
dotnet test --solution Just.PreciseMath.slnx -c Release --no-build --minimum-expected-tests 1
|
||
dotnet format Just.PreciseMath.slnx --verify-no-changes --no-restore
|
||
```
|
||
|
||
Test results and coverage reports are available in the `test-results` artifact
|
||
on CI workflow runs.
|
||
|
||
## Benchmarks
|
||
|
||
BenchmarkDotNet measures arithmetic throughput, dependent-chain latency, and allocations,
|
||
including comparisons of `DoubleDouble`, `decimal`, and `double`, mixed scalar operations,
|
||
and exponent-boundary paths. These are performance measurements, not accuracy tests.
|
||
|
||
After the Release build above, run from the repository root:
|
||
|
||
```sh
|
||
# Discover benchmark methods without running them.
|
||
dotnet run --project 2-benchmarks/Just.PreciseMath.Benchmarks -c Release --no-build -- --list flat
|
||
|
||
# Smoke test; Dry timings are not performance measurements.
|
||
dotnet run --project 2-benchmarks/Just.PreciseMath.Benchmarks -c Release --no-build -- --job Dry --filter '*'
|
||
|
||
# Full measurement run.
|
||
dotnet run --project 2-benchmarks/Just.PreciseMath.Benchmarks -c Release --no-build -- --filter '*'
|
||
```
|
||
|
||
Reports are written under the ignored `BenchmarkDotNet.Artifacts/` directory.
|
||
Replace `'*'` with a benchmark-name pattern to select a subset; use `--artifacts <path>`
|
||
to keep runs separate. Run measurements on an idle machine and inspect BenchmarkDotNet warnings.
|
||
|
||
## Project structure
|
||
|
||
- `0-source/Just.PreciseMath/`: library implementation.
|
||
- `1-tests/Just.PreciseMath.Tests/`: unit tests.
|
||
- `2-benchmarks/Just.PreciseMath.Benchmarks/`: arithmetic benchmarks against `decimal` and `double`.
|
||
|
||
## Contributing
|
||
|
||
Follow `.editorconfig` and include regression tests with numerical changes.
|
||
Explain the algorithm's assumptions, the source of reference values, and any
|
||
error tolerances. Include updated `packages.lock.json` files with dependency changes.
|
||
|
||
## License
|
||
|
||
Licensed under the [MIT License](LICENSE).
|