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() {