Skip to content

Volume.FromMilliliter(1) is 1 litre (1e-3 m³), 1000× too large: Milli prefix is applied to m³ instead of the litre #286

Description

@matt-edmondson

What's wrong

In Semantics.SourceGenerators/Metadata/units.json (around line 199), Milliliter is defined only as "magnitude": "Milli", with no conversion factor:

{ "name": "Milliliter", "symbol": "mL", "system": "SIDerived", "magnitude": "Milli" }

UnitsGenerator.BuildToBaseFactorExpression applies the magnitude to the dimension's SI base unit. For Volume that base unit is m³, not the litre, so the generated ToBaseFactor is MetricMagnitudes.Milli = 1e-3 m³. That is one litre. The correct factor is 1e-6 m³.

The same pattern gives correct results elsewhere only because those units are prefixed forms of the base unit itself. For example, Gram = Milli × kg = 1e-3 kg is correct, and Millimeter = Milli × m is correct. The litre is not the base unit of Volume, so the prefix alone is wrong here.

Failure scenario

  • Volume<double>.FromMilliliter(1.0).Value returns 0.001 (m³). It should return 1e-6.
  • Everything that goes through the unit's factor is off by 1000×: Capacity.FromMilliliter, IUnit.ToBaseFactor, ToBaseFactorAs<T>(), and every In(new Milliliter()).

For example, 5 mL of a dose or reagent comes out as 5 L. No test references Milliliter, so nothing catches this.

Suggested fix

Give the unit both parts:

"magnitude": "Milli",
"conversionFactor": "LiterToCubicMeters"

BuildToBaseFactorExpression already handles magnitude × factor, which produces 1e-3 × 1e-3 = 1e-6. Then regenerate.

Acceptance criteria

  • Volume<double>.FromMilliliter(1000).In(new Liter()) equals 1 (within tolerance).
  • Volume<double>.FromMilliliter(1).Value equals 1e-6 m³.
  • Check the other units in units.json that carry a magnitude in dimensions whose base unit is not the unprefixed form of that unit. Any new ones found get the same treatment and a test.

Activity

  1. matt-edmondson commented on Sep 27, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    • Category: Bug
    • Priority: High. FromMilliliter is off by 1000× and nothing fails, so 5 mL silently becomes 5 L. It flows through ToBaseFactor, In(...) and Capacity, and no test covers it.
    • Area / suggested assignee: Semantics.SourceGenerators/Metadata/units.json (the Milliliter entry) and UnitsGenerator.BuildToBaseFactorExpression. Owner: @matt-edmondson
    • Duplicates / in progress: None found. No open PR covers this. The open dependency bump Bump the ktsu group with 15 updates #278 doesn't touch unit metadata.
    • Next step: Add "conversionFactor": "LiterToCubicMeters" to Milliliter and regenerate. Then audit every magnitude-bearing unit whose dimension's base unit isn't that unit's unprefixed form, for example other litre-based units or prefixed non-SI units. A generator-side check or a test that round-trips every unit against a known value would stop this class of bug from coming back.

    Generated by Claude Code

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

Metadata

Metadata

Labels

readyFully specified; implement as written

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions