5 Commits

Author SHA1 Message Date
melekhin
587b6d9ed1 IMensuraUnit<U>
All checks were successful
Publish NuGet packages / publish (push) Successful in 52s
2026-06-15 09:37:03 +07:00
melekhin
d49374b482 public IMensuraUnit
All checks were successful
Publish NuGet packages / publish (push) Successful in 51s
2026-06-15 09:00:43 +07:00
melekhin
998624177a Merge branch 'master' of https://lancool.qwertykez.fun/QWERTYkez/QWERTYkez.Mensura
All checks were successful
Publish NuGet packages / publish (push) Successful in 1m1s
2026-06-15 08:39:01 +07:00
melekhin
001b061016 Volumetric operator /(Linear left, Area right) 2026-06-15 08:38:57 +07:00
7c0f89ebbb EFcoreExtension
All checks were successful
Publish NuGet packages / publish (push) Successful in 1m15s
2026-06-13 20:25:29 +07:00
13 changed files with 1371 additions and 1180 deletions

View File

@@ -81,6 +81,24 @@ namespace QWERTYkez.Mensura
sb.AppendLine("}");
spc.AddSource(".ComplexUnits.MensuraBinder.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
sb = new StringBuilder();
sb.AppendLine("using Microsoft.EntityFrameworkCore;");
sb.AppendLine("namespace QWERTYkez.Mensura.Extensions;");
sb.AppendLine("public static partial class EFCoreExtension");
sb.AppendLine("{");
sb.AppendLine(" [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]");
sb.AppendLine(" internal static void AddGeneratedComplexConverters(ModelConfigurationBuilder configurationBuilder)");
sb.AppendLine(" {");
foreach (var structInfo in structs)
{
sb.AppendLine($" configurationBuilder.Properties<{structInfo.TypeName}>().HaveConversion<MensuraUnitConverter<{structInfo.TypeName}>>();");
}
sb.AppendLine(" }");
sb.AppendLine("}");
spc.AddSource(".ComplexUnits.EFCoreExtension.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
});
}
@@ -177,7 +195,7 @@ using System.Runtime.Serialization;
namespace QWERTYkez.Mensura.Units;
[JsonConverter(typeof(UnitJsonConverter<{typeNameZ}>))]
public readonly partial record struct {typeNameZ} : IMensuraUnit<{typeNameZ}>, IEquatable<{typeNameZ}>, IMensuraUnit
public readonly partial record struct {typeNameZ} : IEquatable<{typeNameZ}>, IMensuraUnit, IMensuraUnit<{typeNameZ}>
{
[JsonInclude, DataMember, JsonPropertyName(""v""), Obsolete] // для JSON / EF на случай сбоев, если пробелма с _Value
@@ -194,6 +212,16 @@ public readonly partial record struct {typeNameZ} : IMensuraUnit<{typeNameZ}>, I
public static explicit operator double({typeNameZ} unit) => unit._Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public {typeNameZ} Abs() => new(Math.Abs(_Value));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal R Pow2_Internal<R>() where R : struct, IMensuraUnit, IEquatable<R> => (_Value * _Value).ToUnit<R>();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal R Sqrt_Internal<R>() where R : struct, IMensuraUnit, IEquatable<R> => Math.Sqrt(_Value).ToUnit<R>();
[JsonIgnore, IgnoreDataMember] public bool IsPositive => _Value >= 0;
[JsonIgnore, IgnoreDataMember] public bool IsGreaterThanZero => _Value > 0;
[JsonIgnore, IgnoreDataMember] public bool IsNegative => double.IsNegative(_Value);

View File

@@ -67,6 +67,24 @@ namespace QWERTYkez.Mensura
sb.AppendLine("}");
spc.AddSource(".Units.MensuraBinder.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
sb = new StringBuilder();
sb.AppendLine("using Microsoft.EntityFrameworkCore;");
sb.AppendLine("namespace QWERTYkez.Mensura.Extensions;");
sb.AppendLine("public static partial class EFCoreExtension");
sb.AppendLine("{");
sb.AppendLine(" [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]");
sb.AppendLine(" internal static void AddGeneratedConverters(ModelConfigurationBuilder configurationBuilder)");
sb.AppendLine(" {");
foreach (var structInfo in structs)
{
sb.AppendLine($" configurationBuilder.Properties<{structInfo.TypeName}>().HaveConversion<MensuraUnitConverter<{structInfo.TypeName}>>();");
}
sb.AppendLine(" }");
sb.AppendLine("}");
spc.AddSource(".Units.EFCoreExtension.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
});
}
@@ -126,7 +144,7 @@ using System.Runtime.Serialization;
namespace QWERTYkez.Mensura.Units;
[JsonConverter(typeof(UnitJsonConverter<{typeName}>))]
public readonly partial record struct {typeName} : IMensuraUnit<{typeName}>, IEquatable<{typeName}>, IMensuraUnit
public readonly partial record struct {typeName} : IEquatable<{typeName}>, IMensuraUnit, IMensuraUnit<{typeName}>
{
[JsonInclude, DataMember, JsonPropertyName(""v""), Obsolete] // для JSON / EF на случай сбоев, если пробелма с _Value
internal double Value { get => _Value; init => _Value = value; }

View File

@@ -7,16 +7,16 @@
private static readonly double scalarDouble = 500.0; // прибавляемое значение в мм
// Коллекции единиц
private static readonly Length[] unitsArray = new[] { Length.Meter, Length._MilliMeter };
private static readonly Length?[] nullableUnitsArray = new Length?[] { Length.Meter, null, Length._MilliMeter };
private static readonly List<Length> unitsList = new List<Length> { Length.Meter, Length._MilliMeter };
private static readonly List<Length?> nullableUnitsList = new List<Length?> { Length.Meter, null, Length._MilliMeter };
private static readonly Length[] unitsArray = [Length.Meter, Length._MilliMeter];
private static readonly Length?[] nullableUnitsArray = [Length.Meter, null, Length._MilliMeter];
private static readonly List<Length> unitsList = [Length.Meter, Length._MilliMeter];
private static readonly List<Length?> nullableUnitsList = [Length.Meter, null, Length._MilliMeter];
// Коллекции double
private static readonly double[] doubleArray = new double[] { 200.0, 300.0 };
private static readonly double?[] nullableDoubleArray = new double?[] { 200.0, null, 300.0 };
private static readonly List<double> doubleList = new List<double> { 200.0, 300.0 };
private static readonly List<double?> nullableDoubleList = new List<double?> { 200.0, null, 300.0 };
private static readonly double[] doubleArray = [200.0, 300.0];
private static readonly double?[] nullableDoubleArray = [200.0, null, 300.0];
private static readonly List<double> doubleList = [200.0, 300.0];
private static readonly List<double?> nullableDoubleList = [200.0, null, 300.0];
// ====================== 1. T[] + double ======================
[Fact]
@@ -303,7 +303,7 @@
[Fact]
public void Plus_NullArray_ReturnsNull()
{
Length[] nullArray = null;
Length[] nullArray = null!;
var result = nullArray.Plus<Length>(5.0);
Assert.Null(result);
}
@@ -311,7 +311,7 @@
[Fact]
public void Plus_NullList_ReturnsNull()
{
List<Length> nullList = null;
List<Length> nullList = null!;
var result = nullList.Plus<Length>(5.0);
Assert.Null(result);
}

View File

@@ -20,6 +20,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="10.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
@@ -31,8 +34,6 @@
<ItemGroup>
<ProjectReference Include="..\QWERTYkez.Mensura\QWERTYkez.Mensura.csproj" />
<InternalsVisibleTo Include="..\QWERTYkez.Mensura\QWERTYkez.Mensura.csproj" />
<ProjectReference Include="..\QWERTYkez.Mensura.Generator\QWERTYkez.Mensura.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

View File

@@ -0,0 +1,98 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace QWERTYkez.Mensura.Tests
{
public class TestEntity1
{
public int Id { get; set; }
public Length Length { get; set; }
}
public class TestEntity2
{
public int Id { get; set; }
public double Length { get; set; }
}
public class SharedDbContext(DbContextOptions<SharedDbContext> options) : DbContext(options)
{
public DbSet<TestEntity1> Entities1 { get; set; }
public DbSet<TestEntity2> Entities2 { get; set; }
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.UseMensuraUnits();
}
}
public class IdentityConversionTest : IDisposable
{
private readonly SqliteConnection _connection;
public IdentityConversionTest()
{
_connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
}
public void Dispose()
{
_connection?.Dispose();
GC.SuppressFinalize(this);
}
[Fact]
public void Length_And_Double_Are_Stored_Identically()
{
// 1. Сохраняем Length → читаем как double
double savedDoubleFromLength;
using (var context = new SharedDbContext(new DbContextOptionsBuilder<SharedDbContext>().UseSqlite(_connection).Options))
{
context.Database.EnsureCreated();
var expectedLength = Length.Meter; // 1000 мм
context.Entities1.Add(new TestEntity1 { Length = expectedLength });
context.SaveChanges();
using var cmd = context.Database.GetDbConnection().CreateCommand();
cmd.CommandText = "SELECT Length FROM Entities1";
savedDoubleFromLength = (double)cmd.ExecuteScalar()!;
}
// 2. Сохраняем double → читаем как Length
double savedDoubleFromDouble = 1234.5;
using (var context = new SharedDbContext(new DbContextOptionsBuilder<SharedDbContext>().UseSqlite(_connection).Options))
{
context.Database.EnsureCreated();
context.Entities2.Add(new TestEntity2 { Length = savedDoubleFromDouble });
context.SaveChanges();
using var cmd = context.Database.GetDbConnection().CreateCommand();
cmd.CommandText = "SELECT Length FROM Entities2";
savedDoubleFromDouble = (double)cmd.ExecuteScalar()!; // должно быть 1234.5
}
// 3. Проверяем, что сырые значения сохранены корректно
Assert.Equal(1000.0, savedDoubleFromLength);
Assert.Equal(1234.5, savedDoubleFromDouble);
// 4. Проверяем обратимость: читаем из Entities1 как TestEntity2
using (var context = new SharedDbContext(new DbContextOptionsBuilder<SharedDbContext>().UseSqlite(_connection).Options))
{
var fromLengthTable = context.Entities2
.FromSqlRaw("SELECT Id, Length FROM Entities1")
.First();
Assert.Equal(1000.0, fromLengthTable.Length);
}
// 5. Проверяем обратимость: читаем из Entities2 как TestEntity1
using (var context = new SharedDbContext(new DbContextOptionsBuilder<SharedDbContext>().UseSqlite(_connection).Options))
{
var fromDoubleTable = context.Entities1
.FromSqlRaw("SELECT Id, Length FROM Entities2")
.First();
Assert.Equal(1234.5, (double)fromDoubleTable.Length);
}
}
}
}

View File

@@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace QWERTYkez.Mensura.Extensions;
public static partial class EFCoreExtension
{
/// <summary>
/// Регистрирует ValueConverter для всех типов, реализующих IMensuraUnit.
/// </summary>
public static void UseMensuraUnits(this ModelConfigurationBuilder configurationBuilder)
{
AddGeneratedConverters(configurationBuilder);
AddGeneratedComplexConverters(configurationBuilder);
}
internal class MensuraUnitConverter<U>()
: ValueConverter<U, double>(unit => unit.ToDouble(), value => value.ToUnit<U>())
where U : struct, IMensuraUnit, IEquatable<U> { }
}

View File

@@ -1,5 +1,19 @@
namespace QWERTYkez.Mensura;
internal interface IMensuraUnit { }
public interface IMensuraUnit
{
[JsonIgnore, IgnoreDataMember] public bool IsPositive { get; }
[JsonIgnore, IgnoreDataMember] public bool IsGreaterThanZero { get; }
[JsonIgnore, IgnoreDataMember] public bool IsNegative { get; }
[JsonIgnore, IgnoreDataMember] public bool IsZero { get; }
[JsonIgnore, IgnoreDataMember] public bool IsNaN { get; }
[JsonIgnore, IgnoreDataMember] public bool IsFinite { get; }
[JsonIgnore, IgnoreDataMember] public bool IsInfinity { get; }
[JsonIgnore, IgnoreDataMember] public bool IsPositiveInfinity { get; }
[JsonIgnore, IgnoreDataMember] public bool IsNegativeInfinity { get; }
}
internal interface IMensuraUnit<U> where U : struct, IMensuraUnit, IEquatable<U> { }
internal interface IMensuraUnit<U> where U : struct, IMensuraUnit, IEquatable<U>
{
public U Abs();
}

View File

@@ -9,7 +9,7 @@ internal class ListMimic<T>
}
[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct NullableDoubleMimic
internal struct NullableDoubleMimic
{
[FieldOffset(0)] public bool HasValue;
[FieldOffset(8)] public double Value;

View File

@@ -3,15 +3,15 @@
internal static class Coefficients
{
internal static double MultiplyCoefficient<T1, T2, TResult>(T1 a, T2 b, TResult r)
where T1 : struct, IMensuraUnit<T1>, IEquatable<T1>, IMensuraUnit
where T2 : struct, IMensuraUnit<T2>, IEquatable<T2>, IMensuraUnit
where TResult : struct, IMensuraUnit<TResult>, IEquatable<TResult>, IMensuraUnit
where T1 : struct, IEquatable<T1>, IMensuraUnit
where T2 : struct, IEquatable<T2>, IMensuraUnit
where TResult : struct, IEquatable<TResult>, IMensuraUnit
=> r.ToDouble() / (a.ToDouble() * b.ToDouble());
internal static double DivideCoefficient<T1, T2, TResult>(T1 a, T2 b, TResult r)
where T1 : struct, IMensuraUnit<T1>, IEquatable<T1>, IMensuraUnit
where T2 : struct, IMensuraUnit<T2>, IEquatable<T2>, IMensuraUnit
where TResult : struct, IMensuraUnit<TResult>, IEquatable<TResult>, IMensuraUnit
where T1 : struct, IEquatable<T1>, IMensuraUnit
where T2 : struct, IEquatable<T2>, IMensuraUnit
where TResult : struct, IEquatable<TResult>, IMensuraUnit
=> r.ToDouble() * b.ToDouble() / a.ToDouble();
}
@@ -70,6 +70,10 @@ public readonly partial record struct Area // MilliMetersSquared
internal static readonly double Coeff1 = Coefficients.MultiplyCoefficient(Area.MeterSquared, ForceLinear.NewtonPerMeter, Torque._Newton_Meter);
[MethodImpl(MethodImplOptions.NoInlining), OperatorsGenerator] public static Length operator /(Area left, Length right) => new(left._Value / right._Value);
[MethodImpl(MethodImplOptions.NoInlining), OperatorsGenerator] public static ForceVolumetric operator /(ForceLinear left, Area right) => new(left._Value / right._Value);
[MethodImpl(MethodImplOptions.NoInlining), OperatorsGenerator] public static Density operator /(MassLinear left, Area right) => new(left._Value / right._Value);
}
public readonly partial record struct Volume // MillimetersCubic

View File

@@ -10,10 +10,18 @@
<NoWarn>1701;1702;IDE1006</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\QWERTYkez.Mensura.Generator\QWERTYkez.Mensura.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<InternalsVisibleTo Include="QWERTYkez.Mensura.Tests" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" PrivateAssets="All" />
<ProjectReference Include="..\QWERTYkez.Mensura.Generator\QWERTYkez.Mensura.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<InternalsVisibleTo Include="QWERTYkez.Mensura.Tests" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.36" Condition="'$(TargetFramework)' == 'net6.0'" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" Condition="'$(TargetFramework)' == 'net7.0'" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.28" Condition="'$(TargetFramework)' == 'net8.0'" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.17" Condition="'$(TargetFramework)' == 'net9.0'" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.9" Condition="'$(TargetFramework)' == 'net10.0'" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@@ -258,7 +258,7 @@
//namespace QWERTYkez.Mensura.Units.Pogon
//{
// [JsonConverter(typeof(PogonAAAAAAAAAAAAAAAAConverter))]
// public readonly partial record struct ZZZZZZZZZZZZZZZZ : IMensuraUnit<ZZZZZZZZZZZZZZZZ>, IEquatable<ZZZZZZZZZZZZZZZZ>, IMensuraUnit
// public readonly partial record struct ZZZZZZZZZZZZZZZZ : IEquatable<ZZZZZZZZZZZZZZZZ>, IMensuraUnit
// {
// [JsonInclude, DataMember, JsonPropertyName("v"), Obsolete] // для JSON / EF на случай сбоев, если пробелма с _Value

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +1,70 @@
#if DEBUG
namespace QWERTYkez.Mensura.Units;
//#if DEBUG
//namespace QWERTYkez.Mensura.Units;
/// <summary>
/// Base value is MilliMeters
/// </summary>
[DebuggerDisplay("mm = {_MilliMeters.ToString(\"0.###\")}, m = {Meters.ToString(\"0.###\")}")]
public readonly partial record struct XXXXXXXX
{
public static XXXXXXXX MilliMeter { get; } = new(1);
[NotMapped, JsonIgnore, IgnoreDataMember] public double _MilliMeters { get => _Value; init => _Value = value; }
///// <summary>
///// Base value is MilliMeters
///// </summary>
//[DebuggerDisplay("mm = {_MilliMeters.ToString(\"0.###\")}, m = {Meters.ToString(\"0.###\")}")]
//public readonly partial record struct XXXXXXXX
//{
// public static XXXXXXXX MilliMeter { get; } = new(1);
// [NotMapped, JsonIgnore, IgnoreDataMember] public double _MilliMeters { get => _Value; init => _Value = value; }
public static XXXXXXXX CentiMeter { get; } = new(XXXXXXXXConv.CentiMeters.To(1));
[NotMapped, JsonIgnore, IgnoreDataMember]
public double CentiMeters
{
get => XXXXXXXXConv.CentiMeters.From(_Value);
init
{
_Value = XXXXXXXXConv.CentiMeters.To(value);
}
}
// public static XXXXXXXX CentiMeter { get; } = new(XXXXXXXXConv.CentiMeters.To(1));
// [NotMapped, JsonIgnore, IgnoreDataMember]
// public double CentiMeters
// {
// get => XXXXXXXXConv.CentiMeters.From(_Value);
// init
// {
// _Value = XXXXXXXXConv.CentiMeters.To(value);
// }
// }
public static XXXXXXXX DeciMeter { get; } = new(XXXXXXXXConv.DeciMeters.To(1));
[NotMapped, JsonIgnore, IgnoreDataMember]
public double DeciMeters
{
get => XXXXXXXXConv.DeciMeters.From(_Value);
init => _Value = XXXXXXXXConv.DeciMeters.To(value);
}
// public static XXXXXXXX DeciMeter { get; } = new(XXXXXXXXConv.DeciMeters.To(1));
// [NotMapped, JsonIgnore, IgnoreDataMember]
// public double DeciMeters
// {
// get => XXXXXXXXConv.DeciMeters.From(_Value);
// init => _Value = XXXXXXXXConv.DeciMeters.To(value);
// }
public static XXXXXXXX Meter { get; } = new(XXXXXXXXConv.Meters.To(1));
[NotMapped, JsonIgnore, IgnoreDataMember]
public double Meters
{
get => XXXXXXXXConv.Meters.From(_Value);
init => _Value = XXXXXXXXConv.Meters.To(value);
}
// public static XXXXXXXX Meter { get; } = new(XXXXXXXXConv.Meters.To(1));
// [NotMapped, JsonIgnore, IgnoreDataMember]
// public double Meters
// {
// get => XXXXXXXXConv.Meters.From(_Value);
// init => _Value = XXXXXXXXConv.Meters.To(value);
// }
public static XXXXXXXX KiloMeter { get; } = new(XXXXXXXXConv.KiloMeters.To(1));
[NotMapped, JsonIgnore, IgnoreDataMember]
public double KiloMeters
{
get => XXXXXXXXConv.KiloMeters.From(_Value);
init => _Value = XXXXXXXXConv.KiloMeters.To(value);
}
// public static XXXXXXXX KiloMeter { get; } = new(XXXXXXXXConv.KiloMeters.To(1));
// [NotMapped, JsonIgnore, IgnoreDataMember]
// public double KiloMeters
// {
// get => XXXXXXXXConv.KiloMeters.From(_Value);
// init => _Value = XXXXXXXXConv.KiloMeters.To(value);
// }
public XXXXXXXX AddMilliMeters(double value) => new(_Value + value);
public XXXXXXXX AddCentiMeters(double value) => new(_Value + XXXXXXXXConv.CentiMeters.To(value));
public XXXXXXXX AddDeciMeters(double value) => new(_Value + XXXXXXXXConv.DeciMeters.To(value));
public XXXXXXXX AddMeters(double value) => new(_Value + XXXXXXXXConv.Meters.To(value));
public XXXXXXXX AddKiloMeters(double value) => new(_Value + XXXXXXXXConv.KiloMeters.To(value));
}
// public XXXXXXXX AddMilliMeters(double value) => new(_Value + value);
// public XXXXXXXX AddCentiMeters(double value) => new(_Value + XXXXXXXXConv.CentiMeters.To(value));
// public XXXXXXXX AddDeciMeters(double value) => new(_Value + XXXXXXXXConv.DeciMeters.To(value));
// public XXXXXXXX AddMeters(double value) => new(_Value + XXXXXXXXConv.Meters.To(value));
// public XXXXXXXX AddKiloMeters(double value) => new(_Value + XXXXXXXXConv.KiloMeters.To(value));
//}
internal readonly struct XXXXXXXXConv
{
private XXXXXXXXConv(double multiplicator) => this.Multiplicator = multiplicator;
public double To(double value) => value * Multiplicator;
public double From(double value) => value / Multiplicator;
public double Multiplicator { get; init; }
public static XXXXXXXXConv MilliMeters { get; } = new(1);
public static XXXXXXXXConv CentiMeters { get; } = new(10);
public static XXXXXXXXConv DeciMeters { get; } = new(100);
public static XXXXXXXXConv Meters { get; } = new(1000);
public static XXXXXXXXConv KiloMeters { get; } = new(1000000);
}
#endif
//internal readonly struct XXXXXXXXConv
//{
// private XXXXXXXXConv(double multiplicator) => this.Multiplicator = multiplicator;
// public double To(double value) => value * Multiplicator;
// public double From(double value) => value / Multiplicator;
// public double Multiplicator { get; init; }
// public static XXXXXXXXConv MilliMeters { get; } = new(1);
// public static XXXXXXXXConv CentiMeters { get; } = new(10);
// public static XXXXXXXXConv DeciMeters { get; } = new(100);
// public static XXXXXXXXConv Meters { get; } = new(1000);
// public static XXXXXXXXConv KiloMeters { get; } = new(1000000);
//}
//#endif