diff --git a/.editorconfig b/.editorconfig index 8b76ca3..cb720e7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -67,6 +67,8 @@ dotnet_style_predefined_type_for_locals_parameters_members = true:silent dotnet_style_predefined_type_for_member_access = true:silent # Parentheses preferences +# Keep explicit grouping for readable mathematical expressions, even when redundant. +dotnet_diagnostic.IDE0047.severity = none dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent diff --git a/.gitignore b/.gitignore index 916c2f4..4f4b75d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ review-* *.worktrees +.hermes # User-specific files *.rsuser diff --git a/0-source/Just.PreciseMath/DoubleDouble.cs b/0-source/Just.PreciseMath/DoubleDouble.cs index ff3752f..17f03ed 100644 --- a/0-source/Just.PreciseMath/DoubleDouble.cs +++ b/0-source/Just.PreciseMath/DoubleDouble.cs @@ -3,10 +3,9 @@ namespace Just.PreciseMath; /// /// Represents higher precision floating point type /// -/// -/// Constructs new DoubleDouble from given low and high components -/// -public readonly struct DoubleDouble : IEquatable, IEqualityOperators +public readonly struct DoubleDouble : + IEquatable, + IEqualityOperators { internal readonly double _high; internal readonly double _low; @@ -18,6 +17,14 @@ public readonly struct DoubleDouble : IEquatable, IEqualityOperato _low = low; } + /// + /// Constructs new DoubleDouble from a given double. + /// + /// Initial high component + public DoubleDouble(double high) : this(high, 0.0) + { + } + #region Static constants /// /// Represents a value that is not a number (NaN). @@ -31,6 +38,18 @@ public readonly struct DoubleDouble : IEquatable, IEqualityOperato /// Represents a zero value. /// public static DoubleDouble Zero => new(); + /// + /// Represents the ratio of the circumference of a circle to its diameter, specified by the constant, π. + /// + public static DoubleDouble PI => new(3.141592653589793, 1.2246467991473532e-16); + /// + /// Represents the natural logarithmic base, specified by the constant, e. + /// + public static DoubleDouble E => new(2.718281828459045, 1.4456468917292502e-16); + /// + /// Represents the natural logarithm of value 2. + /// + public static DoubleDouble LN2 => new(0.6931471805599453, 2.3190468138462996e-17); #endregion /// diff --git a/0-source/Just.PreciseMath/PreciseMathHelper.cs b/0-source/Just.PreciseMath/PreciseMathHelper.cs new file mode 100644 index 0000000..7265b1d --- /dev/null +++ b/0-source/Just.PreciseMath/PreciseMathHelper.cs @@ -0,0 +1,42 @@ +namespace Just.PreciseMath; + +internal static class PreciseMathHelper +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (double Res, double Err) TwoAdd(double a, double b) + { + double r = a + b; + double t = r - a; + + return (r, (a - (r - t)) + (b - t)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (double Res, double Err) TwoQuickAdd(double a, double b) + { + double r = a + b; + + return (r, b - (r - a)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (double Res, double Err) TwoSubstract(double a, double b) + { + double r = a - b; + double t = r - a; + + return (r, (a - (r - t)) - (b + t)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (double Res, double Err) TwoMultiply(double a, double b) + { + double r = a * b; + + return (r, Math.FusedMultiplyAdd(a, b, -r)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (double Res, double Err) TwoSuare(double a) + { + double r = a * a; + + return (r, Math.FusedMultiplyAdd(a, a, -r)); + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e775bd6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,159 @@ +# Agent guidelines + +These rules apply throughout this repository. Keep purpose, status, usage, and +development instructions in `README.md`; keep agent workflow rules here. Neither +file is a session log. + +## Scope and repository map + +This is a WIP .NET double-double arithmetic library, not arbitrary-precision math. +Do not claim API completeness or accuracy beyond tested contracts. + +- `0-source/Just.PreciseMath/`: library implementation and package metadata. +- `1-tests/Just.PreciseMath.Tests/`: xUnit v3 tests, Shouldly assertions, and + Microsoft.Testing.Platform (MTP) with `coverlet.MTP` coverage. +- `2-benchmarks/Just.PreciseMath.Benchmarks/`: source-free BenchmarkDotNet library + scaffold, not yet a runnable benchmark suite. +- Root `Directory.Build.props` holds shared settings. Each numbered directory's + props explicitly imports it; preserve this import chain. +- `review-legacy/`: optional local reference material, excluded by `.gitignore`. + Start with `review-legacy/Review-Revisited-DoubleDouble.md` when present. The code + and proposed fixes contain known defects: reproduce findings against current + code rather than treating them as correctness oracles. Do not compile, copy + wholesale, or force-add these files. If absent, proceed without them; do not + invent their contents or make validation depend on them. +- Use the ignored, repository-local `.hermes/` directory for plans, checklists, + investigation notes, and handoff context. Create or update notes as useful; + keep them concise and revalidate them against current files. Optional + `.hermes/project-context.md` holds setup and review background. Do not store + secrets, force-add this directory, or make builds or tests depend on it. + +## Working rules + +- Check both staged and unstaged changes before starting. Preserve the user's + work and index; do not stage, commit, push, publish packages, or rewrite history + without an explicit request. Do not read or expose credentials. +- Read definitions, callers, tests, and relevant configuration before editing. + Limit changes to the request; do not implement the roadmap or repair unrelated WIP code. +- Reproduce numerical bugs with a failing regression test before fixing them. + For new behavior, define the contract and test cases before implementation. + Exercise sibling overloads and operand orders that can share the defect. +- Do not weaken analyzers, nullable checks, warnings-as-errors, or assertions to + get a green build. Explain necessary policy changes before making them. +- Keep dependencies minimal. Check existing references, compatibility, and current + stable releases before adding or upgrading packages. + +## C# conventions + +Follow `.editorconfig`, not incidental style in unfinished code. + +- Use file-scoped namespaces, explicit types rather than `var`, and block-bodied + methods. Preserve the configured expression-bodied property/accessor preferences. +- Use `_camelCase` for non-public instance fields, including internal fields; + `s_camelCase` for non-public mutable static fields; PascalCase for constants + and static readonly fields. Do not rename internal fields to remove underscores. +- Preserve parentheses that make mathematical grouping readable. `IDE0047` is + intentionally disabled; do not re-enable it or remove grouping as style cleanup. +- Document public APIs and non-obvious numerical preconditions. Explain algorithms, + error behavior, and range constraints rather than narrating syntax. +- Tests and benchmarks already have internal access through `InternalsVisibleTo`; + do not widen the public API merely to make a helper testable. + +## Numerical correctness + +- Treat floating-point evaluation order as part of the algorithm. Do not reassociate + expressions, discard residuals, replace fused multiply-add with multiply-plus-add, + or simplify error-free transforms without justification and regression tests. + Mathematically equivalent formulas can round differently. +- State and verify algorithm preconditions, especially magnitude ordering for + quick-sum transforms, normalization assumptions, and overflow/underflow limits. +- The internal two-component `DoubleDouble` constructor currently does not normalize. + Do not assume arbitrary pairs are canonical. Establish the intended normalization, + NaN, infinity, and signed-zero contracts before changing construction, equality, + hashing, ordering, or classification; keep those operations consistent. +- For affected operations, cover cancellation, widely separated magnitudes, zero + and signed zero, subnormals, extreme finite values, infinities, and NaNs. Check + intermediate overflow/underflow even when the final result is representable. +- Derive expected values from independent high-precision references or exact + binary/rational cases, never from the implementation under test. Record the + reference source or reproducible derivation and justify tolerances. +- Do not collapse both components to `double` or `decimal` to validate double-double + accuracy: that can discard precisely the bits being tested. Use component-aware + or higher-precision comparisons, and inspect sign bits when testing signed zero. +- Smoke tests, coverage percentages, and benchmark output are not proof of + numerical correctness. + +## Build and verification + +Run commands from the repository root. Read SDK and runner selection from +`global.json`; read framework, language, and dependency versions from project and +props files rather than duplicating version pins here. + +For documentation-only changes, validate referenced paths and any new or changed +commands, then run `git diff --check`. + +Report commands actually run, their outcomes, and anything not verified. Distinguish +pre-existing failures from failures introduced by the change. + +### Code or build changes + +Use targeted tests while iterating, then run the full sequence: + +```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 +git diff --check +``` + +Use verify-only formatting first. Report unrelated existing violations separately +and validate the changed files; do not reformat the repository to hide failures. + +### Test or coverage pipeline changes + +After the code/build checks, also run: + +```sh +dotnet test --solution Just.PreciseMath.slnx -c Release --no-build --minimum-expected-tests 1 --report-xunit-trx --coverlet --coverlet-output-format cobertura --coverlet-include "[Just.PreciseMath]*" +``` + +Keep MTP-native options and the empty-suite failure. Do not substitute VSTest +collector commands or remove compile assets from `coverlet.MTP`; generated MTP +registration requires them. Inspect actual TRX and Cobertura output, including +failed-test behavior when changing report collection. + +### Packaging changes + +After the code/build checks, run: + +```sh +dotnet pack 0-source/Just.PreciseMath/Just.PreciseMath.csproj -c Release --no-build +``` + +Inspect package metadata and the included `README.md` and `LICENSE`. Do not publish. + +### Dependency changes + +For intentional dependency changes, run `dotnet restore --force-evaluate` and review +all affected `packages.lock.json` files before the locked restore above. Include +lock-file updates in the change; never regenerate them to bypass unexpected restore failures. + +## CI and benchmarks + +- `.gitea/workflows/test-dotnet.yaml` is the CI source of truth. Keep reports + artifact-only: no badges, publication branches, or repository-write publishing jobs. +- Preserve upload-on-failure and both report globs: `1-tests/**/TestResults/**/*.trx` + and `1-tests/**/TestResults/**/*cobertura*.xml`. Cobertura filenames can be timestamped; + `*.cobertura.xml` alone will miss them. +- Cache NuGet packages, not `bin/` or `obj/`; restore remains necessary on a cache hit. + A local restore does not prove CI cache reuse or remote artifact upload succeeded. +- Before upgrading actions, check each action's required Node runtime against both + the Gitea runner and job image. A recent runner can still launch an old Node image; + `ubuntu-latest` is a configured label, not a guarantee of GitHub's environment. +- Do not add an empty benchmark job. When benchmarks are introduced, add an executable + entry point and cases, then smoke-run them in Release with BenchmarkDotNet's Dry job. + Require discovered/executed cases and verify that benchmark failures fail CI. +- Keep smoke validation separate from performance measurement. Do not gate timing + regressions on a shared runner or compare coverage-instrumented measurements. + Use controlled, repeatable baseline/candidate runs before proposing performance gates. diff --git a/README.md b/README.md index 23d117c..cb7292b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Just.PreciseMath -**WIP — initial library implementation and one smoke test; numerical correctness is not yet validated.** +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. -A .NET 10 library being developed for extended-precision floating-point arithmetic -using double-double representations (a high/low pair of `double` values), with -common mathematical functions. This is fixed extended precision, not arbitrary -precision; accuracy guarantees and the public API are not settled. +> **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. ## Planned scope @@ -13,64 +14,35 @@ precision; accuracy guarantees and the public API are not settled. - Common functions including `Abs`, `Sqrt`, `Pow`, `Exp`, and `Log`. - Correctness tests against higher-precision references and performance benchmarks. -The [consolidated legacy review](review-legacy/Review-Revisited-DoubleDouble.md) -is the starting point. Priorities are normalization and special-value contracts -(NaN, infinities, signed zero), corrected constants and scalar subtraction, -consistent comparisons, precision-preserving conversions/formatting, and -range-safe arithmetic and transcendental functions, including cancellation and -subnormal cases. The files in `review-legacy/` contain known defects: they are -reference material only, are not compiled, and should not be used in production. +These are development goals, not a list of currently supported features. -## Layout +## Build and test -- `0-source/Just.PreciseMath/` — library and package metadata. -- `1-tests/Just.PreciseMath.Tests/` — xUnit v3, Shouldly, and MTP-native Coverlet. -- `2-benchmarks/Just.PreciseMath.Benchmarks/` — BenchmarkDotNet scaffold. -- `review-legacy/` — original implementation and numerical reviews. - -## Development - -Use the .NET 10 SDK selected by `global.json` (10.0.1xx, latest installed patch). -Shared settings enable nullable analysis, .NET 10 recommended analyzers, build-time -code-style checks, and warnings as errors. `.editorconfig` retains advisory style -preferences alongside explicitly enforced warning/error rules. +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 +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 +dotnet test --solution Just.PreciseMath.slnx -c Release --no-build --minimum-expected-tests 1 dotnet format Just.PreciseMath.slnx --verify-no-changes --no-restore ``` -Commit each project's `packages.lock.json` with dependency changes. CI caches -NuGet packages using those lock files and always runs `dotnet restore --locked-mode`, -including on cache hits. After intentional dependency updates, run -`dotnet restore --force-evaluate` and review the updated lock files before committing. -Gitea's runner cache must be reachable from the job container, with persistent -storage if the runner itself is recreated. No build outputs are cached. +Test results and coverage reports are available in the `test-results` artifact +on CI workflow runs. -The initial smoke test checks that `DoubleDouble.One` exposes high and low -components of `1.0` and `0.0`. It exercises the test/coverage pipeline, not the -numerical accuracy of the planned library. +## Project structure -CI collects TRX test results and Cobertura coverage for `Just.PreciseMath` only, -excluding test dependencies, and requires at least one test. The `test-results` -artifact contains TRX test results and timestamped Cobertura XML coverage reports, -retained for three days. -Open the workflow run in Gitea Actions and download that artifact. Upload is -attempted even when tests fail; no badges or repository-write token are needed. +- `0-source/Just.PreciseMath/`: library implementation. +- `1-tests/Just.PreciseMath.Tests/`: unit tests. +- `2-benchmarks/Just.PreciseMath.Benchmarks/`: reserved for performance benchmarks. -To generate the same reports locally: +## Contributing -```sh -dotnet test --solution Just.PreciseMath.slnx -c Release --no-build --minimum-expected-tests 1 --report-xunit-trx --coverlet --coverlet-output-format cobertura --coverlet-include "[Just.PreciseMath]*" -``` +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. -Reports are written under each test project's `bin/Release/net10.0/TestResults/`. -The benchmark project deliberately builds as a library without source files; -add an entry point and benchmark cases, then change `OutputType` to `Exe` and run -it in Release without a debugger. No performance results exist yet. +## License -Package metadata uses the development version `0.1.0-dev`. Licensed under the -[MIT License](LICENSE), included in the package. -Do not publish this scaffold as a usable numerical library. +Licensed under the [MIT License](LICENSE).