Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The opt-in lives in `.sonarlint/sonar-local.props` (analyzer package) and `.sona
| `Semantics.Color` | Physically-grounded color types. Canonical linear-RGB `Color` hub plus color-space satellites (`Srgb`, `Hsl`, `Hsv`, `Oklab`, `Oklch`); every type converts to and from every other, routed through the nearest shared hub (`Srgb` within the sRGB family, `Oklab` within the perceptual family, linear `Color` across families) so no conversion takes a redundant gamma round-trip. Also WCAG accessibility tooling, HSL/perceptual adjustment operations (lighten/saturate/hue/invert), and `NamedColors`. Targets `net8.0`–`net10.0` + `netstandard2.0`/`netstandard2.1`. |
| `Semantics.Quantities` | Hand-written runtime types (`IPhysicalQuantity<TSelf, T>`, `PhysicalQuantityCore`, `IVector0`..`IVector4`, `UnitSystem`) plus generator output under `Generated/`. Every generated quantity is a `readonly record struct`. |
| `Semantics.SourceGenerators` | Roslyn incremental generators that emit quantity types, units, conversions, magnitudes, physical constants, and storage-type helpers from metadata. Only the physics-specific half lives here — `Models/`, `Metadata/`, `Generators/`, and the bindings in `SemanticsGenerator`/`SemanticsDiagnostics`/`Emit`. The C# syntax templates come from `ktsu.CodeBlocker.Templates`; the metadata-driven generator base, metadata loading and the diagnostic catalogue come from `ktsu.SourceGeneratorToolkit` (#181, #192). |
| `Semantics.Quantities.{Double,Float,Decimal}` | Props-only satellite packages. Each ships a `buildTransitive` props file (generated by `scripts/Generate-AliasProps.ps1`) that injects global-using aliases binding every quantity to one storage type, so consumers write `Mass` instead of `Mass<double>`. |
| `Semantics.Quantities.{Double,Float,Decimal,Precise}` | Props-only satellite packages. Each ships a `buildTransitive` props file (generated by `scripts/Generate-AliasProps.ps1`) that injects global-using aliases binding every quantity to one storage type, so consumers write `Mass` instead of `Mass<double>`. `Precise` binds to `ktsu.PreciseNumber.PreciseNumber` and is the one whose storage type comes from a package rather than being a C# keyword, so it carries a `PackageReference` the others do not; the core `Semantics.Quantities` still has no PreciseNumber dependency. |
| `Semantics.Vocabulary` | **Shared source, not a project.** Resolves `dimensions.json` into the quantities and operators it describes and separates out what cannot be honoured. Compiled into both `Semantics.SourceGenerators` and `Semantics.Cpp` via `Compile Include`; see its README for why source rather than an assembly, and what that costs. |
| `Semantics.Cpp` | The C++ projection of the quantity vocabulary, in its own project because `ktsu.Coder` ships no `net8.0`. Reads `dimensions.json` and emits one C++ class per dimension, per vector form and per named overload, plus the declared relationships as operators. |
| `Semantics.Cpp.Test` | Its tests, including ones that compile the whole generated vocabulary with `g++`/`clang++`, assert what it means through `static_assert`, and check that a dimensionally wrong product — scalar or componentwise — is refused by the compiler. Also `SevenTargetProjectionTests`, which is not about C++ at all: see below. |
Expand Down Expand Up @@ -381,7 +381,7 @@ var converted = sourceString.As<SourceType, TargetType>();
- Edit `Semantics.SourceGenerators/Metadata/dimensions.json` to add a dimension, vector form, semantic overload, or relationship.
- Rebuild `Semantics.SourceGenerators` and the consuming `Semantics.Quantities` project; emitted files appear in `Semantics.Quantities/Generated/Semantics.SourceGenerators/<GeneratorName>/`.
- Treat generator output as committed source. Diff it before commit so accidental regressions are visible.
- After adding or renaming a quantity, regenerate the storage-type alias props with `pwsh scripts/Generate-AliasProps.ps1` (it reads the generated catalogue and rewrites `Semantics.Quantities.{Double,Float,Decimal}/buildTransitive/*.props`) and commit them. The `verify-generated` workflow rebuilds, regenerates, and fails the PR if either the generated sources or the alias props drift.
- After adding or renaming a quantity, regenerate the storage-type alias props with `pwsh scripts/Generate-AliasProps.ps1` (it reads the generated catalogue and rewrites `Semantics.Quantities.{Double,Float,Decimal,Precise}/buildTransitive/*.props`) and commit them. The `verify-generated` workflow rebuilds, regenerates, and fails the PR if either the generated sources or the alias props drift.
- Factory names are the **singular lemma** (#49). The generator emits `From{name}` using each unit's `name` from `units.json` verbatim (e.g. `Length.FromMeter`, `Mass.FromKilogram`, `Speed.FromMeterPerSecond`, `Length.FromFoot`, `Frequency.FromHertz`). The rule is purely mechanical, so `name` must itself be the singular lemma — including compounds, whose leading noun is singular too (`MeterPerSecond`, `RevolutionPerMinute`, `PartPerMillion`, not `MeterPerSecond`/`RevolutionPerMinute`/`PartPerMillion`). There is no `factoryName` field and no pluralisation step; the generator never has to know English pluralisation.
- Generator diagnostics:
- **SEM001** — a relationship in `dimensions.json` references a dimension that does not exist (typo or rename). The operator is silently dropped.
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<!-- Test-only: Semantics ships no JSON converter, so the tests register this factory the
same way a consumer is expected to. Not referenced by any shipping project. -->
<PackageVersion Include="ktsu.RoundTripStringJsonConverter" Version="1.0.58" />
<PackageVersion Include="ktsu.PreciseNumber" Version="2.0.2" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.6.1" />
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Each package has its own README with the full API surface, examples, and referen
| [`ktsu.Semantics.Quantities.Double`](Semantics.Quantities.Double/README.md) | `double` storage-type aliases | [README](Semantics.Quantities.Double/README.md) |
| [`ktsu.Semantics.Quantities.Float`](Semantics.Quantities.Float/README.md) | `float` storage-type aliases | [README](Semantics.Quantities.Float/README.md) |
| [`ktsu.Semantics.Quantities.Decimal`](Semantics.Quantities.Decimal/README.md) | `decimal` storage-type aliases | [README](Semantics.Quantities.Decimal/README.md) |
| [`ktsu.Semantics.Quantities.Precise`](Semantics.Quantities.Precise/README.md) | `ktsu.PreciseNumber` storage-type aliases | [README](Semantics.Quantities.Precise/README.md) |
| [`ktsu.Semantics.Music`](Semantics.Music/README.md) | Musical value types and harmonic analysis | [README](Semantics.Music/README.md) |
| [`ktsu.Semantics.Color`](Semantics.Color/README.md) | Linear/perceptual color with accessibility tooling | [README](Semantics.Color/README.md) |

Expand Down
1 change: 1 addition & 0 deletions Semantics.Quantities.Decimal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ The aliases are project-wide global usings keyed on the bare type name (`Mass`,
- [`ktsu.Semantics.Quantities.Double`](../Semantics.Quantities.Double/README.md)
- [`ktsu.Semantics.Quantities.Float`](../Semantics.Quantities.Float/README.md)
- `ktsu.Semantics.Quantities.Decimal` (this package)
- [`ktsu.Semantics.Quantities.Precise`](../Semantics.Quantities.Precise/README.md)

A project that genuinely needs mixed storage types should skip the alias packages and reference `ktsu.Semantics.Quantities` directly, writing the closed generic (`Mass<decimal>`) explicitly.

Expand Down
1 change: 1 addition & 0 deletions Semantics.Quantities.Double/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ The aliases are project-wide global usings keyed on the bare type name (`Mass`,
- `ktsu.Semantics.Quantities.Double` (this package)
- [`ktsu.Semantics.Quantities.Float`](../Semantics.Quantities.Float/README.md)
- [`ktsu.Semantics.Quantities.Decimal`](../Semantics.Quantities.Decimal/README.md)
- [`ktsu.Semantics.Quantities.Precise`](../Semantics.Quantities.Precise/README.md)

A project that genuinely needs mixed storage types should skip the alias packages and reference `ktsu.Semantics.Quantities` directly, writing the closed generic (`Mass<double>`) explicitly.

Expand Down
1 change: 1 addition & 0 deletions Semantics.Quantities.Float/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ The aliases are project-wide global usings keyed on the bare type name (`Mass`,
- [`ktsu.Semantics.Quantities.Double`](../Semantics.Quantities.Double/README.md)
- `ktsu.Semantics.Quantities.Float` (this package)
- [`ktsu.Semantics.Quantities.Decimal`](../Semantics.Quantities.Decimal/README.md)
- [`ktsu.Semantics.Quantities.Precise`](../Semantics.Quantities.Precise/README.md)

A project that genuinely needs mixed storage types should skip the alias packages and reference `ktsu.Semantics.Quantities` directly, writing the closed generic (`Mass<float>`) explicitly.

Expand Down
101 changes: 101 additions & 0 deletions Semantics.Quantities.Precise/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# ktsu.Semantics.Quantities.Precise

> Storage-type aliases that bind every `ktsu.Semantics.Quantities` type to `ktsu.PreciseNumber.PreciseNumber`, so you write `Mass` instead of `Mass<PreciseNumber>`, project-wide.

[![License](https://img.shields.io/github/license/ktsu-dev/Semantics.svg?label=License&logo=nuget)](../LICENSE.md)
[![NuGet Version](https://img.shields.io/nuget/v/ktsu.Semantics.Quantities.Precise?label=Stable&logo=nuget)](https://nuget.org/packages/ktsu.Semantics.Quantities.Precise)
[![NuGet Version](https://img.shields.io/nuget/vpre/ktsu.Semantics.Quantities.Precise?label=Latest&logo=nuget)](https://nuget.org/packages/ktsu.Semantics.Quantities.Precise)
[![NuGet Downloads](https://img.shields.io/nuget/dt/ktsu.Semantics.Quantities.Precise?label=Downloads&logo=nuget)](https://nuget.org/packages/ktsu.Semantics.Quantities.Precise)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/ktsu-dev/Semantics?label=Commits&logo=github)](https://github.com/ktsu-dev/Semantics/commits/main)
[![GitHub contributors](https://img.shields.io/github/contributors/ktsu-dev/Semantics?label=Contributors&logo=github)](https://github.com/ktsu-dev/Semantics/graphs/contributors)
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ktsu-dev/Semantics/dotnet.yml?label=Build&logo=github)](https://github.com/ktsu-dev/Semantics/actions)

`ktsu.Semantics.Quantities.Precise` is a satellite of [`ktsu.Semantics.Quantities`](../Semantics.Quantities/README.md) in the [ktsu.Semantics](../README.md) family. Read the core package README first for the quantity API itself.

## Introduction

Every quantity in `ktsu.Semantics.Quantities` is generic over its numeric storage type, so you normally write `Mass<PreciseNumber>`, `Speed<PreciseNumber>`, and so on. If a project uses one storage type throughout, that generic argument is noise.

This package is props-only. It ships no assembly, just a `buildTransitive` props file that injects one C# global-using alias per quantity, binding each open generic type to [`ktsu.PreciseNumber.PreciseNumber`](https://nuget.org/packages/ktsu.PreciseNumber). Reference it and you can write `Mass`, `Speed`, `Force3D` with no generic argument, and every quantity resolves to its `PreciseNumber` form. The aliases are real `Mass<PreciseNumber>` (and so on), so they interoperate with the entire API with no conversion.

Installing this package also pulls in the matching version of `ktsu.Semantics.Quantities` and `ktsu.PreciseNumber` as dependencies, so it is the only reference you need. The core quantities package does not depend on `ktsu.PreciseNumber`; that dependency arrives only with this package.

## Why this storage type

`PreciseNumber` is an arbitrary-precision type — a `BigInteger` significand with a decimal exponent — so a unit factor is applied at the precision the value carries rather than at the 15 to 17 significant digits a `double` holds. Conversions that terminate come out exact:

```csharp
Length.FromFoot(PreciseNumber.One).In(Units.Inch) // exactly 12
1.0 * 0.3048 / 0.0254 // 12.000000000000002 in double
```

Roots are computed in the storage type's own arithmetic rather than through a `double` round trip, so a vector length keeps its digits too:

```csharp
Velocity3D.One.Length() // 1.73205080756887729352744634150587236694280525381038
```

This costs speed and allocation against `double`, so it suits work where the error budget matters more than throughput — long-horizon integration, unit-conversion chains, and reference values to check a faster pipeline against.

Two limits worth knowing. The logarithmic scales (`Decibels`, `Cents`, `PH`, and the rest) still compute through `double` whatever the storage type. And a quotient that does not terminate is rounded to a precision derived from its operands, not carried forever.

## Installation

### Package Manager Console

```powershell
Install-Package ktsu.Semantics.Quantities.Precise
```

### .NET CLI

```bash
dotnet add package ktsu.Semantics.Quantities.Precise
```

### Package Reference

```xml
<PackageReference Include="ktsu.Semantics.Quantities.Precise" Version="x.y.z" />
```

## Usage Example

```csharp
using ktsu.Semantics.Quantities; // types resolve to their PreciseNumber form via the injected aliases
using ktsu.PreciseNumber;

Mass mass = Mass.FromKilogram(10.ToPreciseNumber());
Speed speed = Speed.FromMeterPerSecond(15.ToPreciseNumber());
Mass total = mass + Mass.FromKilogram(2.ToPreciseNumber()); // still a Mass<PreciseNumber>, full identity

Force3D f = new() { X = 3.ToPreciseNumber(), Y = 4.ToPreciseNumber(), Z = PreciseNumber.Zero };
ForceMagnitude mag = f.Magnitude(); // exactly 5
```

Under the hood the injected alias looks like this (one line per quantity, roughly 220 in total):

```xml
<Using Include="ktsu.Semantics.Quantities.Mass&lt;ktsu.PreciseNumber.PreciseNumber&gt;" Alias="Mass" />
```

## Use exactly one storage-type alias package per project

The aliases are project-wide global usings keyed on the bare type name (`Mass`, `Speed`, ...). Referencing two flavor packages in the same project would define the same alias name twice (one bound to `PreciseNumber`, one to another type), which is a compile error. So reference exactly one of:

- [`ktsu.Semantics.Quantities.Double`](../Semantics.Quantities.Double/README.md)
- [`ktsu.Semantics.Quantities.Float`](../Semantics.Quantities.Float/README.md)
- [`ktsu.Semantics.Quantities.Decimal`](../Semantics.Quantities.Decimal/README.md)
- `ktsu.Semantics.Quantities.Precise` (this package)

A project that genuinely needs mixed storage types should skip the alias packages and reference `ktsu.Semantics.Quantities` directly, writing the closed generic (`Mass<PreciseNumber>`) explicitly.

The alias lists are generated from the quantity catalogue by `scripts/Generate-AliasProps.ps1` and validated in CI, so they stay in lockstep with the quantities the core package emits.

## Contributing

Contributions are welcome! Feel free to open issues or submit pull requests.

## License

This project is licensed under the MIT License. See the [LICENSE.md](../LICENSE.md) file for details.
33 changes: 33 additions & 0 deletions Semantics.Quantities.Precise/Semantics.Quantities.Precise.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project>
<Sdk Name="Microsoft.NET.Sdk" />
<Sdk Name="ktsu.Sdk" />

<PropertyGroup>
<!-- Matches Semantics.Quantities (which requires INumber<T>, so net8.0+). -->
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>
<!-- Props-only package: no compiled assembly, just the buildTransitive aliases. -->
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- No assembly means no symbols and no source to ship. ktsu.Sdk defaults IncludeSource=true,
which emits an empty .snupkg that nuget.org rejects with HTTP 400 ("does not contain any
symbol (.pdb) files"), aborting publish. Suppress symbol/source package generation. -->
<IncludeSymbols>false</IncludeSymbols>
<IncludeSource>false</IncludeSource>
<Description>Storage-type aliases for ktsu.Semantics.Quantities. Reference this package to write `Mass` instead of `Mass&lt;PreciseNumber&gt;` (and every other quantity) bound to ktsu.PreciseNumber, project-wide. Unit factors and vector lengths are then exact to the precision the value carries rather than to a double. Use one storage-type alias package per project.</Description>
<!-- NU5128: a dependencies-only package legitimately has no lib/ assemblies. -->
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>

<ItemGroup>
<None Include="buildTransitive\ktsu.Semantics.Quantities.Precise.props" Pack="true" PackagePath="buildTransitive\" />
</ItemGroup>

<ItemGroup>
<!-- Packs as a NuGet dependency on the matching ktsu.Semantics.Quantities version. -->
<ProjectReference Include="..\Semantics.Quantities\Semantics.Quantities.csproj" />
<!-- The aliases name ktsu.PreciseNumber.PreciseNumber, so a consumer of this package needs that
package too. Semantics.Quantities itself does not reference it: PreciseNumber support is
opt-in through this package alone, and the core stays free of the dependency. -->
<PackageReference Include="ktsu.PreciseNumber" />
</ItemGroup>

</Project>
Loading
Loading