Skip to content

A unit claimed by two dimensions reports the first one declared, so every angular unit reports Dimensionless #223

Description

@matt-edmondson

What happens

new Radian().Dimension.Name                 // "Dimensionless"
new Radian().Dimension.DimensionalFormula   // empty

Same for Degree, Gradian, Milliradian and Revolution. Every one of them reports the dimension of a bare ratio, which is the exact conflation the angle axis was added to prevent — CLAUDE.md puts it as "without it an angle is the same type as a ratio and an angular speed the same type as a frequency".

RadianPerSecond is fine and reports AngularVelocity, which is the tell: it is claimed by one dimension and the angular displacement units are claimed by two.

Why

dimensions.json lists those five units in availableUnits on both Dimensionless and AngularDisplacement. UnitsGenerator builds a unit-name → dimension-names map and then, in BuildUnitClass:

string dimensionExpr = dims.Count > 0
    ? $"PhysicalDimensions.{dims[0]}"
    : "null!";

dims is in physicalDimensions declaration order and Dimensionless is the first entry in the file, so a unit it claims can never report anything else. The interfaces are unaffected — Radian is emitted as IUnit, IDimensionlessUnit, IAngularDisplacementUnit, so it is only the singular Dimension property that has to pick, and it picks by file position.

Six units are claimed twice in all:

Unit Claimed by
Radian Dimensionless, AngularDisplacement
Degree Dimensionless, AngularDisplacement
Gradian Dimensionless, AngularDisplacement
Milliradian Dimensionless, AngularDisplacement
Revolution Dimensionless, AngularDisplacement
SquareMeter Area, NuclearCrossSection

SquareMeter is the benign one and is worth keeping in view rather than fixing: Area and NuclearCrossSection are the same exponents (), one of the 72-dimensions-over-63-exponent-vectors collisions the nominal layer exists for, so whichever wins gives the same eight numbers and only Dimension.Name differs. The angular five are not benign, because the two candidates disagree about the exponents themselves.

Why it matters outside this repository

ktsu.Schema's C++ reflection table carries eight exponents per member, and it derives them rather than restating them: the member names a unit, UnitRegistry resolves it, and the unit's DimensionInfo supplies the numbers. That is the property the whole table rests on — it cannot drift from the unit beside it. A member measured in radians therefore reaches C++ as .dimension = { 0, 0, 0, 0, 0, 0, 0, 0 }, which is byte-for-byte what a member that measures nothing emits. A consumer reading the table cannot tell an angle from a flag.

Two ways to fix it, and they are not equivalent

  1. Prefer the more specific claim. When a unit is claimed by several dimensions, report one whose dimensionalFormula is non-empty in preference to one whose is empty. Nothing is removed: Radian still implements IDimensionlessUnit, so Ratio<T>.FromRadian and Ratio<T>.In(new Radian()) still compile. Only the value of Dimension changes, and IUnit.Dimension has no consumer inside this repository — PhysicalQuantityCore compares the quantity's dimension, not the unit's. SquareMeter is left to first-wins, since both its claims are non-empty and identical.

  2. Stop claiming them. Remove the five from Dimensionless.availableUnits, so an angle is not a ratio in the metadata either. This is the stronger statement and the one the eight-axis model actually implies, but it deletes Ratio<T>.FromRadian, FromDegree, FromGradian, FromMilliradian and FromRevolution from a shipped package — the same breaking-change objection that keeps the four SEM008 operators emitted.

(1) is non-breaking and fixes the observable bug; (2) is the honest one and is breaking. They are also composable: (1) now, (2) at the next major.

Either way the fix wants a test that a unit claimed twice reports the specific dimension, since nothing today would notice the ordering changing back.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions