diff --git a/CLAUDE.md b/CLAUDE.md
index 00df749..65f2ec6 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -304,7 +304,13 @@ arithmetic, so a `decimal` length has 28 significant digits. A value a `double`
`BigInteger` of 2^2048, is scaled by powers of four into [1, 4) for the seed, and the root is scaled
back by the matching power of two. A root that does not settle throws `ArithmeticException` rather
than returning an estimate. The logarithmic scales and the hand-written audio types still compute
-through `double`.
+through `double`, and as of #235 every generated conversion says so in its own XML ``
+rather than only here — the method tooltip is where a `decimal` or `PreciseNumber` consumer spends
+the precision, so that is where the cost is stated. `SourceGeneratorTests` counts the remarks
+against the conversions that actually drop to `double`, so a conversion added later cannot ship
+undocumented. If the scales ever compute in `T`'s own arithmetic (option 3 on #235, which wants
+`Exp`/`Log` on `PreciseNumber` first — ktsu-dev/PreciseNumber#81), the remarks become false and
+should be removed with the `double` path rather than left to rot.
`StorageMath` is public API, not just the generator's helper (#239): an application doing its own
vector math over quantities would otherwise reimplement the root, and worse. `Cbrt`, `RootN` and
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Cents.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Cents.g.cs
index 1d597af..dcae475 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Cents.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Cents.g.cs
@@ -16,6 +16,14 @@ namespace ktsu.Semantics.Quantities;
/// An octave is 1200 cents: cents = 1200·log2(frequencyRatio).
/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a
/// standalone companion (from logarithmic.json) rather than a physical dimension.
+///
+/// Precision: the conversions to and from the linear counterparts compute in
+/// whatever is, so a value carried in a
+/// storage type wider than — , or
+/// PreciseNumber — is accurate to about fifteen significant digits once it has been
+/// through one. Arithmetic, comparison and the raw factory keep every digit
+/// holds; only the logarithm and its inverse do not.
+///
///
/// The floating-point storage type.
/// The scale value.
@@ -34,6 +42,13 @@ public readonly partial record struct Cents(T Value) : IComparable>
///
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static Cents FromFrequencyRatio(Ratio linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -44,6 +59,13 @@ public static Cents FromFrequencyRatio(Ratio linear)
/// Converts this interval to a frequency ratio using ratio = 2^(cents/1200).
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public Ratio ToFrequencyRatio()
{
double scaleValue = double.CreateChecked(Value);
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Decibels.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Decibels.g.cs
index 6ffaa34..126e8fa 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Decibels.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Decibels.g.cs
@@ -16,6 +16,14 @@ namespace ktsu.Semantics.Quantities;
/// Decibels express ratios on a logarithmic scale. Amplitude/field quantities use dB = 20·log10(ratio); power quantities use dB = 10·log10(ratio). A level of 0 dB is unity.
/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a
/// standalone companion (from logarithmic.json) rather than a physical dimension.
+///
+/// Precision: the conversions to and from the linear counterparts compute in
+/// whatever is, so a value carried in a
+/// storage type wider than — , or
+/// PreciseNumber — is accurate to about fifteen significant digits once it has been
+/// through one. Arithmetic, comparison and the raw factory keep every digit
+/// holds; only the logarithm and its inverse do not.
+///
///
/// The floating-point storage type.
/// The scale value.
@@ -34,6 +42,13 @@ public readonly partial record struct Decibels(T Value) : IComparable
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static Decibels FromGain(Gain linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -44,6 +59,13 @@ public static Decibels FromGain(Gain linear)
/// Converts this level to a linear amplitude gain using gain = 10^(dB/20).
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public Gain ToAmplitude()
{
double scaleValue = double.CreateChecked(Value);
@@ -55,6 +77,13 @@ public Gain ToAmplitude()
///
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static Decibels FromPowerRatio(Ratio linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -65,6 +94,13 @@ public static Decibels FromPowerRatio(Ratio linear)
/// Converts this level to a linear power ratio using ratio = 10^(dB/10).
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public Ratio ToPower()
{
double scaleValue = double.CreateChecked(Value);
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/DirectionalityIndex.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/DirectionalityIndex.g.cs
index 6c6c489..051f614 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/DirectionalityIndex.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/DirectionalityIndex.g.cs
@@ -16,6 +16,14 @@ namespace ktsu.Semantics.Quantities;
/// DI = 10·log10(I_axis / I_average).
/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a
/// standalone companion (from logarithmic.json) rather than a physical dimension.
+///
+/// Precision: the conversions to and from the linear counterparts compute in
+/// whatever is, so a value carried in a
+/// storage type wider than — , or
+/// PreciseNumber — is accurate to about fifteen significant digits once it has been
+/// through one. Arithmetic, comparison and the raw factory keep every digit
+/// holds; only the logarithm and its inverse do not.
+///
///
/// The floating-point storage type.
/// The scale value.
@@ -34,6 +42,13 @@ public readonly partial record struct DirectionalityIndex(T Value) : ICompara
///
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static DirectionalityIndex FromIntensityRatio(Ratio linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -44,6 +59,13 @@ public static DirectionalityIndex FromIntensityRatio(Ratio linear)
/// Converts this index to the linear intensity ratio using ratio = 10^(DI/10).
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public Ratio ToIntensityRatio()
{
double scaleValue = double.CreateChecked(Value);
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/PH.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/PH.g.cs
index 98659b4..ba4dacc 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/PH.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/PH.g.cs
@@ -16,6 +16,14 @@ namespace ktsu.Semantics.Quantities;
/// pH is a logarithmic scale over the linear Concentration quantity (stored in the SI base mol/m³; 1 mol/L = 1000 mol/m³).
/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a
/// standalone companion (from logarithmic.json) rather than a physical dimension.
+///
+/// Precision: the conversions to and from the linear counterparts compute in
+/// whatever is, so a value carried in a
+/// storage type wider than — , or
+/// PreciseNumber — is accurate to about fifteen significant digits once it has been
+/// through one. Arithmetic, comparison and the raw factory keep every digit
+/// holds; only the logarithm and its inverse do not.
+///
///
/// The floating-point storage type.
/// The scale value.
@@ -34,6 +42,13 @@ public readonly partial record struct PH(T Value) : IComparable>
///
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static PH FromHydrogenConcentration(Concentration linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -45,6 +60,13 @@ public static PH FromHydrogenConcentration(Concentration linear)
/// Converts this pH to the equivalent hydrogen-ion concentration using [H⁺] = 10^(−pH) mol/L.
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public Concentration ToHydrogenConcentration()
{
double scaleValue = double.CreateChecked(Value);
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Semitones.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Semitones.g.cs
index bc7c03c..b39fc7c 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Semitones.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/Semitones.g.cs
@@ -16,6 +16,14 @@ namespace ktsu.Semantics.Quantities;
/// An octave is 12 semitones: semitones = 12·log2(frequencyRatio).
/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a
/// standalone companion (from logarithmic.json) rather than a physical dimension.
+///
+/// Precision: the conversions to and from the linear counterparts compute in
+/// whatever is, so a value carried in a
+/// storage type wider than — , or
+/// PreciseNumber — is accurate to about fifteen significant digits once it has been
+/// through one. Arithmetic, comparison and the raw factory keep every digit
+/// holds; only the logarithm and its inverse do not.
+///
///
/// The floating-point storage type.
/// The scale value.
@@ -34,6 +42,13 @@ public readonly partial record struct Semitones(T Value) : IComparable
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static Semitones FromFrequencyRatio(Ratio linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -44,6 +59,13 @@ public static Semitones FromFrequencyRatio(Ratio linear)
/// Converts this interval to a frequency ratio using ratio = 2^(semitones/12).
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public Ratio ToFrequencyRatio()
{
double scaleValue = double.CreateChecked(Value);
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundIntensityLevel.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundIntensityLevel.g.cs
index 260a2a0..ef12bdc 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundIntensityLevel.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundIntensityLevel.g.cs
@@ -16,6 +16,14 @@ namespace ktsu.Semantics.Quantities;
/// SIL is a logarithmic power quantity: SIL = 10·log10(I / I₀) with I₀ = 10⁻¹² W/m².
/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a
/// standalone companion (from logarithmic.json) rather than a physical dimension.
+///
+/// Precision: the conversions to and from the linear counterparts compute in
+/// whatever is, so a value carried in a
+/// storage type wider than — , or
+/// PreciseNumber — is accurate to about fifteen significant digits once it has been
+/// through one. Arithmetic, comparison and the raw factory keep every digit
+/// holds; only the logarithm and its inverse do not.
+///
///
/// The floating-point storage type.
/// The scale value.
@@ -34,6 +42,13 @@ public readonly partial record struct SoundIntensityLevel(T Value) : ICompara
///
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static SoundIntensityLevel FromSoundIntensity(SoundIntensity linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -45,6 +60,13 @@ public static SoundIntensityLevel FromSoundIntensity(SoundIntensity linear
/// Converts this level to the equivalent linear sound intensity using I = I₀·10^(SIL/10).
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public SoundIntensity ToSoundIntensity()
{
double scaleValue = double.CreateChecked(Value);
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundPowerLevel.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundPowerLevel.g.cs
index 695252b..be113e4 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundPowerLevel.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundPowerLevel.g.cs
@@ -16,6 +16,14 @@ namespace ktsu.Semantics.Quantities;
/// SWL is a logarithmic power quantity: SWL = 10·log10(P / P₀) with P₀ = 10⁻¹² W.
/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a
/// standalone companion (from logarithmic.json) rather than a physical dimension.
+///
+/// Precision: the conversions to and from the linear counterparts compute in
+/// whatever is, so a value carried in a
+/// storage type wider than — , or
+/// PreciseNumber — is accurate to about fifteen significant digits once it has been
+/// through one. Arithmetic, comparison and the raw factory keep every digit
+/// holds; only the logarithm and its inverse do not.
+///
///
/// The floating-point storage type.
/// The scale value.
@@ -34,6 +42,13 @@ public readonly partial record struct SoundPowerLevel(T Value) : IComparable<
///
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static SoundPowerLevel FromSoundPower(SoundPower linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -45,6 +60,13 @@ public static SoundPowerLevel FromSoundPower(SoundPower linear)
/// Converts this level to the equivalent linear sound power using P = P₀·10^(SWL/10).
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public SoundPower ToSoundPower()
{
double scaleValue = double.CreateChecked(Value);
diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundPressureLevel.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundPressureLevel.g.cs
index 0f38e16..e47b47c 100644
--- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundPressureLevel.g.cs
+++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.LogarithmicScalesGenerator/SoundPressureLevel.g.cs
@@ -16,6 +16,14 @@ namespace ktsu.Semantics.Quantities;
/// SPL is a logarithmic field quantity: SPL = 20·log10(p / p₀) with p₀ = 20 µPa.
/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a
/// standalone companion (from logarithmic.json) rather than a physical dimension.
+///
+/// Precision: the conversions to and from the linear counterparts compute in
+/// whatever is, so a value carried in a
+/// storage type wider than — , or
+/// PreciseNumber — is accurate to about fifteen significant digits once it has been
+/// through one. Arithmetic, comparison and the raw factory keep every digit
+/// holds; only the logarithm and its inverse do not.
+///
///
/// The floating-point storage type.
/// The scale value.
@@ -34,6 +42,13 @@ public readonly partial record struct SoundPressureLevel(T Value) : IComparab
///
/// The linear .
/// A new . A linear value of zero maps to negative infinity.
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public static SoundPressureLevel FromSoundPressure(SoundPressure linear)
{
double linearValue = double.CreateChecked(linear.Value);
@@ -45,6 +60,13 @@ public static SoundPressureLevel FromSoundPressure(SoundPressure linear)
/// Converts this level to the equivalent linear sound pressure using p = p₀·10^(SPL/20).
///
/// The linear .
+ ///
+ /// Computes in whatever is: the value is
+ /// converted to , the logarithm or power is taken there, and the
+ /// result is converted back. A storage type wider than —
+ /// , or PreciseNumber — therefore keeps about fifteen
+ /// significant digits across this conversion, rather than the precision it is capable of.
+ ///
public SoundPressure ToSoundPressure()
{
double scaleValue = double.CreateChecked(Value);
diff --git a/Semantics.SourceGenerators/Generators/LogarithmicScalesGenerator.cs b/Semantics.SourceGenerators/Generators/LogarithmicScalesGenerator.cs
index 83cb0ce..dfdef78 100644
--- a/Semantics.SourceGenerators/Generators/LogarithmicScalesGenerator.cs
+++ b/Semantics.SourceGenerators/Generators/LogarithmicScalesGenerator.cs
@@ -79,6 +79,14 @@ private static void EmitScale(SourceProductionContext context, LogarithmicScaleD
cb.WriteLine("/// Logarithmic scales don't obey linear arithmetic, so this type is generated as a");
cb.WriteLine("/// standalone companion (from logarithmic.json) rather than a physical dimension.");
+ cb.WriteLine("/// ");
+ cb.WriteLine("/// Precision: the conversions to and from the linear counterparts compute in");
+ cb.WriteLine("/// whatever is, so a value carried in a");
+ cb.WriteLine("/// storage type wider than — , or");
+ cb.WriteLine("/// PreciseNumber — is accurate to about fifteen significant digits once it has been");
+ cb.WriteLine("/// through one. Arithmetic, comparison and the raw factory keep every digit");
+ cb.WriteLine("/// holds; only the logarithm and its inverse do not.");
+ cb.WriteLine("/// ");
cb.WriteLine("/// ");
cb.WriteLine("/// The floating-point storage type.");
cb.WriteLine("/// The scale value.");
@@ -122,6 +130,29 @@ private static void WriteScalarFactory(CodeBlocker cb, LogarithmicScaleDefinitio
cb.NewLine();
}
+ ///
+ /// Emits the remarks stating that a conversion computes in regardless
+ /// of the storage type.
+ ///
+ /// The code blocker to write to.
+ ///
+ /// On the conversion rather than only on the type, because the call site is where a caller
+ /// spends the precision, and the method's own tooltip is what they see there. The storage type
+ /// is otherwise exact — unit factors, metric magnitudes and vector lengths all compute in
+ /// T's own arithmetic — so a scale quietly dropping to fifteen digits is a surprise
+ /// worth stating rather than leaving to be discovered in a result.
+ ///
+ private static void WriteDoublePrecisionRemarks(CodeBlocker cb)
+ {
+ cb.WriteLine("/// ");
+ cb.WriteLine("/// Computes in whatever is: the value is");
+ cb.WriteLine("/// converted to , the logarithm or power is taken there, and the");
+ cb.WriteLine("/// result is converted back. A storage type wider than —");
+ cb.WriteLine("/// , or PreciseNumber — therefore keeps about fifteen");
+ cb.WriteLine("/// significant digits across this conversion, rather than the precision it is capable of.");
+ cb.WriteLine("/// ");
+ }
+
private static void WriteConversion(CodeBlocker cb, LogarithmicScaleDefinition scale, LogarithmicConversionDefinition conversion, string fullType)
{
string linear = conversion.Linear;
@@ -145,6 +176,7 @@ private static void WriteConversion(CodeBlocker cb, LogarithmicScaleDefinition s
cb.WriteLine(Emit.SummaryClose);
cb.WriteLine($"/// The linear .");
cb.WriteLine($"/// A new . A linear value of zero maps to negative infinity.");
+ WriteDoublePrecisionRemarks(cb);
cb.WriteLine($"public static {fullType} {fromName}({linear} linear)");
using (new Scope(cb))
{
@@ -168,6 +200,7 @@ private static void WriteConversion(CodeBlocker cb, LogarithmicScaleDefinition s
cb.WriteLine($"/// {conversion.ToSummary ?? $"Converts this value to the linear {linear}."}");
cb.WriteLine(Emit.SummaryClose);
cb.WriteLine($"/// The linear .");
+ WriteDoublePrecisionRemarks(cb);
cb.WriteLine($"public {linear} {toName}()");
using (new Scope(cb))
{
diff --git a/Semantics.Test/Quantities/SourceGeneratorTests.cs b/Semantics.Test/Quantities/SourceGeneratorTests.cs
index d89d3f6..4bc2c57 100644
--- a/Semantics.Test/Quantities/SourceGeneratorTests.cs
+++ b/Semantics.Test/Quantities/SourceGeneratorTests.cs
@@ -27,6 +27,13 @@ public class SourceGeneratorTests
/// The header every generated file is expected to begin with.
private const string ExpectedHeader = "// Copyright (c) 2023-2026 ktsu-dev contributors\n// ";
+ /// The sentence every logarithmic conversion computing in double is expected to carry.
+ private const string DoublePrecisionRemark =
+ "Computes in whatever is";
+
+ /// The line that marks a conversion body actually dropping to double.
+ private const string DoubleConversionMarker = "= double.CreateChecked(";
+
private static string MetadataDirectory => Path.Combine(AppContext.BaseDirectory, "GeneratorMetadata");
private static GeneratorHarness Harness => new(MetadataDirectory);
@@ -101,6 +108,68 @@ public void QuantitiesGenerator_EmitsTheExpectedBreadthOfTypes()
Assert.IsGreaterThan(50, result.GeneratedSources.Length);
}
+ ///
+ /// Every logarithmic-scale conversion that computes in carries XML
+ /// remarks saying so.
+ ///
+ ///
+ /// The conversions convert to , take the logarithm or power there, and
+ /// convert back, so a or PreciseNumber scale keeps about fifteen
+ /// significant digits across one. Everything else about these types is exact in the storage type,
+ /// which is what makes the drop worth stating at the call site rather than leaving to be found in
+ /// a result. Counted rather than spot-checked, so a conversion added later cannot ship
+ /// undocumented.
+ ///
+ [TestMethod]
+ public void LogarithmicScalesGenerator_DocumentsEveryConversionThatComputesInDouble()
+ {
+ GeneratorRunResult result = Harness.Run(new LogarithmicScalesGenerator());
+
+ Assert.IsGreaterThan(0, result.GeneratedSources.Length, "LogarithmicScalesGenerator produced no sources.");
+
+ int documented = 0;
+ foreach (GeneratedSourceResult source in result.GeneratedSources)
+ {
+ string text = source.SourceText.ToString();
+ int conversions = Occurrences(text, DoubleConversionMarker);
+ int remarks = Occurrences(text, DoublePrecisionRemark);
+
+ Assert.AreEqual(
+ conversions,
+ remarks,
+ $"{source.HintName} has {conversions} conversion(s) computing in double but {remarks} " +
+ $"carrying the precision remarks. A conversion that drops to double has to say so in " +
+ $"its own XML docs, because the method's tooltip is where a caller spending that " +
+ $"precision would see it.");
+
+ documented += remarks;
+ }
+
+ // Reaching zero would mean the double path is gone — issue #235's option 3 landed, and the
+ // conversions compute in T's own arithmetic. The remarks would then be false and should be
+ // removed along with this test, rather than this assertion being relaxed.
+ Assert.IsGreaterThan(0, documented, "No logarithmic-scale conversion carries the precision remarks.");
+ }
+
+ ///
+ /// Counts non-overlapping occurrences of a substring.
+ ///
+ /// The text to search.
+ /// The substring to count.
+ /// The number of occurrences.
+ private static int Occurrences(string text, string value)
+ {
+ int count = 0;
+ for (int index = text.IndexOf(value, StringComparison.Ordinal);
+ index >= 0;
+ index = text.IndexOf(value, index + value.Length, StringComparison.Ordinal))
+ {
+ count++;
+ }
+
+ return count;
+ }
+
[TestMethod]
public void Generator_WithMalformedMetadata_ReportsDiagnosticInsteadOfThrowing()
{