From 05ea59fde2dc5f61d242ab5b913f415dd5c93c51 Mon Sep 17 00:00:00 2001 From: Nikolay Gekht Date: Tue, 23 Mar 2021 16:50:03 -0400 Subject: [PATCH] - new units - tipographical points and picas, troy oz, metric, uk and us tonne. new method of class creation unit.New(v) and conversion to and from tuples --- Gehtsoft.Measurements.Test/AreaTest.cs | 2 + Gehtsoft.Measurements.Test/CoreClassesTest.cs | 38 +++++++++++++ Gehtsoft.Measurements.Test/DistanceTest.cs | 2 + .../MeasurementMathTest.cs | 55 +++++++++++++++++++ Gehtsoft.Measurements.Test/WeightTest.cs | 4 ++ Gehtsoft.Measurements/AreaUnit.cs | 7 +++ Gehtsoft.Measurements/DistanceUnit.cs | 14 +++++ Gehtsoft.Measurements/Measurement.cs | 51 ++++++++++++++++- Gehtsoft.Measurements/MeasurementMath.cs | 28 ++++++++-- Gehtsoft.Measurements/UnitExtensions.cs | 35 ++++++++++++ Gehtsoft.Measurements/WeightUnit.cs | 36 +++++++++++- doc/project.proj | 10 ++-- doc/project.xml | 2 +- 13 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 Gehtsoft.Measurements/UnitExtensions.cs diff --git a/Gehtsoft.Measurements.Test/AreaTest.cs b/Gehtsoft.Measurements.Test/AreaTest.cs index d2349f9..c256a6f 100644 --- a/Gehtsoft.Measurements.Test/AreaTest.cs +++ b/Gehtsoft.Measurements.Test/AreaTest.cs @@ -8,6 +8,8 @@ public class AreaTest [Theory] [InlineData(120, AreaUnit.Acre, 48.56227, AreaUnit.Hectare, 1e-5)] [InlineData(120, AreaUnit.Acre, 0.48562300014476, AreaUnit.SquareKilometer, 1e-5)] + [InlineData(1, AreaUnit.Ar, 0.01, AreaUnit.Hectare, 1e-5)] + [InlineData(1, AreaUnit.Ar, 100, AreaUnit.SquareMeter, 1e-5)] public void Conversion(double value, AreaUnit unit, double expected, AreaUnit targetUnit, double accurracy = 1e-10) { var v = new Measurement(value, unit); diff --git a/Gehtsoft.Measurements.Test/CoreClassesTest.cs b/Gehtsoft.Measurements.Test/CoreClassesTest.cs index 54ef787..5d80df8 100644 --- a/Gehtsoft.Measurements.Test/CoreClassesTest.cs +++ b/Gehtsoft.Measurements.Test/CoreClassesTest.cs @@ -295,6 +295,44 @@ public void Formattable() $"{v:N2}".Should().Be("1.23m"); $"{v:N4}".Should().Be("1.2346m"); } + + [Fact] + public void NewUnitExtension() + { + var u1 = AngularUnit.MOA.New(25); + u1.Should().Be(new Measurement(25, AngularUnit.MOA)); + + var u2 = DistanceUnit.Point.New(1.23456); + u2.Value.Should().Be(1.23456); + u2.Unit.Should().Be(DistanceUnit.Point); + } + + [Fact] + public void TupleTest() + { + var t1 = new Tuple(15, AngularUnit.MOA); + + var u1 = new Measurement(t1); + u1.Should().Be(AngularUnit.MOA.New(15)); + + var u2 = (Measurement)t1; + u2.Should().Be(AngularUnit.MOA.New(15)); + + var t2 = (Tuple)u1; + t2.Should().Be(new Tuple(15, AngularUnit.MOA)); + + (double a, AngularUnit b) t3 = (10.0, AngularUnit.MOA); + + var u3 = new Measurement(t3); + u3.Value.Should().Be(t3.a); + u3.Unit.Should().Be(t3.b); + + var t4 = ((double x, AngularUnit y))u3; + t4.x.Should().Be(10); + t4.y.Should().Be(AngularUnit.MOA); + + + } } } diff --git a/Gehtsoft.Measurements.Test/DistanceTest.cs b/Gehtsoft.Measurements.Test/DistanceTest.cs index 3504d55..a041642 100644 --- a/Gehtsoft.Measurements.Test/DistanceTest.cs +++ b/Gehtsoft.Measurements.Test/DistanceTest.cs @@ -27,6 +27,8 @@ public class DistanceTest [InlineData(1, DistanceUnit.Millimeter, 0.001, DistanceUnit.Meter)] [InlineData(1760, DistanceUnit.Yard, 1, DistanceUnit.Mile)] [InlineData(1, DistanceUnit.NauticalMile, 1852, DistanceUnit.Meter)] + [InlineData(11, DistanceUnit.Point, 3.88055555556, DistanceUnit.Millimeter)] + [InlineData(11, DistanceUnit.Pica, 46.56666666667, DistanceUnit.Millimeter)] public void Conversion(double value, DistanceUnit unit, double expected, DistanceUnit targetUnit) { var v = new Measurement(value, unit); diff --git a/Gehtsoft.Measurements.Test/MeasurementMathTest.cs b/Gehtsoft.Measurements.Test/MeasurementMathTest.cs index 9622937..167c6a9 100644 --- a/Gehtsoft.Measurements.Test/MeasurementMathTest.cs +++ b/Gehtsoft.Measurements.Test/MeasurementMathTest.cs @@ -17,6 +17,7 @@ public void Sin(double value, AngularUnit unit, double expected) { var v = new Measurement(value, unit); MeasurementMath.Sin(v).Should().BeApproximately(expected, 1e-10); + v.Sin().Should().BeApproximately(expected, 1e-10); MeasurementMath.Asin(expected).In(unit).Should().BeApproximately(value, 1e-7); } @@ -61,6 +62,11 @@ public void Abs(double value, AngularUnit unit, double expected) var v1 = MeasurementMath.Abs(v); v1.Value.Should().BeApproximately(expected, 1e-10); v1.Unit.Should().Be(unit); + + var v2 = v.Abs(); + v2.Value.Should().BeApproximately(expected, 1e-10); + v2.Unit.Should().Be(unit); + } [Theory] @@ -126,5 +132,54 @@ public void Velocity(double distance, DistanceUnit distanceUnit, double velocity var velocity2 = MeasurementMath.Velocity(distance1, ts1); velocity2.In(velocityUnit).Should().BeApproximately(velocity, 1e-5); } + + [Fact] + public void CompareStatements() + { + (DistanceUnit.Centimeter.New(10) == DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + (DistanceUnit.Centimeter.New(10) != DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + (DistanceUnit.Centimeter.New(10) > DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + (DistanceUnit.Centimeter.New(10) >= DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + (DistanceUnit.Centimeter.New(10) < DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + (DistanceUnit.Centimeter.New(10) <= DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + + (DistanceUnit.Centimeter.New(20) == DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + (DistanceUnit.Centimeter.New(20) != DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + (DistanceUnit.Centimeter.New(20) > DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + (DistanceUnit.Centimeter.New(20) >= DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + (DistanceUnit.Centimeter.New(20) < DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + (DistanceUnit.Centimeter.New(20) <= DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + + (DistanceUnit.Centimeter.New(5) == DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + (DistanceUnit.Centimeter.New(5) != DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + (DistanceUnit.Centimeter.New(5) > DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + (DistanceUnit.Centimeter.New(5) >= DistanceUnit.Meter.New(0.1)).Should().BeFalse(); + (DistanceUnit.Centimeter.New(5) < DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + (DistanceUnit.Centimeter.New(5) <= DistanceUnit.Meter.New(0.1)).Should().BeTrue(); + } + + [Fact] + public void MathStatements() + { + (DistanceUnit.Centimeter.New(5) + DistanceUnit.Millimeter.New(5)).Should().Be(DistanceUnit.Centimeter.New(5.5)); + (DistanceUnit.Centimeter.New(5) - DistanceUnit.Millimeter.New(5)).Should().Be(DistanceUnit.Centimeter.New(4.5)); + (DistanceUnit.Centimeter.New(5) * 2).Should().Be(DistanceUnit.Centimeter.New(10)); + (2 * DistanceUnit.Centimeter.New(5)).Should().Be(DistanceUnit.Centimeter.New(10)); + (DistanceUnit.Centimeter.New(5) / 2).Should().Be(DistanceUnit.Centimeter.New(2.5)); + (DistanceUnit.Centimeter.New(5) / DistanceUnit.Centimeter.New(2.5)).Should().Be(2.0); + + (+DistanceUnit.Centimeter.New(5)).Should().Be(DistanceUnit.Centimeter.New(5)); + (-DistanceUnit.Centimeter.New(5)).Should().Be(DistanceUnit.Centimeter.New(-5)); + + (WeightUnit.UKTonne.New(1) / WeightUnit.USTonne.New(1)).Should().BeApproximately(1.1201764057331863285556780595369, 1e-10); + } + + [Fact] + public void Sign() + { + AngularUnit.MOA.New(5).Sign().Should().BeGreaterThan(0); + AngularUnit.MOA.New(0).Sign().Should().Be(0); + AngularUnit.MOA.New(-5).Sign().Should().BeLessThan(0); + } } } diff --git a/Gehtsoft.Measurements.Test/WeightTest.cs b/Gehtsoft.Measurements.Test/WeightTest.cs index 4227f5a..d1ed51d 100644 --- a/Gehtsoft.Measurements.Test/WeightTest.cs +++ b/Gehtsoft.Measurements.Test/WeightTest.cs @@ -11,6 +11,10 @@ public class WeightTest [InlineData(1, WeightUnit.Pound, 0.453592, WeightUnit.Kilogram, 1e-5)] [InlineData(1, WeightUnit.Kilogram, 9.80665, WeightUnit.Neuton, 1e-5)] [InlineData(5, WeightUnit.Ounce, 141.747615, WeightUnit.Gram, 1e-5)] + [InlineData(1, WeightUnit.TroyOz, 31.1034768, WeightUnit.Gram, 1e-5)] + [InlineData(1, WeightUnit.Tonne, 1000, WeightUnit.Kilogram, 1e-5)] + [InlineData(1, WeightUnit.USTonne, 0.907, WeightUnit.Tonne, 1e-5)] + [InlineData(1, WeightUnit.UKTonne, 1.016, WeightUnit.Tonne, 1e-5)] public void Conversion(double value, WeightUnit unit, double expected, WeightUnit targetUnit, double accurracy = 1e-10) { var v = new Measurement(value, unit); diff --git a/Gehtsoft.Measurements/AreaUnit.cs b/Gehtsoft.Measurements/AreaUnit.cs index 795c08f..3151eb8 100644 --- a/Gehtsoft.Measurements/AreaUnit.cs +++ b/Gehtsoft.Measurements/AreaUnit.cs @@ -74,5 +74,12 @@ public enum AreaUnit [Unit("ha", 0)] [Conversion(ConversionOperation.Multiply, 1e+10)] Hectare, + + /// + /// Ar (1/100 of hectare, "sotka") + /// + [Unit("ar", 0)] + [Conversion(ConversionOperation.Multiply, 1e+8)] + Ar, } } \ No newline at end of file diff --git a/Gehtsoft.Measurements/DistanceUnit.cs b/Gehtsoft.Measurements/DistanceUnit.cs index 4d25177..a45bfc3 100644 --- a/Gehtsoft.Measurements/DistanceUnit.cs +++ b/Gehtsoft.Measurements/DistanceUnit.cs @@ -81,5 +81,19 @@ public enum DistanceUnit [Unit("km", 3)] [Conversion(ConversionOperation.Divide, 25.4, ConversionOperation.Multiply, 1_000_000)] Kilometer, + + /// + /// Typographical/DTP point (1 pt == 1/72 of inch) + /// + [Unit("pt", 1)] + [Conversion(ConversionOperation.Divide, 72)] + Point, + + /// + /// Typographical/DTP point (1 pt == 1/72 of inch) + /// + [Unit("p", 1)] + [Conversion(ConversionOperation.Divide, 6)] + Pica, } } diff --git a/Gehtsoft.Measurements/Measurement.cs b/Gehtsoft.Measurements/Measurement.cs index fd0e94c..fb45449 100644 --- a/Gehtsoft.Measurements/Measurement.cs +++ b/Gehtsoft.Measurements/Measurement.cs @@ -21,7 +21,6 @@ namespace Gehtsoft.Measurements /// The class supports serialization using `System.Text.Json` serializer and `XmlSerializer` as well as /// many 3rd party serializers such as `BinaronSerializer`. /// - /// The measurement unit /// public readonly struct Measurement : IEquatable>, IComparable>, IFormattable where T : Enum @@ -50,6 +49,26 @@ public Measurement(double value, T unit) Unit = unit; } + /// + /// Constructor that accepts a tuple. + /// + /// + public Measurement(Tuple value) + { + Value = value.Item1; + Unit = value.Item2; + } + + /// + /// Constructor that accepts a anonymous tuple. + /// + /// + public Measurement((double, T) value) + { + Value = value.Item1; + Unit = value.Item2; + } + /// /// Constructor that accepts a text representation of a value /// @@ -311,6 +330,8 @@ public int CompareTo(Measurement other) double v1, v2; v1 = In(BaseUnit); v2 = other.In(BaseUnit); + if (Math.Abs(v1 - v2) < 1e-10) + return 0; return v1.CompareTo(v2); } @@ -370,6 +391,15 @@ public int CompareTo(Measurement other) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Measurement operator -(Measurement v1) => new Measurement(-v1.Value, v1.Unit); + + /// + /// Unary plus value + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Measurement operator +(Measurement v1) => v1; + /// /// Add one measurement to another. /// @@ -421,5 +451,24 @@ public int CompareTo(Measurement other) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double operator /(Measurement v1, Measurement v2) => v1.Value / v2.In(v1.Unit); + + /// + /// Implicitly converts the value to a tuple + /// + /// + public static implicit operator Tuple(Measurement value) => new Tuple(value.Value, value.Unit); + + /// + /// Explicitly converts the a tuple to a value + /// + /// + public static explicit operator Measurement(Tuple value) => new Measurement(value.Item1, value.Item2); + + /// + /// Implicitly converts the value to an anonymous tuple + /// + /// + public static implicit operator (double, T)(Measurement value) => (value.Value, value.Unit); + } } diff --git a/Gehtsoft.Measurements/MeasurementMath.cs b/Gehtsoft.Measurements/MeasurementMath.cs index 237b818..de04c17 100644 --- a/Gehtsoft.Measurements/MeasurementMath.cs +++ b/Gehtsoft.Measurements/MeasurementMath.cs @@ -10,25 +10,41 @@ namespace Gehtsoft.Measurements /// public static class MeasurementMath { + /// + /// Returns sign of the value + /// The method return `-1` for negative values, `0` for zero value and `1` for positive values + /// + /// + /// + /// + public static int Sign(this Measurement value) where T : Enum + { + if (value.Value < 0) + return -1; + else if (value.Value == 0) + return 0; + return 1; + } + /// /// Calculate sine of angular value /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Sin(Measurement value) => Math.Sin(value.In(AngularUnit.Radian)); + public static double Sin(this Measurement value) => Math.Sin(value.In(AngularUnit.Radian)); /// /// Calculate cosine of angular value /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Cos(Measurement value) => Math.Cos(value.In(AngularUnit.Radian)); + public static double Cos(this Measurement value) => Math.Cos(value.In(AngularUnit.Radian)); /// /// Calculate tangent of angular value /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Tan(Measurement value) => Math.Tan(value.In(AngularUnit.Radian)); + public static double Tan(this Measurement value) => Math.Tan(value.In(AngularUnit.Radian)); /// /// Calculate arcsine as angular value @@ -52,19 +68,19 @@ public static class MeasurementMath /// Calculate square root of a value /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Measurement Sqrt(Measurement value) where T : Enum => new Measurement(Math.Sqrt(value.Value), value.Unit); + public static Measurement Sqrt(this Measurement value) where T : Enum => new Measurement(Math.Sqrt(value.Value), value.Unit); /// /// Raise the value in the power specified /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Measurement Pow(Measurement value, double exp) where T : Enum => new Measurement(Math.Pow(value.Value, exp), value.Unit); + public static Measurement Pow(this Measurement value, double exp) where T : Enum => new Measurement(Math.Pow(value.Value, exp), value.Unit); /// /// Calculate the absolute value /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Measurement Abs(Measurement value) where T : Enum => new Measurement(Math.Abs(value.Value), value.Unit); + public static Measurement Abs(this Measurement value) where T : Enum => new Measurement(Math.Abs(value.Value), value.Unit); /// /// Calculate velocity from distance and time diff --git a/Gehtsoft.Measurements/UnitExtensions.cs b/Gehtsoft.Measurements/UnitExtensions.cs new file mode 100644 index 0000000..cb19c61 --- /dev/null +++ b/Gehtsoft.Measurements/UnitExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Gehtsoft.Measurements +{ + /// + /// The extensions for unit type enumerations + /// + public static class UnitExtensions + { + /// + /// Creates a new value of the specified unit. + /// The method is an extension for a `enum` value, i.e. you can use it as `AngularUnit.MOA.New(10)` instead of writing `new Measurement<AngularUnit>(10, AngularUnit.MOA)` + /// + /// + /// + /// + /// + public static Measurement New(this T unit, double value) where T : Enum => new Measurement(value, unit); + + /// + /// Converts an enumeration of doubles into an enumeration of measures of the specified unit. + /// + /// + /// + /// + /// + public static IEnumerable> As(this IEnumerable values, T unit) where T : Enum + { + foreach (var v in values) + yield return new Measurement(v, unit); + } + } +} diff --git a/Gehtsoft.Measurements/WeightUnit.cs b/Gehtsoft.Measurements/WeightUnit.cs index 8c1614e..0b199a4 100644 --- a/Gehtsoft.Measurements/WeightUnit.cs +++ b/Gehtsoft.Measurements/WeightUnit.cs @@ -17,24 +17,28 @@ public enum WeightUnit [Unit("oz", 1)] [Conversion(ConversionOperation.Multiply, 437.5)] Ounce, + /// /// Grams /// [Unit("g", 1)] [Conversion(ConversionOperation.Multiply, 15.4323583529)] Gram, + /// /// Points /// [Unit("lb", 3)] [Conversion(ConversionOperation.Multiply, 7000)] Pound, + /// /// Kilograms /// [Unit("kg", 3)] [Conversion(ConversionOperation.Multiply, 15432.3583529)] Kilogram, + /// /// Newton /// @@ -47,6 +51,36 @@ public enum WeightUnit /// [Unit("dr", 1)] [Conversion(ConversionOperation.Multiply, 1.7718451953125)] - Dram + Dram, + + /// + /// Troy Ounce + /// + [Unit("tr.oz", 1)] + [Conversion(ConversionOperation.Multiply, 15.4323583529 * 31.1034768)] + TroyOz, + + /// + /// Metric Tonne + /// + [Unit("t", 3)] + [Conversion(ConversionOperation.Multiply, 15432358.3529)] + Tonne, + + /// + /// US Tonne + /// + [Unit("us.t", 3)] + [Conversion(ConversionOperation.Multiply, 15432358.3529 * 0.907)] + USTonne, + + /// + /// UK Tonne + /// + [Unit("uk.t", 3)] + [Conversion(ConversionOperation.Multiply, 15432358.3529 * 1.016)] + UKTonne, + + } } diff --git a/doc/project.proj b/doc/project.proj index c423a88..0be737f 100644 --- a/doc/project.proj +++ b/doc/project.proj @@ -4,7 +4,7 @@ netstandard2.0 - + @@ -25,10 +25,10 @@ - - - - + + + + diff --git a/doc/project.xml b/doc/project.xml index 1c0da45..c47f9b9 100644 --- a/doc/project.xml +++ b/doc/project.xml @@ -14,7 +14,7 @@ - +