diff --git a/OnixLabs.Core/IValueComparable.cs b/OnixLabs.Core/IValueComparable.cs index 921fdeb..29b24bb 100644 --- a/OnixLabs.Core/IValueComparable.cs +++ b/OnixLabs.Core/IValueComparable.cs @@ -20,8 +20,8 @@ namespace OnixLabs.Core; /// Defines an extension to the default and interfaces, /// including equality, inequality, greater than, greater than or equal, less than, and less than or equal operators. /// -/// The underlying type of the objects to compare. -public interface IValueComparable : IComparable, IComparable where T : IValueComparable +/// The underlying type of the objects to compare. +public interface IValueComparable : IComparable, IComparable where TSelf : IValueComparable { /// /// Performs an equality comparison between two object instances. @@ -29,7 +29,7 @@ public interface IValueComparable : IComparable, IComparable where T : /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static abstract bool operator ==(T left, T right); + public static abstract bool operator ==(TSelf left, TSelf right); /// /// Performs an inequality comparison between two object instances. @@ -37,7 +37,7 @@ public interface IValueComparable : IComparable, IComparable where T : /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static abstract bool operator !=(T left, T right); + public static abstract bool operator !=(TSelf left, TSelf right); /// /// Performs a greater than comparison between two object instances. @@ -45,7 +45,7 @@ public interface IValueComparable : IComparable, IComparable where T : /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is greater than the right-hand instance; otherwise, . - public static abstract bool operator >(T left, T right); + public static abstract bool operator >(TSelf left, TSelf right); /// /// Performs a greater than or equal comparison between two object instances. @@ -53,7 +53,7 @@ public interface IValueComparable : IComparable, IComparable where T : /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is greater than or equal to the right-hand instance; otherwise, . - public static abstract bool operator >=(T left, T right); + public static abstract bool operator >=(TSelf left, TSelf right); /// /// Performs a less than comparison between two object instances. @@ -61,7 +61,7 @@ public interface IValueComparable : IComparable, IComparable where T : /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is less than the right-hand instance; otherwise, . - public static abstract bool operator <(T left, T right); + public static abstract bool operator <(TSelf left, TSelf right); /// /// Performs a less than or equal comparison between two object instances. @@ -69,5 +69,5 @@ public interface IValueComparable : IComparable, IComparable where T : /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is less than or equal to the right-hand instance; otherwise, . - public static abstract bool operator <=(T left, T right); + public static abstract bool operator <=(TSelf left, TSelf right); } diff --git a/OnixLabs.Core/IValueEquatable.cs b/OnixLabs.Core/IValueEquatable.cs index 0a9e7a6..5d4bbe4 100644 --- a/OnixLabs.Core/IValueEquatable.cs +++ b/OnixLabs.Core/IValueEquatable.cs @@ -19,8 +19,8 @@ namespace OnixLabs.Core; /// /// Defines an extension to the default interface, including equality and inequality operators. /// -/// The underlying type of the objects to compare. -public interface IValueEquatable : IEquatable where T : IValueEquatable +/// The underlying type of the objects to compare. +public interface IValueEquatable : IEquatable where TSelf : IValueEquatable { /// /// Performs an equality comparison between two object instances. @@ -28,7 +28,7 @@ public interface IValueEquatable : IEquatable where T : IValueEquatable /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is equal to the right-hand instance; otherwise, . - public static abstract bool operator ==(T? left, T? right); + public static abstract bool operator ==(TSelf? left, TSelf? right); /// /// Performs an inequality comparison between two object instances. @@ -36,5 +36,5 @@ public interface IValueEquatable : IEquatable where T : IValueEquatable /// The left-hand instance to compare. /// The right-hand instance to compare. /// Returns if the left-hand instance is not equal to the right-hand instance; otherwise, . - public static abstract bool operator !=(T? left, T? right); + public static abstract bool operator !=(TSelf? left, TSelf? right); } diff --git a/OnixLabs.Core/Text/IBaseValue.cs b/OnixLabs.Core/Text/IBaseValue.cs index 103c244..5dc42a2 100644 --- a/OnixLabs.Core/Text/IBaseValue.cs +++ b/OnixLabs.Core/Text/IBaseValue.cs @@ -40,4 +40,4 @@ public interface IBaseValue : IBinaryConvertible, ISpanFormattable /// /// Defines a generic base encoding representation. /// -public interface IBaseValue : IValueEquatable, ISpanParsable, IBaseValue where T : struct, IBaseValue; +public interface IBaseValue : IValueEquatable, ISpanParsable, IBaseValue where TSelf : struct, IBaseValue; diff --git a/OnixLabs.Playground/OnixLabs.Playground.csproj b/OnixLabs.Playground/OnixLabs.Playground.csproj index 3c25aa1..bbd8a80 100644 --- a/OnixLabs.Playground/OnixLabs.Playground.csproj +++ b/OnixLabs.Playground/OnixLabs.Playground.csproj @@ -8,6 +8,7 @@ + diff --git a/OnixLabs.Units.UnitTests/AreaTests.cs b/OnixLabs.Units.UnitTests/AreaTests.cs new file mode 100644 index 0000000..2be164c --- /dev/null +++ b/OnixLabs.Units.UnitTests/AreaTests.cs @@ -0,0 +1,771 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Globalization; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class AreaTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+106; + + [Fact(DisplayName = "Area.Zero should produce the expected result")] + public void AreaZeroShouldProduceExpectedResult() + { + // Given / When + Area area = Area.Zero; + + // Then + Assert.Equal(0.0, area.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareQuectometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void AreaFromSquareQuectometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareQuectometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareRontometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void AreaFromSquareRontometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareRontometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareYoctometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void AreaFromSquareYoctometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareYoctometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareZeptometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void AreaFromSquareZeptometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareZeptometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareAttometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void AreaFromSquareAttometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareAttometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareFemtometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void AreaFromSquareFemtometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareFemtometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquarePicometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void AreaFromSquarePicometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquarePicometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareNanometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void AreaFromSquareNanometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareNanometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMicrometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void AreaFromSquareMicrometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareMicrometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMillimeters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e54)] + [InlineData(2.5, 2.5e54)] + public void AreaFromSquareMillimetersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareMillimeters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareCentimeters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e56)] + [InlineData(2.5, 2.5e56)] + public void AreaFromSquareCentimetersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareCentimeters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareDecimeters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e58)] + [InlineData(2.5, 2.5e58)] + public void AreaFromSquareDecimetersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareDecimeters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMeters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e60)] + [InlineData(2.5, 2.5e60)] + public void AreaFromSquareMetersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareMeters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareDecameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e62)] + [InlineData(2.5, 2.5e62)] + public void AreaFromSquareDecametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareDecameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareHectometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e64)] + [InlineData(2.5, 2.5e64)] + public void AreaFromSquareHectometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareHectometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareKilometers should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e66)] + [InlineData(2.5, 2.5e66)] + public void AreaFromSquareKilometersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareKilometers(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMegameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e72)] + [InlineData(2.5, 2.5e72)] + public void AreaFromSquareMegametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareMegameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareGigameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e78)] + [InlineData(2.5, 2.5e78)] + public void AreaFromSquareGigametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareGigameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareTerameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e84)] + [InlineData(2.5, 2.5e84)] + public void AreaFromSquareTerametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareTerameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquarePetameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e90)] + [InlineData(2.5, 2.5e90)] + public void AreaFromSquarePetametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquarePetameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareExameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e96)] + [InlineData(2.5, 2.5e96)] + public void AreaFromSquareExametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareExameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareZettameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e102)] + [InlineData(2.5, 2.5e102)] + public void AreaFromSquareZettametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareZettameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareYottameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e108)] + [InlineData(2.5, 2.5e108)] + public void AreaFromSquareYottametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareYottameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareRonnameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e114)] + [InlineData(2.5, 2.5e114)] + public void AreaFromSquareRonnametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareRonnameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareQuettameters should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e120)] + [InlineData(2.5, 2.5e120)] + public void AreaFromSquareQuettametersShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareQuettameters(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareInches should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 6.4516e56)] + [InlineData(2.5, 1.6129e57)] + public void AreaFromSquareInchesShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareInches(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareFeet should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.290304e58)] + [InlineData(2.5, 2.322576e59)] + public void AreaFromSquareFeetShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareFeet(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareYards should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 8.3612736e59)] + [InlineData(2.5, 2.0903184e60)] + public void AreaFromSquareYardsShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareYards(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMiles should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.589988110336e66)] + [InlineData(2.5, 6.47497027584e66)] + public void AreaFromSquareMilesShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareMiles(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareNauticalMiles should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.4299040000e66)] + [InlineData(2.5, 8.5747600000e66)] + public void AreaFromSquareNauticalMilesShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareNauticalMiles(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareFermis should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void AreaFromSquareFermisShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareFermis(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareAngstroms should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e40)] + [InlineData(2.5, 2.5e40)] + public void AreaFromSquareAngstromsShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareAngstroms(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareAstronomicalUnits should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.2379522821e82)] + [InlineData(2.5, 5.59488070525e82)] + public void AreaFromSquareAstronomicalUnitsShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareAstronomicalUnits(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareLightYears should produce the expected SquareQuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 8.9505421074819e91)] + [InlineData(2.5, 2.237635526870475e92)] + public void AreaFromSquareLightYearsShouldProduceExpectedSquareQuectoMeters(double value, double expectedSquareQuectoMeters) + { + // Given / When + Area a = Area.FromSquareLightYears(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareParsecs should produce the expected SquareQuectoMeters")] + [InlineData(0.0)] + [InlineData(1.0)] + [InlineData(2.5)] + public void AreaFromSquareParsecsShouldProduceExpectedSquareQuectoMeters(double value) + { + // Given + const double metersPerParsec = 1.495978707e11 * 648000.0 / Math.PI; + const double sqMetersPerSqParsec = metersPerParsec * metersPerParsec; + const double sqQmPerSqParsec = sqMetersPerSqParsec * 1e60; + double expectedSquareQuectoMeters = value * sqQmPerSqParsec; + + // When + Area a = Area.FromSquareParsecs(value); + + // Then + Assert.Equal(expectedSquareQuectoMeters, a.SquareQuectoMeters, Tolerance); + } + + [Fact(DisplayName = "Area.Add should produce the expected result")] + public void AreaAddShouldProduceExpectedValue() + { + // Given + Area left = Area.FromSquareMeters(1500.0); + Area right = Area.FromSquareMeters(500.0); + + // When + Area result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.SquareMeters, Tolerance); + } + + [Fact(DisplayName = "Area.Subtract should produce the expected result")] + public void AreaSubtractShouldProduceExpectedValue() + { + // Given + Area left = Area.FromSquareMeters(1500.0); + Area right = Area.FromSquareMeters(400.0); + + // When + Area result = left.Subtract(right); + + // Then + Assert.Equal(1100.0, result.SquareMeters, Tolerance); + } + + [Fact(DisplayName = "Area.Multiply should produce the expected result")] + public void AreaMultiplyShouldProduceExpectedValue() + { + // Given + Area left = Area.FromSquareMeters(10.0); // 1e61 sqQm + Area right = Area.FromSquareMeters(3.0); // 3e60 sqQm + + // When + Area result = left.Multiply(right); // 1e61 * 3e60 = 3e121 sqQm + + // Then + Assert.Equal(1e61, left.SquareQuectoMeters, Tolerance); + Assert.Equal(3e60, right.SquareQuectoMeters, Tolerance); + Assert.Equal(3e121, result.SquareQuectoMeters, Tolerance); + } + + [Fact(DisplayName = "Area.Divide should produce the expected result")] + public void AreaDivideShouldProduceExpectedValue() + { + // Given + Area left = Area.FromSquareMeters(100.0); // 1e62 sqQm + Area right = Area.FromSquareMeters(20.0); // 2e61 sqQm + + // When + Area result = left.Divide(right); // 1e62 / 2e61 = 5 sqQm + + // Then + Assert.Equal(5.0, result.SquareQuectoMeters, Tolerance); + Assert.Equal(5e-60, result.SquareMeters, Tolerance); + } + + [Fact(DisplayName = "Area comparison should produce the expected result (left equal to right)")] + public void AreaComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Area left = Area.FromSquareMeters(1234.0); + Area right = Area.FromSquareMeters(1234.0); + + // When / Then + Assert.Equal(0, Area.Compare(left, right)); + Assert.Equal(0, left.CompareTo(right)); + Assert.Equal(0, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "Area comparison should produce the expected result (left greater than right)")] + public void AreaComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Area left = Area.FromSquareMeters(4567.0); + Area right = Area.FromSquareMeters(1234.0); + + // When / Then + Assert.Equal(1, Area.Compare(left, right)); + Assert.Equal(1, left.CompareTo(right)); + Assert.Equal(1, left.CompareTo((object)right)); + Assert.True(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.False(left <= right); + } + + [Fact(DisplayName = "Area comparison should produce the expected result (left less than right)")] + public void AreaComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Area left = Area.FromSquareMeters(1234.0); + Area right = Area.FromSquareMeters(4567.0); + + // When / Then + Assert.Equal(-1, Area.Compare(left, right)); + Assert.Equal(-1, left.CompareTo(right)); + Assert.Equal(-1, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.False(left >= right); + Assert.True(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "Area equality should produce the expected result (left equal to right)")] + public void AreaEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Area left = Area.FromSquareKilometers(2.0); + Area right = Area.FromSquareMeters(2000000.0); + + // When / Then + Assert.True(Area.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Area equality should produce the expected result (left not equal to right)")] + public void AreaEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Area left = Area.FromSquareKilometers(2.0); + Area right = Area.FromSquareMeters(2500000.0); + + // When / Then + Assert.False(Area.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Area.ToString should produce the expected result")] + public void AreaToStringShouldProduceExpectedResult() + { + // Given + Area a = Area.FromSquareMeters(1000000.0); + + // When / Then + Assert.Equal("1,000,000.000 m²", $"{a:sqm3}"); + Assert.Equal("1.000 km²", $"{a:sqkm3}"); + Assert.Equal("100.000 hm²", $"{a:sqhm3}"); + Assert.Equal("10,000.000 dam²", $"{a:sqdam3}"); + Assert.Equal("10,000,000,000.000 cm²", $"{a:sqcm3}"); + Assert.Equal("1,550,003,100.006 in²", $"{a:sqin3}"); + Assert.Equal("10,763,910.417 ft²", $"{a:sqft3}"); + Assert.Equal("1,195,990.046 yd²", $"{a:sqyd3}"); + } + + [Fact(DisplayName = "Area.ToString should honor custom culture separators")] + public void AreaToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Area a = Area.FromSquareMeters(1234.56); + + // When + string formatted = a.ToString("sqm2", customCulture); + + // Then + Assert.Equal("1.234,56 m²", formatted); + } + + [Fact(DisplayName = "Area property conversions should be consistent")] + public void AreaPropertyConversionsShouldBeConsistent() + { + // Given + Area a = Area.FromSquareMeters(1.0); + + // Then - verify SI unit conversions are consistent + Assert.Equal(1.0, a.SquareMeters, Tolerance); + Assert.Equal(1e60, a.SquareQuectoMeters, Tolerance); + Assert.Equal(1e-6, a.SquareKiloMeters, Tolerance); + Assert.Equal(100.0, a.SquareDeciMeters, Tolerance); + Assert.Equal(10000.0, a.SquareCentiMeters, Tolerance); + Assert.Equal(1000000.0, a.SquareMilliMeters, Tolerance); + } + + [Fact(DisplayName = "Area imperial unit conversions should be accurate")] + public void AreaImperialUnitConversionsShouldBeAccurate() + { + // Given - 1 square meter + Area a = Area.FromSquareMeters(1.0); + + // Then - verify imperial conversions + Assert.Equal(1550.0031000062, a.SquareInches, 1e-6); + Assert.Equal(10.76391041671, a.SquareFeet, 1e-6); + Assert.Equal(1.1959900463011, a.SquareYards, 1e-6); + } + + [Fact(DisplayName = "Area round-trip conversions should be accurate")] + public void AreaRoundTripConversionsShouldBeAccurate() + { + // Given + double originalValue = 123.456; + + // When - convert from square meters and back + Area a = Area.FromSquareMeters(originalValue); + + // Then + Assert.Equal(originalValue, a.SquareMeters, 1e-10); + } + + [Fact(DisplayName = "Area from square kilometers to square meters should be accurate")] + public void AreaFromSquareKilometersToSquareMetersShouldBeAccurate() + { + // Given + Area a = Area.FromSquareKilometers(1.0); + + // Then + Assert.Equal(1000000.0, a.SquareMeters, Tolerance); + } + + [Fact(DisplayName = "Area from square miles to square kilometers should be accurate")] + public void AreaFromSquareMilesToSquareKilometersShouldBeAccurate() + { + // Given + Area a = Area.FromSquareMiles(1.0); + + // Then - 1 square mile = 2.589988110336 square kilometers + Assert.Equal(2.589988110336, a.SquareKiloMeters, 1e-6); + } + + [Fact(DisplayName = "Area GetHashCode should be consistent for equal values")] + public void AreaGetHashCodeShouldBeConsistentForEqualValues() + { + // Given + Area a1 = Area.FromSquareMeters(100.0); + Area a2 = Area.FromSquareMeters(100.0); + + // Then + Assert.Equal(a1.GetHashCode(), a2.GetHashCode()); + } + + [Fact(DisplayName = "Area.Equals with null object should return false")] + public void AreaEqualsWithNullObjectShouldReturnFalse() + { + // Given + Area a = Area.FromSquareMeters(100.0); + + // Then + Assert.False(a.Equals(null)); + } + + [Fact(DisplayName = "Area.Equals with different type should return false")] + public void AreaEqualsWithDifferentTypeShouldReturnFalse() + { + // Given + Area a = Area.FromSquareMeters(100.0); + + // Then + Assert.False(a.Equals("not an area")); + } + + [Fact(DisplayName = "Area default ToString should use SquareQuectoMeters")] + public void AreaDefaultToStringShouldUseSquareQuectoMeters() + { + // Given + Area a = Area.FromSquareQuectometers(123.0); + + // When + string result = a.ToString(); + + // Then + Assert.Contains("qm²", result); + } +} diff --git a/OnixLabs.Units.UnitTests/DataSizeTests.cs b/OnixLabs.Units.UnitTests/DataSizeTests.cs new file mode 100644 index 0000000..b13f6ce --- /dev/null +++ b/OnixLabs.Units.UnitTests/DataSizeTests.cs @@ -0,0 +1,613 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Globalization; + +namespace OnixLabs.Units.UnitTests; + +public sealed class DataSizeTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e-12; + + [Fact(DisplayName = "DataSize.Zero should produce the expected result")] + public void DataSizeZeroShouldProduceExpectedResult() + { + // Given / When + DataSize size = DataSize.Zero; + + // Then + Assert.Equal(0.0, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(8.0, 8.0)] + [InlineData(1000.0, 1000.0)] + [InlineData(1024.0, 1024.0)] + [InlineData(8192.0, 8192.0)] + public void DataSizeFromBitsShouldProduceExpectedBits(double bits, double expectedBits) + { + // When + DataSize size = DataSize.FromBits(bits); + + // Then + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 8.0)] + [InlineData(1000.0, 8000.0)] + [InlineData(1024.0, 8192.0)] + [InlineData(2048.0, 16384.0)] + public void DataSizeFromBytesShouldProduceExpectedBits(double bytes, double expectedBits) + { + DataSize size = DataSize.FromBytes(bytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromKibiBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0)] + [InlineData(2.0, 2048.0)] + [InlineData(8.0, 8192.0)] + [InlineData(10.0, 10240.0)] + public void DataSizeFromKibiBitsShouldProduceExpectedBits(double kibibits, double expectedBits) + { + DataSize size = DataSize.FromKibiBits(kibibits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromKibiBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 8192.0)] + [InlineData(2.0, 16384.0)] + [InlineData(10.0, 81920.0)] + [InlineData(0.5, 4096.0)] + public void DataSizeFromKibiBytesShouldProduceExpectedBits(double kibibytes, double expectedBits) + { + DataSize size = DataSize.FromKibiBytes(kibibytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromKiloBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0)] + [InlineData(8.0, 8000.0)] + [InlineData(10.0, 10000.0)] + [InlineData(0.5, 500.0)] + public void DataSizeFromKiloBitsShouldProduceExpectedBits(double kilobits, double expectedBits) + { + DataSize size = DataSize.FromKiloBits(kilobits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromKiloBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 8000.0)] + [InlineData(2.0, 16000.0)] + [InlineData(0.5, 4000.0)] + [InlineData(10.0, 80000.0)] + public void DataSizeFromKiloBytesShouldProduceExpectedBits(double kilobytes, double expectedBits) + { + DataSize size = DataSize.FromKiloBytes(kilobytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromMebiBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0)] + public void DataSizeFromMebiBitsShouldProduceExpectedBits(double mebibits, double expectedBits) + { + DataSize size = DataSize.FromMebiBits(mebibits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromMebiBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 8.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 8.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 8.0)] + public void DataSizeFromMebiBytesShouldProduceExpectedBits(double mebibytes, double expectedBits) + { + DataSize size = DataSize.FromMebiBytes(mebibytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromMegaBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0)] + public void DataSizeFromMegaBitsShouldProduceExpectedBits(double megabits, double expectedBits) + { + DataSize size = DataSize.FromMegaBits(megabits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromMegaBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 8.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 8.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 8.0)] + public void DataSizeFromMegaBytesShouldProduceExpectedBits(double megabytes, double expectedBits) + { + DataSize size = DataSize.FromMegaBytes(megabytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromGibiBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0)] + public void DataSizeFromGibiBitsShouldProduceExpectedBits(double gibibits, double expectedBits) + { + DataSize size = DataSize.FromGibiBits(gibibits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromGibiBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + public void DataSizeFromGibiBytesShouldProduceExpectedBits(double gibibytes, double expectedBits) + { + DataSize size = DataSize.FromGibiBytes(gibibytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromGigaBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0)] + public void DataSizeFromGigaBitsShouldProduceExpectedBits(double gigabits, double expectedBits) + { + DataSize size = DataSize.FromGigaBits(gigabits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromGigaBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + public void DataSizeFromGigaBytesShouldProduceExpectedBits(double gigabytes, double expectedBits) + { + DataSize size = DataSize.FromGigaBytes(gigabytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromTebiBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + public void DataSizeFromTebiBitsShouldProduceExpectedBits(double tebibits, double expectedBits) + { + DataSize size = DataSize.FromTebiBits(tebibits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromTebiBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + public void DataSizeFromTebiBytesShouldProduceExpectedBits(double tebibytes, double expectedBits) + { + DataSize size = DataSize.FromTebiBytes(tebibytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromTeraBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + public void DataSizeFromTeraBitsShouldProduceExpectedBits(double terabits, double expectedBits) + { + DataSize size = DataSize.FromTeraBits(terabits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromTeraBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + public void DataSizeFromTeraBytesShouldProduceExpectedBits(double terabytes, double expectedBits) + { + DataSize size = DataSize.FromTeraBytes(terabytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromPebiBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + public void DataSizeFromPebiBitsShouldProduceExpectedBits(double pebibits, double expectedBits) + { + DataSize size = DataSize.FromPebiBits(pebibits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromPebiBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + public void DataSizeFromPebiBytesShouldProduceExpectedBits(double pebibytes, double expectedBits) + { + DataSize size = DataSize.FromPebiBytes(pebibytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromPetaBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + public void DataSizeFromPetaBitsShouldProduceExpectedBits(double petabits, double expectedBits) + { + DataSize size = DataSize.FromPetaBits(petabits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromPetaBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + public void DataSizeFromPetaBytesShouldProduceExpectedBits(double petabytes, double expectedBits) + { + DataSize size = DataSize.FromPetaBytes(petabytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromExbiBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + public void DataSizeFromExbiBitsShouldProduceExpectedBits(double exbibits, double expectedBits) + { + DataSize size = DataSize.FromExbiBits(exbibits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromExbiBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + public void DataSizeFromExbiBytesShouldProduceExpectedBits(double exbibytes, double expectedBits) + { + DataSize size = DataSize.FromExbiBytes(exbibytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromExaBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + public void DataSizeFromExaBitsShouldProduceExpectedBits(double exabits, double expectedBits) + { + DataSize size = DataSize.FromExaBits(exabits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromExaBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + public void DataSizeFromExaBytesShouldProduceExpectedBits(double exabytes, double expectedBits) + { + DataSize size = DataSize.FromExaBytes(exabytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromZebiBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + public void DataSizeFromZebiBitsShouldProduceExpectedBits(double zebibits, double expectedBits) + { + DataSize size = DataSize.FromZebiBits(zebibits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromZebiBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + public void DataSizeFromZebiBytesShouldProduceExpectedBits(double zebibytes, double expectedBits) + { + DataSize size = DataSize.FromZebiBytes(zebibytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromZettaBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + public void DataSizeFromZettaBitsShouldProduceExpectedBits(double zettabits, double expectedBits) + { + DataSize size = DataSize.FromZettaBits(zettabits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromZettaBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(10.0, 10.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + public void DataSizeFromZettaBytesShouldProduceExpectedBits(double zettabytes, double expectedBits) + { + DataSize size = DataSize.FromZettaBytes(zettabytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromYobiBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)] + public void DataSizeFromYobiBitsShouldProduceExpectedBits(double yobibits, double expectedBits) + { + DataSize size = DataSize.FromYobiBits(yobibits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromYobiBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(0.5, 0.5 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(2.0, 2.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + [InlineData(10.0, 10.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 8.0)] + public void DataSizeFromYobiBytesShouldProduceExpectedBits(double yobibytes, double expectedBits) + { + DataSize size = DataSize.FromYobiBytes(yobibytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromYottaBits should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0)] + public void DataSizeFromYottaBitsShouldProduceExpectedBits(double yottabits, double expectedBits) + { + DataSize size = DataSize.FromYottaBits(yottabits); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Theory(DisplayName = "DataSize.FromYottaBytes should produce the expected Bits")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(0.5, 0.5 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + [InlineData(2.0, 2.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0 * 8.0)] + public void DataSizeFromYottaBytesShouldProduceExpectedBits(double yottabytes, double expectedBits) + { + DataSize size = DataSize.FromYottaBytes(yottabytes); + Assert.Equal(expectedBits, size.Bits, Tolerance); + } + + [Fact(DisplayName = "DataSize.Add should produce the expected result")] + public void DataSizeAddShouldProduceExpectedValue() + { + // Given + DataSize left = DataSize.FromBytes(1500.0); + DataSize right = DataSize.FromBytes(500.0); + + // When + DataSize result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.Bytes, Tolerance); + } + + [Fact(DisplayName = "DataSize.Subtract should produce the expected result")] + public void DataSizeSubtractShouldProduceExpectedValue() + { + // Given + DataSize left = DataSize.FromBytes(1500.0); + DataSize right = DataSize.FromBytes(400.0); + + // When + DataSize result = left.Subtract(right); + + // Then + Assert.Equal(1100.0, result.Bytes, Tolerance); + } + + [Fact(DisplayName = "DataSize.Multiply should produce the expected result")] + public void DataSizeMultiplyShouldProduceExpectedValue() + { + // Given + DataSize left = DataSize.FromBytes(10.0); // 80 bits + DataSize right = DataSize.FromBytes(3.0); // 24 bits + + // When + DataSize result = left.Multiply(right); // 80 * 24 = 1920 bits + + // Then + Assert.Equal(1920.0, result.Bits, Tolerance); + Assert.Equal(240.0, result.Bytes, Tolerance); + } + + [Fact(DisplayName = "DataSize.Divide should produce the expected result")] + public void DataSizeDivideShouldProduceExpectedValue() + { + // Given + DataSize left = DataSize.FromBytes(100.0); // 800 bits + DataSize right = DataSize.FromBytes(20.0); // 160 bits + + // When + DataSize result = left.Divide(right); // 800 / 160 = 5 bits + + // Then + Assert.Equal(5.0, result.Bits, Tolerance); + Assert.Equal(0.625, result.Bytes, Tolerance); + } + + [Fact(DisplayName = "DataSize comparison should produce the expected result (left equal to right)")] + public void DataSizeComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + DataSize left = DataSize.FromBytes(1234.0); + DataSize right = DataSize.FromBytes(1234.0); + + // When / Then + Assert.Equal(0, DataSize.Compare(left, right)); + Assert.Equal(0, left.CompareTo(right)); + Assert.Equal(0, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "DataSize comparison should produce the expected result (left greater than right)")] + public void DataSizeComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + DataSize left = DataSize.FromBytes(4567.0); + DataSize right = DataSize.FromBytes(1234.0); + + // When / Then + Assert.Equal(1, DataSize.Compare(left, right)); + Assert.Equal(1, left.CompareTo(right)); + Assert.Equal(1, left.CompareTo((object)right)); + Assert.True(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.False(left <= right); + } + + [Fact(DisplayName = "DataSize comparison should produce the expected result (left less than right)")] + public void DataSizeComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + DataSize left = DataSize.FromBytes(1234.0); + DataSize right = DataSize.FromBytes(4567.0); + + // When / Then + Assert.Equal(-1, DataSize.Compare(left, right)); + Assert.Equal(-1, left.CompareTo(right)); + Assert.Equal(-1, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.False(left >= right); + Assert.True(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "DataSize equality should produce the expected result (left equal to right)")] + public void DataSizeEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + DataSize left = DataSize.FromBytes(2048.0); + DataSize right = DataSize.FromBytes(2048.0); + + // When / Then + Assert.True(DataSize.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "DataSize equality should produce the expected result (left not equal to right)")] + public void DataSizeEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + DataSize left = DataSize.FromBytes(2048.0); + DataSize right = DataSize.FromBytes(4096.0); + + // When / Then + Assert.False(DataSize.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "DataSize.ToString should produce the expected result")] + public void DataSizeToStringShouldProduceExpectedResult() + { + // Given / When + // Use values that render nicely across several specifiers. + DataSize size = DataSize.FromBytes(1_048_576.0); // 1 MiB + + // Then (explicit formatting + scale) + Assert.Equal("8,388,608.000 b", $"{size:b3}"); + Assert.Equal("1,048,576.000 B", $"{size:B3}"); + Assert.Equal("8,192.000 Kib", $"{size:Kib3}"); + Assert.Equal("1,024.000 KiB", $"{size:KiB3}"); + Assert.Equal("8,388.608 Kb", $"{size:Kb3}"); + Assert.Equal("1,048.576 KB", $"{size:KB3}"); + Assert.Equal("8.000 Mib", $"{size:Mib3}"); + Assert.Equal("1.000 MiB", $"{size:MiB3}"); + } + + [Fact(DisplayName = "DataSize.ToString should honor custom culture separators")] + public void DataSizeToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + DataSize size = DataSize.FromKiloBytes(1234.56); // 1,234.56 KB + + // When + string formatted = size.ToString("KB2", customCulture); + + // Then (German uses '.' for thousands and ',' for decimals) + Assert.Equal("1.234,56 KB", formatted); + } +} diff --git a/OnixLabs.Units.UnitTests/DistanceTests.cs b/OnixLabs.Units.UnitTests/DistanceTests.cs new file mode 100644 index 0000000..dec25e3 --- /dev/null +++ b/OnixLabs.Units.UnitTests/DistanceTests.cs @@ -0,0 +1,666 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Globalization; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class DistanceTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+42; + + [Fact(DisplayName = "Distance.Zero should produce the expected result")] + public void DistanceZeroShouldProduceExpectedResult() + { + // Given / When + Distance distance = Distance.Zero; + + // Then + Assert.Equal(0.0, distance.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromQuectometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void DistanceFromQuectometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromQuectometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromRontometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e3)] + [InlineData(2.5, 2.5e3)] + public void DistanceFromRontometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromRontometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromYoctometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void DistanceFromYoctometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromYoctometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromZeptometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e9)] + [InlineData(2.5, 2.5e9)] + public void DistanceFromZeptometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromZeptometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromAttometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void DistanceFromAttometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromAttometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromFemtometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e15)] + [InlineData(2.5, 2.5e15)] + public void DistanceFromFemtometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromFemtometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromPicometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void DistanceFromPicometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromPicometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromNanometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e21)] + [InlineData(2.5, 2.5e21)] + public void DistanceFromNanometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromNanometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromMicrometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void DistanceFromMicrometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromMicrometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromMillimeters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void DistanceFromMillimetersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromMillimeters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromCentimeters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e28)] + [InlineData(2.5, 2.5e28)] + public void DistanceFromCentimetersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromCentimeters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromDecimeters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e29)] + [InlineData(2.5, 2.5e29)] + public void DistanceFromDecimetersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromDecimeters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromMeters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void DistanceFromMetersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromMeters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromDecameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e31)] + [InlineData(2.5, 2.5e31)] + public void DistanceFromDecametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromDecameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromHectometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e32)] + [InlineData(2.5, 2.5e32)] + public void DistanceFromHectometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromHectometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromKilometers should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e33)] + [InlineData(2.5, 2.5e33)] + public void DistanceFromKilometersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromKilometers(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromMegameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void DistanceFromMegametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromMegameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromGigameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e39)] + [InlineData(2.5, 2.5e39)] + public void DistanceFromGigametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromGigameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromTerameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void DistanceFromTerametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromTerameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromPetameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e45)] + [InlineData(2.5, 2.5e45)] + public void DistanceFromPetametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromPetameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromExameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void DistanceFromExametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromExameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromZettameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e51)] + [InlineData(2.5, 2.5e51)] + public void DistanceFromZettametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromZettameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromYottameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e54)] + [InlineData(2.5, 2.5e54)] + public void DistanceFromYottametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromYottameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromRonnameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e57)] + [InlineData(2.5, 2.5e57)] + public void DistanceFromRonnametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromRonnameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromQuettameters should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e60)] + [InlineData(2.5, 2.5e60)] + public void DistanceFromQuettametersShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromQuettameters(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromInches should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.54e28)] + [InlineData(2.5, 6.35e28)] + public void DistanceFromInchesShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromInches(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromFeet should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.048e29)] + [InlineData(2.5, 7.62e29)] + public void DistanceFromFeetShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromFeet(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromYards should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.144e29)] + [InlineData(2.5, 2.286e30)] + public void DistanceFromYardsShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromYards(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromMiles should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.609344e33)] + [InlineData(2.5, 4.02336e33)] + public void DistanceFromMilesShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromMiles(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromNauticalMiles should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.852e33)] + [InlineData(2.5, 4.63e33)] + public void DistanceFromNauticalMilesShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromNauticalMiles(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromFermis should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e15)] + [InlineData(2.5, 2.5e15)] + public void DistanceFromFermisShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromFermis(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromAngstroms should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e20)] + [InlineData(2.5, 2.5e20)] + public void DistanceFromAngstromsShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromAngstroms(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromAstronomicalUnits should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.495978707e41)] + [InlineData(2.5, 3.7399467675e41)] + public void DistanceFromAstronomicalUnitsShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromAstronomicalUnits(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromLightYears should produce the expected QuectoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.4607304725808e45)] + [InlineData(2.5, 2.3651826181452e46)] + public void DistanceFromLightYearsShouldProduceExpectedQuectoMeters(double value, double expectedQuectometers) + { + // Given / When + Distance d = Distance.FromLightYears(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Theory(DisplayName = "Distance.FromParsecs should produce the expected QuectoMeters")] + [InlineData(0.0)] + [InlineData(1.0)] + [InlineData(2.5)] + public void DistanceFromParsecsShouldProduceExpectedQuectoMeters(double value) + { + // Given + const double metersPerParsec = 1.495978707e11 * 648000.0 / Math.PI; + const double qmPerParsec = metersPerParsec * 1e30; + double expectedQuectometers = value * qmPerParsec; + + // When + Distance d = Distance.FromParsecs(value); + + // Then + Assert.Equal(expectedQuectometers, d.QuectoMeters, Tolerance); + } + + [Fact(DisplayName = "Distance.Add should produce the expected result")] + public void DistanceAddShouldProduceExpectedValue() + { + // Given + Distance left = Distance.FromMeters(1500.0); + Distance right = Distance.FromMeters(500.0); + + // When + Distance result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.Meters, Tolerance); + } + + [Fact(DisplayName = "Distance.Subtract should produce the expected result")] + public void DistanceSubtractShouldProduceExpectedValue() + { + // Given + Distance left = Distance.FromMeters(1500.0); + Distance right = Distance.FromMeters(400.0); + + // When + Distance result = left.Subtract(right); + + // Then + Assert.Equal(1100.0, result.Meters, Tolerance); + } + + [Fact(DisplayName = "Distance.Multiply should produce the expected result")] + public void DistanceMultiplyShouldProduceExpectedValue() + { + // Given + Distance left = Distance.FromMeters(10.0); // 1e31 qm + Distance right = Distance.FromMeters(3.0); // 3e30 qm + + // When + Distance result = left.Multiply(right); // 1e31 * 3e30 = 3e61 qm + + // Then + Assert.Equal(1e31, left.QuectoMeters, Tolerance); + Assert.Equal(3e30, right.QuectoMeters, Tolerance); + Assert.Equal(3e61, result.QuectoMeters, Tolerance); + } + + [Fact(DisplayName = "Distance.Divide should produce the expected result")] + public void DistanceDivideShouldProduceExpectedValue() + { + // Given + Distance left = Distance.FromMeters(100.0); // 1e32 qm + Distance right = Distance.FromMeters(20.0); // 2e31 qm + + // When + Distance result = left.Divide(right); // 1e32 / 2e31 = 5 qm + + // Then + Assert.Equal(5.0, result.QuectoMeters, Tolerance); + Assert.Equal(5e-30, result.Meters, Tolerance); + } + + [Fact(DisplayName = "Distance comparison should produce the expected result (left equal to right)")] + public void DistanceComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Distance left = Distance.FromMeters(1234.0); + Distance right = Distance.FromMeters(1234.0); + + // When / Then + Assert.Equal(0, Distance.Compare(left, right)); + Assert.Equal(0, left.CompareTo(right)); + Assert.Equal(0, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "Distance comparison should produce the expected result (left greater than right)")] + public void DistanceComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Distance left = Distance.FromMeters(4567.0); + Distance right = Distance.FromMeters(1234.0); + + // When / Then + Assert.Equal(1, Distance.Compare(left, right)); + Assert.Equal(1, left.CompareTo(right)); + Assert.Equal(1, left.CompareTo((object)right)); + Assert.True(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.False(left <= right); + } + + [Fact(DisplayName = "Distance comparison should produce the expected result (left less than right)")] + public void DistanceComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Distance left = Distance.FromMeters(1234.0); + Distance right = Distance.FromMeters(4567.0); + + // When / Then + Assert.Equal(-1, Distance.Compare(left, right)); + Assert.Equal(-1, left.CompareTo(right)); + Assert.Equal(-1, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.False(left >= right); + Assert.True(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "Distance equality should produce the expected result (left equal to right)")] + public void DistanceEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Distance left = Distance.FromKilometers(2.0); + Distance right = Distance.FromMeters(2000.0); + + // When / Then + Assert.True(Distance.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Distance equality should produce the expected result (left not equal to right)")] + public void DistanceEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Distance left = Distance.FromKilometers(2.0); + Distance right = Distance.FromMeters(2500.0); + + // When / Then + Assert.False(Distance.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Distance.ToString should produce the expected result")] + public void DistanceToStringShouldProduceExpectedResult() + { + // Given + Distance d = Distance.FromMeters(1000.0); + + // When / Then + Assert.Equal("1,000.000 m", $"{d:m3}"); + Assert.Equal("1.000 km", $"{d:km3}"); + Assert.Equal("10.000 hm", $"{d:hm3}"); + Assert.Equal("100.000 dam", $"{d:dam3}"); + Assert.Equal("100,000.000 cm", $"{d:cm3}"); + Assert.Equal("39,370.079 in", $"{d:in3}"); + Assert.Equal("3,280.840 ft", $"{d:ft3}"); + Assert.Equal("1,093.613 yd", $"{d:yd3}"); + } + + [Fact(DisplayName = "Distance.ToString should honor custom culture separators")] + public void DistanceToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Distance d = Distance.FromMeters(1234.56); + + // When + string formatted = d.ToString("m2", customCulture); + + // Then + Assert.Equal("1.234,56 m", formatted); + } +} diff --git a/OnixLabs.Units.UnitTests/OnixLabs.Units.UnitTests.csproj b/OnixLabs.Units.UnitTests/OnixLabs.Units.UnitTests.csproj new file mode 100644 index 0000000..a828a8f --- /dev/null +++ b/OnixLabs.Units.UnitTests/OnixLabs.Units.UnitTests.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/OnixLabs.Units.UnitTests/TemperatureTests.cs b/OnixLabs.Units.UnitTests/TemperatureTests.cs new file mode 100644 index 0000000..71ade4c --- /dev/null +++ b/OnixLabs.Units.UnitTests/TemperatureTests.cs @@ -0,0 +1,478 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Globalization; + +namespace OnixLabs.Units.UnitTests; + +public sealed class TemperatureTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e-12; + + [Fact(DisplayName = "Temperature.Zero should produce the expected result")] + public void TemperatureZeroShouldProduceExpectedResult() + { + // Given / When + Temperature temperature = Temperature.Zero; + + // Then + Assert.Equal(-273.15, temperature.Celsius, Tolerance); + Assert.Equal(0.0, temperature.Kelvin, Tolerance); + Assert.Equal(-459.67, temperature.Fahrenheit, Tolerance); + Assert.Equal(559.725, temperature.Delisle, Tolerance); + Assert.Equal(-90.1395, temperature.Newton, Tolerance); + Assert.Equal(0.0, temperature.Rankine, Tolerance); + Assert.Equal(-218.52, temperature.Reaumur, Tolerance); + Assert.Equal(-135.90375, temperature.Romer, Tolerance); + } + + [Theory(DisplayName = "Temperature.FromCelsius should produce the expected result")] + [InlineData(-273.15, -273.15, 0.0, -459.67, 559.725, -90.1395, 0.0, -218.52, -135.90375)] + [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0, 7.5)] + [InlineData(100.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0, 60.0)] + [InlineData(-40.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0, -13.5)] + [InlineData(20.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0, 18.0)] + public void TemperatureFromCelsiusShouldProduceExpectedResult( + double celsius, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur, + double expectedRomer) + { + // When + Temperature temperature = Temperature.FromCelsius(celsius); + + // Then + Assert.Equal(expectedCelsius, temperature.Celsius, Tolerance); + Assert.Equal(expectedKelvin, temperature.Kelvin, Tolerance); + Assert.Equal(expectedFahrenheit, temperature.Fahrenheit, Tolerance); + Assert.Equal(expectedDelisle, temperature.Delisle, Tolerance); + Assert.Equal(expectedNewton, temperature.Newton, Tolerance); + Assert.Equal(expectedRankine, temperature.Rankine, Tolerance); + Assert.Equal(expectedReaumur, temperature.Reaumur, Tolerance); + Assert.Equal(expectedRomer, temperature.Romer, Tolerance); + } + + [Theory(DisplayName = "Temperature.FromDelisle should produce the expected result")] + [InlineData(559.725, -273.15, 0.0, -459.67, 559.725, -90.1395, 0.0, -218.52, -135.90375)] + [InlineData(150.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0, 7.5)] + [InlineData(0.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0, 60.0)] + [InlineData(210.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0, -13.5)] + [InlineData(120.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0, 18.0)] + public void TemperatureFromDelisleShouldProduceExpectedResult( + double delisle, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur, + double expectedRomer) + { + // When + Temperature temperature = Temperature.FromDelisle(delisle); + + // Then + Assert.Equal(expectedCelsius, temperature.Celsius, Tolerance); + Assert.Equal(expectedKelvin, temperature.Kelvin, Tolerance); + Assert.Equal(expectedFahrenheit, temperature.Fahrenheit, Tolerance); + Assert.Equal(expectedDelisle, temperature.Delisle, Tolerance); + Assert.Equal(expectedNewton, temperature.Newton, Tolerance); + Assert.Equal(expectedRankine, temperature.Rankine, Tolerance); + Assert.Equal(expectedReaumur, temperature.Reaumur, Tolerance); + Assert.Equal(expectedRomer, temperature.Romer, Tolerance); + } + + [Theory(DisplayName = "Temperature.FromFahrenheit should produce the expected result")] + [InlineData(-459.67, -273.15, 0.0, -459.67, 559.725, -90.1395, 0.0, -218.52, -135.90375)] + [InlineData(32.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0, 7.5)] + [InlineData(212.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0, 60.0)] + [InlineData(-40.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0, -13.5)] + [InlineData(68.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0, 18.0)] + public void TemperatureFromFahrenheitShouldProduceExpectedResult( + double fahrenheit, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur, + double expectedRomer) + { + Temperature temperature = Temperature.FromFahrenheit(fahrenheit); + + Assert.Equal(expectedCelsius, temperature.Celsius, Tolerance); + Assert.Equal(expectedKelvin, temperature.Kelvin, Tolerance); + Assert.Equal(expectedFahrenheit, temperature.Fahrenheit, Tolerance); + Assert.Equal(expectedDelisle, temperature.Delisle, Tolerance); + Assert.Equal(expectedNewton, temperature.Newton, Tolerance); + Assert.Equal(expectedRankine, temperature.Rankine, Tolerance); + Assert.Equal(expectedReaumur, temperature.Reaumur, Tolerance); + Assert.Equal(expectedRomer, temperature.Romer, Tolerance); + } + + [Theory(DisplayName = "Temperature.FromKelvin should produce the expected result")] + [InlineData(0.0, -273.15, 0.0, -459.67, 559.725, -90.1395, 0.0, -218.52, -135.90375)] + [InlineData(273.15, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0, 7.5)] + [InlineData(373.15, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0, 60.0)] + [InlineData(233.15, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0, -13.5)] + [InlineData(293.15, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0, 18.0)] + public void TemperatureFromKelvinShouldProduceExpectedResult( + double kelvin, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur, + double expectedRomer) + { + Temperature temperature = Temperature.FromKelvin(kelvin); + + Assert.Equal(expectedCelsius, temperature.Celsius, Tolerance); + Assert.Equal(expectedKelvin, temperature.Kelvin, Tolerance); + Assert.Equal(expectedFahrenheit, temperature.Fahrenheit, Tolerance); + Assert.Equal(expectedDelisle, temperature.Delisle, Tolerance); + Assert.Equal(expectedNewton, temperature.Newton, Tolerance); + Assert.Equal(expectedRankine, temperature.Rankine, Tolerance); + Assert.Equal(expectedReaumur, temperature.Reaumur, Tolerance); + Assert.Equal(expectedRomer, temperature.Romer, Tolerance); + } + + [Theory(DisplayName = "Temperature.FromNewton should produce the expected result")] + [InlineData(-90.1395, -273.15, 0.0, -459.67, 559.725, -90.1395, 0.0, -218.52, -135.90375)] + [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0, 7.5)] + [InlineData(33.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0, 60.0)] + [InlineData(-13.2, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0, -13.5)] + [InlineData(6.6, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0, 18.0)] + public void TemperatureFromNewtonShouldProduceExpectedResult( + double newton, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur, + double expectedRomer) + { + Temperature temperature = Temperature.FromNewton(newton); + + Assert.Equal(expectedCelsius, temperature.Celsius, Tolerance); + Assert.Equal(expectedKelvin, temperature.Kelvin, Tolerance); + Assert.Equal(expectedFahrenheit, temperature.Fahrenheit, Tolerance); + Assert.Equal(expectedDelisle, temperature.Delisle, Tolerance); + Assert.Equal(expectedNewton, temperature.Newton, Tolerance); + Assert.Equal(expectedRankine, temperature.Rankine, Tolerance); + Assert.Equal(expectedReaumur, temperature.Reaumur, Tolerance); + Assert.Equal(expectedRomer, temperature.Romer, Tolerance); + } + + [Theory(DisplayName = "Temperature.FromRankine should produce the expected result")] + [InlineData(0.0, -273.15, 0.0, -459.67, 559.725, -90.1395, 0.0, -218.52, -135.90375)] + [InlineData(491.67, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0, 7.5)] + [InlineData(671.67, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0, 60.0)] + [InlineData(419.67, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0, -13.5)] + [InlineData(527.67, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0, 18.0)] + public void TemperatureFromRankineShouldProduceExpectedResult( + double rankine, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur, + double expectedRomer) + { + Temperature temperature = Temperature.FromRankine(rankine); + + Assert.Equal(expectedCelsius, temperature.Celsius, Tolerance); + Assert.Equal(expectedKelvin, temperature.Kelvin, Tolerance); + Assert.Equal(expectedFahrenheit, temperature.Fahrenheit, Tolerance); + Assert.Equal(expectedDelisle, temperature.Delisle, Tolerance); + Assert.Equal(expectedNewton, temperature.Newton, Tolerance); + Assert.Equal(expectedRankine, temperature.Rankine, Tolerance); + Assert.Equal(expectedReaumur, temperature.Reaumur, Tolerance); + Assert.Equal(expectedRomer, temperature.Romer, Tolerance); + } + + [Theory(DisplayName = "Temperature.FromReaumur should produce the expected result")] + [InlineData(-218.52, -273.15, 0.0, -459.67, 559.725, -90.1395, 0.0, -218.52, -135.90375)] + [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0, 7.5)] + [InlineData(80.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0, 60.0)] + [InlineData(-32.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0, -13.5)] + [InlineData(16.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0, 18.0)] + public void TemperatureFromReaumurShouldProduceExpectedResult( + double reaumur, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur, + double expectedRomer) + { + Temperature temperature = Temperature.FromReaumur(reaumur); + + Assert.Equal(expectedCelsius, temperature.Celsius, Tolerance); + Assert.Equal(expectedKelvin, temperature.Kelvin, Tolerance); + Assert.Equal(expectedFahrenheit, temperature.Fahrenheit, Tolerance); + Assert.Equal(expectedDelisle, temperature.Delisle, Tolerance); + Assert.Equal(expectedNewton, temperature.Newton, Tolerance); + Assert.Equal(expectedRankine, temperature.Rankine, Tolerance); + Assert.Equal(expectedReaumur, temperature.Reaumur, Tolerance); + Assert.Equal(expectedRomer, temperature.Romer, Tolerance); + } + + [Theory(DisplayName = "Temperature.FromRomer should produce the expected result")] + [InlineData(-135.90375, -273.15, 0.0, -459.67, 559.725, -90.1395, 0.0, -218.52, -135.90375)] + [InlineData(7.5, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0, 7.5)] + [InlineData(60.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0, 60.0)] + [InlineData(-13.5, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0, -13.5)] + [InlineData(18.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0, 18.0)] + public void TemperatureFromRomerShouldProduceExpectedResult( + double romer, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur, + double expectedRomer) + { + Temperature temperature = Temperature.FromRomer(romer); + + Assert.Equal(expectedCelsius, temperature.Celsius, Tolerance); + Assert.Equal(expectedKelvin, temperature.Kelvin, Tolerance); + Assert.Equal(expectedFahrenheit, temperature.Fahrenheit, Tolerance); + Assert.Equal(expectedDelisle, temperature.Delisle, Tolerance); + Assert.Equal(expectedNewton, temperature.Newton, Tolerance); + Assert.Equal(expectedRankine, temperature.Rankine, Tolerance); + Assert.Equal(expectedReaumur, temperature.Reaumur, Tolerance); + Assert.Equal(expectedRomer, temperature.Romer, Tolerance); + } + + [Fact(DisplayName = "Temperature.Add should produce the expected result")] + public void TemperatureAddShouldProduceExpectedValue() + { + // Given + Temperature left = Temperature.FromKelvin(100.0); + Temperature right = Temperature.FromKelvin(50.0); + + // When + Temperature result = left.Add(right); + + // Then + Assert.Equal(150.0, result.Kelvin, Tolerance); + } + + [Fact(DisplayName = "Temperature.Subtract should produce the expected result")] + public void TemperatureSubtractShouldProduceExpectedValue() + { + // Given + Temperature left = Temperature.FromKelvin(100.0); + Temperature right = Temperature.FromKelvin(40.0); + + // When + Temperature result = left.Subtract(right); + + // Then + Assert.Equal(60.0, result.Kelvin, Tolerance); + } + + [Fact(DisplayName = "Temperature.Multiply should produce the expected result")] + public void TemperatureMultiplyShouldProduceExpectedValue() + { + // Given + Temperature left = Temperature.FromKelvin(10.0); + Temperature right = Temperature.FromKelvin(3.0); + + // When + Temperature result = left.Multiply(right); + + // Then + Assert.Equal(30.0, result.Kelvin, Tolerance); + } + + [Fact(DisplayName = "Temperature.Divide should produce the expected result")] + public void TemperatureDivideShouldProduceExpectedValue() + { + // Given + Temperature left = Temperature.FromKelvin(100.0); + Temperature right = Temperature.FromKelvin(20.0); + + // When + Temperature result = left.Divide(right); + + // Then + Assert.Equal(5.0, result.Kelvin, Tolerance); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left equal to right)")] + public void TemperatureComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(123); + + // When / Then + Assert.Equal(0, Temperature.Compare(left, right)); + Assert.Equal(0, left.CompareTo(right)); + Assert.Equal(0, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left greater than right)")] + public void TemperatureComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Temperature left = Temperature.FromKelvin(456); + Temperature right = Temperature.FromKelvin(123); + + // When / Then + Assert.Equal(1, Temperature.Compare(left, right)); + Assert.Equal(1, left.CompareTo(right)); + Assert.Equal(1, left.CompareTo((object)right)); + Assert.True(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.False(left <= right); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left greater than or equal to right)")] + public void TemperatureComparisonShouldProduceExpectedLeftGreaterThanOrEqualToRight() + { + // Given + Temperature left = Temperature.FromKelvin(456); + Temperature right = Temperature.FromKelvin(123); + + // When / Then + Assert.Equal(1, Temperature.Compare(left, right)); + Assert.Equal(1, left.CompareTo(right)); + Assert.Equal(1, left.CompareTo((object)right)); + Assert.True(left > right); + Assert.True(left >= right); + Assert.False(left < right); + Assert.False(left <= right); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left less than right)")] + public void TemperatureComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(456); + + // When / Then + Assert.Equal(-1, Temperature.Compare(left, right)); + Assert.Equal(-1, left.CompareTo(right)); + Assert.Equal(-1, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.False(left >= right); + Assert.True(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left less than or equal to right)")] + public void TemperatureComparisonShouldProduceExpectedLeftLessThanOrEqualToRight() + { + // Given + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(456); + + // When / Then + Assert.Equal(-1, Temperature.Compare(left, right)); + Assert.Equal(-1, left.CompareTo(right)); + Assert.Equal(-1, left.CompareTo((object)right)); + Assert.False(left > right); + Assert.False(left >= right); + Assert.True(left < right); + Assert.True(left <= right); + } + + [Fact(DisplayName = "Temperature equality should produce the expected result (left equal to right)")] + public void TemperatureEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(123); + + // When / Then + Assert.True(Temperature.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Temperature equality should produce the expected result (left not equal to right)")] + public void TemperatureEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(456); + + // When / Then + Assert.False(Temperature.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Temperature.ToString should produce the expected result")] + public void TemperatureToStringShouldProduceExpectedResult() + { + // Given / When + Temperature temperature = Temperature.FromCelsius(100.0); + + // Then + Assert.Equal("373.150 K", $"{temperature:K3}"); + Assert.Equal("100.000 °C", $"{temperature:C3}"); + Assert.Equal("0.000 °De", $"{temperature:DE3}"); + Assert.Equal("212.000 °F", $"{temperature:F3}"); + Assert.Equal("33.000 °N", $"{temperature:N3}"); + Assert.Equal("671.670 °R", $"{temperature:R3}"); + Assert.Equal("80.000 °Ré", $"{temperature:RE3}"); + Assert.Equal("60.000 °Rø", $"{temperature:RO3}"); + } + + [Fact(DisplayName = "Temperature.ToString should honor custom culture separators")] + public void TemperatureToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Temperature temperature = Temperature.FromKelvin(1234.56); + + // When + string formatted = temperature.ToString("K2", customCulture); + + // Then + Assert.Equal("1.234,56 K", formatted); + } +} diff --git a/OnixLabs.Units/Area.Arithmetic.cs b/OnixLabs.Units/Area.Arithmetic.cs new file mode 100644 index 0000000..9e46afc --- /dev/null +++ b/OnixLabs.Units/Area.Arithmetic.cs @@ -0,0 +1,30 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Area +{ + /// + public static Area Add(Area left, Area right) => new(left.SquareQuectoMeters + right.SquareQuectoMeters); + + /// + public static Area Subtract(Area left, Area right) => new(left.SquareQuectoMeters - right.SquareQuectoMeters); + + /// + public static Area Multiply(Area left, Area right) => new(left.SquareQuectoMeters * right.SquareQuectoMeters); + + /// + public static Area Divide(Area left, Area right) => new(left.SquareQuectoMeters / right.SquareQuectoMeters); +} diff --git a/OnixLabs.Units/Area.Comparable.cs b/OnixLabs.Units/Area.Comparable.cs new file mode 100644 index 0000000..abc265f --- /dev/null +++ b/OnixLabs.Units/Area.Comparable.cs @@ -0,0 +1,29 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Area +{ + /// + public static int Compare(Area left, Area right) => left.SquareQuectoMeters.CompareTo(right.SquareQuectoMeters); + + /// + public int CompareTo(Area other) => Compare(this, other); + + /// + public int CompareTo(object? obj) => this.CompareToObject(obj); +} diff --git a/OnixLabs.Units/Area.Constants.cs b/OnixLabs.Units/Area.Constants.cs new file mode 100644 index 0000000..bd6ff89 --- /dev/null +++ b/OnixLabs.Units/Area.Constants.cs @@ -0,0 +1,126 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Area +{ + /// + public static Area Zero => new(T.Zero); + + private const string SquareQuectoMetersSpecifier = "sqqm"; + private const string SquareQuectoMetersSymbol = "qm²"; + + private const string SquareRontoMetersSpecifier = "sqrm"; + private const string SquareRontoMetersSymbol = "rm²"; + + private const string SquareYoctoMetersSpecifier = "sqym"; + private const string SquareYoctoMetersSymbol = "ym²"; + + private const string SquareZeptoMetersSpecifier = "sqzm"; + private const string SquareZeptoMetersSymbol = "zm²"; + + private const string SquareAttoMetersSpecifier = "sqam"; + private const string SquareAttoMetersSymbol = "am²"; + + private const string SquareFemtoMetersSpecifier = "sqfm"; + private const string SquareFemtoMetersSymbol = "fm²"; + + private const string SquarePicoMetersSpecifier = "sqpm"; + private const string SquarePicoMetersSymbol = "pm²"; + + private const string SquareNanoMetersSpecifier = "sqnm"; + private const string SquareNanoMetersSymbol = "nm²"; + + private const string SquareMicroMetersSpecifier = "squm"; + private const string SquareMicroMetersSymbol = "µm²"; + + private const string SquareMilliMetersSpecifier = "sqmm"; + private const string SquareMilliMetersSymbol = "mm²"; + + private const string SquareCentiMetersSpecifier = "sqcm"; + private const string SquareCentiMetersSymbol = "cm²"; + + private const string SquareDeciMetersSpecifier = "sqdm"; + private const string SquareDeciMetersSymbol = "dm²"; + + private const string SquareMetersSpecifier = "sqm"; + private const string SquareMetersSymbol = "m²"; + + private const string SquareDecaMetersSpecifier = "sqdam"; + private const string SquareDecaMetersSymbol = "dam²"; + + private const string SquareHectoMetersSpecifier = "sqhm"; + private const string SquareHectoMetersSymbol = "hm²"; + + private const string SquareKiloMetersSpecifier = "sqkm"; + private const string SquareKiloMetersSymbol = "km²"; + + private const string SquareMegaMetersSpecifier = "sqMm"; + private const string SquareMegaMetersSymbol = "Mm²"; + + private const string SquareGigaMetersSpecifier = "sqGm"; + private const string SquareGigaMetersSymbol = "Gm²"; + + private const string SquareTeraMetersSpecifier = "sqTm"; + private const string SquareTeraMetersSymbol = "Tm²"; + + private const string SquarePetaMetersSpecifier = "sqPm"; + private const string SquarePetaMetersSymbol = "Pm²"; + + private const string SquareExaMetersSpecifier = "sqEm"; + private const string SquareExaMetersSymbol = "Em²"; + + private const string SquareZettaMetersSpecifier = "sqZm"; + private const string SquareZettaMetersSymbol = "Zm²"; + + private const string SquareYottaMetersSpecifier = "sqYm"; + private const string SquareYottaMetersSymbol = "Ym²"; + + private const string SquareRonnaMetersSpecifier = "sqRm"; + private const string SquareRonnaMetersSymbol = "Rm²"; + + private const string SquareQuettaMetersSpecifier = "sqQm"; + private const string SquareQuettaMetersSymbol = "Qm²"; + + private const string SquareInchesSpecifier = "sqin"; + private const string SquareInchesSymbol = "in²"; + + private const string SquareFeetSpecifier = "sqft"; + private const string SquareFeetSymbol = "ft²"; + + private const string SquareYardsSpecifier = "sqyd"; + private const string SquareYardsSymbol = "yd²"; + + private const string SquareMilesSpecifier = "sqmi"; + private const string SquareMilesSymbol = "mi²"; + + private const string SquareNauticalMilesSpecifier = "sqnmi"; + private const string SquareNauticalMilesSymbol = "nmi²"; + + private const string SquareFermisSpecifier = "sqfmi"; + private const string SquareFermisSymbol = "fmi²"; + + private const string SquareAngstromsSpecifier = "sqa"; + private const string SquareAngstromsSymbol = "Ų"; + + private const string SquareAstronomicalUnitsSpecifier = "sqau"; + private const string SquareAstronomicalUnitsSymbol = "au²"; + + private const string SquareLightYearsSpecifier = "sqly"; + private const string SquareLightYearsSymbol = "ly²"; + + private const string SquareParsecsSpecifier = "sqpc"; + private const string SquareParsecsSymbol = "pc²"; +} diff --git a/OnixLabs.Units/Area.Equatable.cs b/OnixLabs.Units/Area.Equatable.cs new file mode 100644 index 0000000..f881a4c --- /dev/null +++ b/OnixLabs.Units/Area.Equatable.cs @@ -0,0 +1,33 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct Area +{ + /// + public static bool Equals(Area left, Area right) => left.SquareQuectoMeters == right.SquareQuectoMeters; + + /// + public bool Equals(Area other) => Equals(this, other); + + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Area other && Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(SquareQuectoMeters); +} diff --git a/OnixLabs.Units/Area.Format.cs b/OnixLabs.Units/Area.Format.cs new file mode 100644 index 0000000..20494aa --- /dev/null +++ b/OnixLabs.Units/Area.Format.cs @@ -0,0 +1,29 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Area +{ + /// + public bool TryFormat( + Span destination, + out int charsWritten, + ReadOnlySpan format, + IFormatProvider? provider + ) => ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Area.From.cs b/OnixLabs.Units/Area.From.cs new file mode 100644 index 0000000..d382955 --- /dev/null +++ b/OnixLabs.Units/Area.From.cs @@ -0,0 +1,269 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Area +{ + /// + /// Creates a new instance from the specified Square Quectometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareQuectometers(T value) => new(value); + + /// + /// Creates a new instance from the specified Square Rontometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareRontometers(T value) => new(value * T.CreateChecked(1e6)); + + /// + /// Creates a new instance from the specified Square Yoctometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareYoctometers(T value) => new(value * T.CreateChecked(1e12)); + + /// + /// Creates a new instance from the specified Square Zeptometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareZeptometers(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from the specified Square Attometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareAttometers(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from the specified Square Femtometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareFemtometers(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified Square Picometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquarePicometers(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from the specified Square Nanometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareNanometers(T value) => new(value * T.CreateChecked(1e42)); + + /// + /// Creates a new instance from the specified Square Micrometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareMicrometers(T value) => new(value * T.CreateChecked(1e48)); + + /// + /// Creates a new instance from the specified Square Millimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareMillimeters(T value) => new(value * T.CreateChecked(1e54)); + + /// + /// Creates a new instance from the specified Square Centimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareCentimeters(T value) => new(value * T.CreateChecked(1e56)); + + /// + /// Creates a new instance from the specified Square Decimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareDecimeters(T value) => new(value * T.CreateChecked(1e58)); + + /// + /// Creates a new instance from the specified Square Meters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareMeters(T value) => new(value * T.CreateChecked(1e60)); + + /// + /// Creates a new instance from the specified Square Decameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareDecameters(T value) => new(value * T.CreateChecked(1e62)); + + /// + /// Creates a new instance from the specified Square Hectometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareHectometers(T value) => new(value * T.CreateChecked(1e64)); + + /// + /// Creates a new instance from the specified Square Kilometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareKilometers(T value) => new(value * T.CreateChecked(1e66)); + + /// + /// Creates a new instance from the specified Square Megameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareMegameters(T value) => new(value * T.CreateChecked(1e72)); + + /// + /// Creates a new instance from the specified Square Gigameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareGigameters(T value) => new(value * T.CreateChecked(1e78)); + + /// + /// Creates a new instance from the specified Square Terameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareTerameters(T value) => new(value * T.CreateChecked(1e84)); + + /// + /// Creates a new instance from the specified Square Petameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquarePetameters(T value) => new(value * T.CreateChecked(1e90)); + + /// + /// Creates a new instance from the specified Square Exameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareExameters(T value) => new(value * T.CreateChecked(1e96)); + + /// + /// Creates a new instance from the specified Square Zettameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareZettameters(T value) => new(value * T.CreateChecked(1e102)); + + /// + /// Creates a new instance from the specified Square Yottameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareYottameters(T value) => new(value * T.CreateChecked(1e108)); + + /// + /// Creates a new instance from the specified Square Ronnameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareRonnameters(T value) => new(value * T.CreateChecked(1e114)); + + /// + /// Creates a new instance from the specified Square Quettameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareQuettameters(T value) => new(value * T.CreateChecked(1e120)); + + /// + /// Creates a new instance from the specified Square Inches value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareInches(T value) => new(value * T.CreateChecked(6.4516e56)); + + /// + /// Creates a new instance from the specified Square Feet value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareFeet(T value) => new(value * T.CreateChecked(9.290304e58)); + + /// + /// Creates a new instance from the specified Square Yards value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareYards(T value) => new(value * T.CreateChecked(8.3612736e59)); + + /// + /// Creates a new instance from the specified Square Miles value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareMiles(T value) => new(value * T.CreateChecked(2.589988110336e66)); + + /// + /// Creates a new instance from the specified Square Nautical Miles value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareNauticalMiles(T value) => new(value * T.CreateChecked(3.4299040000e66)); + + /// + /// Creates a new instance from the specified Square Fermis value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareFermis(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified Square Angstroms value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareAngstroms(T value) => new(value * T.CreateChecked(1e40)); + + /// + /// Creates a new instance from the specified Square Astronomical Units value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareAstronomicalUnits(T value) => new(value * T.CreateChecked(2.2379522821e82)); + + /// + /// Creates a new instance from the specified Square Light Years value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareLightYears(T value) => new(value * T.CreateChecked(8.9505421074819e91)); + + /// + /// Creates a new instance from the specified Square Parsecs value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromSquareParsecs(T value) + { + T metersPerParsec = T.CreateChecked(149_597_870_700L) * T.CreateChecked(648000) / T.Pi; + T sqMetersPerSqParsec = metersPerParsec * metersPerParsec; + T sqQmPerSqParsec = sqMetersPerSqParsec * T.CreateChecked(1e60); + return new Area(value * sqQmPerSqParsec); + } +} diff --git a/OnixLabs.Units/Area.To.cs b/OnixLabs.Units/Area.To.cs new file mode 100644 index 0000000..1b6d95e --- /dev/null +++ b/OnixLabs.Units/Area.To.cs @@ -0,0 +1,80 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Globalization; + +namespace OnixLabs.Units; + +public readonly partial struct Area +{ + /// + public override string ToString() => ToString(SquareQuectoMetersSpecifier); + + /// + public string ToString(string? format, IFormatProvider? formatProvider) => ToString(format.AsSpan(), formatProvider); + + /// + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: SquareQuectoMetersSpecifier); + + (T value, string symbol) = specifier switch + { + SquareQuectoMetersSpecifier => (SquareQuectoMeters, SquareQuectoMetersSymbol), + SquareRontoMetersSpecifier => (SquareRontoMeters, SquareRontoMetersSymbol), + SquareYoctoMetersSpecifier => (SquareYoctoMeters, SquareYoctoMetersSymbol), + SquareZeptoMetersSpecifier => (SquareZeptoMeters, SquareZeptoMetersSymbol), + SquareAttoMetersSpecifier => (SquareAttoMeters, SquareAttoMetersSymbol), + SquareFemtoMetersSpecifier => (SquareFemtoMeters, SquareFemtoMetersSymbol), + SquarePicoMetersSpecifier => (SquarePicoMeters, SquarePicoMetersSymbol), + SquareNanoMetersSpecifier => (SquareNanoMeters, SquareNanoMetersSymbol), + SquareMicroMetersSpecifier => (SquareMicroMeters, SquareMicroMetersSymbol), + SquareMilliMetersSpecifier => (SquareMilliMeters, SquareMilliMetersSymbol), + SquareCentiMetersSpecifier => (SquareCentiMeters, SquareCentiMetersSymbol), + SquareDeciMetersSpecifier => (SquareDeciMeters, SquareDeciMetersSymbol), + SquareMetersSpecifier => (SquareMeters, SquareMetersSymbol), + SquareDecaMetersSpecifier => (SquareDecaMeters, SquareDecaMetersSymbol), + SquareHectoMetersSpecifier => (SquareHectoMeters, SquareHectoMetersSymbol), + SquareKiloMetersSpecifier => (SquareKiloMeters, SquareKiloMetersSymbol), + SquareMegaMetersSpecifier => (SquareMegaMeters, SquareMegaMetersSymbol), + SquareGigaMetersSpecifier => (SquareGigaMeters, SquareGigaMetersSymbol), + SquareTeraMetersSpecifier => (SquareTeraMeters, SquareTeraMetersSymbol), + SquarePetaMetersSpecifier => (SquarePetaMeters, SquarePetaMetersSymbol), + SquareExaMetersSpecifier => (SquareExaMeters, SquareExaMetersSymbol), + SquareZettaMetersSpecifier => (SquareZettaMeters, SquareZettaMetersSymbol), + SquareYottaMetersSpecifier => (SquareYottaMeters, SquareYottaMetersSymbol), + SquareRonnaMetersSpecifier => (SquareRonnaMeters, SquareRonnaMetersSymbol), + SquareQuettaMetersSpecifier => (SquareQuettaMeters, SquareQuettaMetersSymbol), + SquareInchesSpecifier => (SquareInches, SquareInchesSymbol), + SquareFeetSpecifier => (SquareFeet, SquareFeetSymbol), + SquareYardsSpecifier => (SquareYards, SquareYardsSymbol), + SquareMilesSpecifier => (SquareMiles, SquareMilesSymbol), + SquareNauticalMilesSpecifier => (SquareNauticalMiles, SquareNauticalMilesSymbol), + SquareFermisSpecifier => (SquareFermis, SquareFermisSymbol), + SquareAngstromsSpecifier => (SquareAngstroms, SquareAngstromsSymbol), + SquareAstronomicalUnitsSpecifier => (SquareAstronomicalUnits, SquareAstronomicalUnitsSymbol), + SquareLightYearsSpecifier => (SquareLightYears, SquareLightYearsSymbol), + SquareParsecsSpecifier => (SquareParsecs, SquareParsecsSymbol), + _ => throw ArgumentException.InvalidFormat(format, + "sqqm, sqrm, sqym, sqzm, sqam, sqfm, sqpm, sqnm, squm, sqmm, sqcm, sqdm, " + + "sqm, sqdam, sqhm, sqkm, sqMm, sqGm, sqTm, sqPm, sqEm, sqZm, sqYm, sqRm, " + + "sqQm, sqin, sqft, sqyd, sqmi, sqnmi, sqfmi, sqa, sqau, sqly, and sqpc") + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {symbol}"; + } +} diff --git a/OnixLabs.Units/Area.cs b/OnixLabs.Units/Area.cs new file mode 100644 index 0000000..c46e00e --- /dev/null +++ b/OnixLabs.Units/Area.cs @@ -0,0 +1,217 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Numerics; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of area. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +#pragma warning disable CA2231 +public readonly partial struct Area : IUnit> where T : IFloatingPoint +#pragma warning restore CA2231 +{ + /// + /// Initializes a new instance of the struct. + /// + /// The area unit in . + private Area(T value) => SquareQuectoMeters = value; + + /// + /// Gets the area in Square Quectometers (qm²). + /// + public T SquareQuectoMeters { get; } + + /// + /// Gets the area in Square Rontometers (rm²). + /// + public T SquareRontoMeters => SquareQuectoMeters / T.CreateChecked(1e6); + + /// + /// Gets the area in Square Yoctometers (ym²). + /// + public T SquareYoctoMeters => SquareQuectoMeters / T.CreateChecked(1e12); + + /// + /// Gets the area in Square Zeptometers (zm²). + /// + public T SquareZeptoMeters => SquareQuectoMeters / T.CreateChecked(1e18); + + /// + /// Gets the area in Square Attometers (am²). + /// + public T SquareAttoMeters => SquareQuectoMeters / T.CreateChecked(1e24); + + /// + /// Gets the area in Square Femtometers (fm²). + /// + public T SquareFemtoMeters => SquareQuectoMeters / T.CreateChecked(1e30); + + /// + /// Gets the area in Square Picometers (pm²). + /// + public T SquarePicoMeters => SquareQuectoMeters / T.CreateChecked(1e36); + + /// + /// Gets the area in Square Nanometers (nm²). + /// + public T SquareNanoMeters => SquareQuectoMeters / T.CreateChecked(1e42); + + /// + /// Gets the area in Square Micrometers (µm²). + /// + public T SquareMicroMeters => SquareQuectoMeters / T.CreateChecked(1e48); + + /// + /// Gets the area in Square Millimeters (mm²). + /// + public T SquareMilliMeters => SquareQuectoMeters / T.CreateChecked(1e54); + + /// + /// Gets the area in Square Centimeters (cm²). + /// + public T SquareCentiMeters => SquareQuectoMeters / T.CreateChecked(1e56); + + /// + /// Gets the area in Square Decimeters (dm²). + /// + public T SquareDeciMeters => SquareQuectoMeters / T.CreateChecked(1e58); + + /// + /// Gets the area in Square Meters (m²). + /// + public T SquareMeters => SquareQuectoMeters / T.CreateChecked(1e60); + + /// + /// Gets the area in Square Decameters (dam²). + /// + public T SquareDecaMeters => SquareQuectoMeters / T.CreateChecked(1e62); + + /// + /// Gets the area in Square Hectometers (hm²). + /// + public T SquareHectoMeters => SquareQuectoMeters / T.CreateChecked(1e64); + + /// + /// Gets the area in Square Kilometers (km²). + /// + public T SquareKiloMeters => SquareQuectoMeters / T.CreateChecked(1e66); + + /// + /// Gets the area in Square Megameters (Mm²). + /// + public T SquareMegaMeters => SquareQuectoMeters / T.CreateChecked(1e72); + + /// + /// Gets the area in Square Gigameters (Gm²). + /// + public T SquareGigaMeters => SquareQuectoMeters / T.CreateChecked(1e78); + + /// + /// Gets the area in Square Terameters (Tm²). + /// + public T SquareTeraMeters => SquareQuectoMeters / T.CreateChecked(1e84); + + /// + /// Gets the area in Square Petameters (Pm²). + /// + public T SquarePetaMeters => SquareQuectoMeters / T.CreateChecked(1e90); + + /// + /// Gets the area in Square Exameters (Em²). + /// + public T SquareExaMeters => SquareQuectoMeters / T.CreateChecked(1e96); + + /// + /// Gets the area in Square Zettameters (Zm²). + /// + public T SquareZettaMeters => SquareQuectoMeters / T.CreateChecked(1e102); + + /// + /// Gets the area in Square Yottameters (Ym²). + /// + public T SquareYottaMeters => SquareQuectoMeters / T.CreateChecked(1e108); + + /// + /// Gets the area in Square Ronnameters (Rm²). + /// + public T SquareRonnaMeters => SquareQuectoMeters / T.CreateChecked(1e114); + + /// + /// Gets the area in Square Quettameters (Qm²). + /// + public T SquareQuettaMeters => SquareQuectoMeters / T.CreateChecked(1e120); + + /// + /// Gets the area in Square Inches (in²). + /// + public T SquareInches => SquareQuectoMeters / T.CreateChecked(6.4516e56); + + /// + /// Gets the area in Square Feet (ft²). + /// + public T SquareFeet => SquareQuectoMeters / T.CreateChecked(9.290304e58); + + /// + /// Gets the area in Square Yards (yd²). + /// + public T SquareYards => SquareQuectoMeters / T.CreateChecked(8.3612736e59); + + /// + /// Gets the area in Square Miles (mi²). + /// + public T SquareMiles => SquareQuectoMeters / T.CreateChecked(2.589988110336e66); + + /// + /// Gets the area in Square Nautical Miles (nmi²). + /// + public T SquareNauticalMiles => SquareQuectoMeters / T.CreateChecked(3.4299040000e66); + + /// + /// Gets the area in Square Fermis (fmi²). + /// + public T SquareFermis => SquareQuectoMeters / T.CreateChecked(1e30); + + /// + /// Gets the area in Square Angstroms (Ų). + /// + public T SquareAngstroms => SquareQuectoMeters / T.CreateChecked(1e40); + + /// + /// Gets the area in Square Astronomical Units (au²). + /// + public T SquareAstronomicalUnits => SquareQuectoMeters / T.CreateChecked(2.2379522821e82); + + /// + /// Gets the area in Square Light Years (ly²). + /// + public T SquareLightYears => SquareQuectoMeters / T.CreateChecked(8.9505421074819e91); + + /// + /// Gets the area in Square Parsecs (pc²). + /// + public T SquareParsecs + { + get + { + T metersPerParsec = T.CreateChecked(149_597_870_700L) * T.CreateChecked(648000) / T.Pi; + T sqMetersPerSqParsec = metersPerParsec * metersPerParsec; + T sqQmPerSqParsec = sqMetersPerSqParsec * T.CreateChecked(1e60); + return SquareQuectoMeters / sqQmPerSqParsec; + } + } +} diff --git a/OnixLabs.Units/DataSize.Arithmetic.cs b/OnixLabs.Units/DataSize.Arithmetic.cs new file mode 100644 index 0000000..d624d04 --- /dev/null +++ b/OnixLabs.Units/DataSize.Arithmetic.cs @@ -0,0 +1,30 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + public static DataSize Add(DataSize left, DataSize right) => new(left.Bits + right.Bits); + + /// + public static DataSize Subtract(DataSize left, DataSize right) => new(left.Bits - right.Bits); + + /// + public static DataSize Multiply(DataSize left, DataSize right) => new(left.Bits * right.Bits); + + /// + public static DataSize Divide(DataSize left, DataSize right) => new(left.Bits / right.Bits); +} diff --git a/OnixLabs.Units/DataSize.Comparable.cs b/OnixLabs.Units/DataSize.Comparable.cs new file mode 100644 index 0000000..6ed4b59 --- /dev/null +++ b/OnixLabs.Units/DataSize.Comparable.cs @@ -0,0 +1,29 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + public static int Compare(DataSize left, DataSize right) => left.Bits.CompareTo(right.Bits); + + /// + public int CompareTo(DataSize other) => Compare(this, other); + + /// + public int CompareTo(object? obj) => this.CompareToObject(obj); +} diff --git a/OnixLabs.Units/DataSize.Constants.cs b/OnixLabs.Units/DataSize.Constants.cs new file mode 100644 index 0000000..a7b59de --- /dev/null +++ b/OnixLabs.Units/DataSize.Constants.cs @@ -0,0 +1,56 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + public static DataSize Zero => new(T.Zero); + + private const string BitsSpecifier = "b"; + private const string BytesSpecifier = "B"; + private const string KibiBitsSpecifier = "Kib"; + private const string KibiBytesSpecifier = "KiB"; + private const string KiloBitsSpecifier = "Kb"; + private const string KiloBytesSpecifier = "KB"; + private const string MebiBitsSpecifier = "Mib"; + private const string MebiBytesSpecifier = "MiB"; + private const string MegaBitsSpecifier = "Mb"; + private const string MegaBytesSpecifier = "MB"; + private const string GibiBitsSpecifier = "Gib"; + private const string GibiBytesSpecifier = "GiB"; + private const string GigaBitsSpecifier = "Gb"; + private const string GigaBytesSpecifier = "GB"; + private const string TebiBitsSpecifier = "Tib"; + private const string TebiBytesSpecifier = "TiB"; + private const string TeraBitsSpecifier = "Tb"; + private const string TeraBytesSpecifier = "TB"; + private const string PebiBitsSpecifier = "Pib"; + private const string PebiBytesSpecifier = "PiB"; + private const string PetaBitsSpecifier = "Pb"; + private const string PetaBytesSpecifier = "PB"; + private const string ExbiBitsSpecifier = "Eib"; + private const string ExbiBytesSpecifier = "EiB"; + private const string ExaBitsSpecifier = "Eb"; + private const string ExaBytesSpecifier = "EB"; + private const string ZebiBitsSpecifier = "Zib"; + private const string ZebiBytesSpecifier = "ZiB"; + private const string ZettaBitsSpecifier = "Zb"; + private const string ZettaBytesSpecifier = "ZB"; + private const string YobiBitsSpecifier = "Yib"; + private const string YobiBytesSpecifier = "YiB"; + private const string YottaBitsSpecifier = "Yb"; + private const string YottaBytesSpecifier = "YB"; +} diff --git a/OnixLabs.Units/DataSize.Equatable.cs b/OnixLabs.Units/DataSize.Equatable.cs new file mode 100644 index 0000000..b890550 --- /dev/null +++ b/OnixLabs.Units/DataSize.Equatable.cs @@ -0,0 +1,33 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + public static bool Equals(DataSize left, DataSize right) => left.Bits == right.Bits; + + /// + public bool Equals(DataSize other) => Equals(this, other); + + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is DataSize other && Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(Bits); +} diff --git a/OnixLabs.Units/DataSize.Format.cs b/OnixLabs.Units/DataSize.Format.cs new file mode 100644 index 0000000..90bd0de --- /dev/null +++ b/OnixLabs.Units/DataSize.Format.cs @@ -0,0 +1,29 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + public bool TryFormat( + Span destination, + out int charsWritten, + ReadOnlySpan format, + IFormatProvider? provider + ) => ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/DataSize.From.cs b/OnixLabs.Units/DataSize.From.cs new file mode 100644 index 0000000..4fc4b3c --- /dev/null +++ b/OnixLabs.Units/DataSize.From.cs @@ -0,0 +1,284 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + /// Creates a new instance from the specified Bits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromBits(T value) => new(value); + + /// + /// Creates a new instance from the specified Bytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromBytes(T value) => new(value * T.CreateChecked(8)); + + /// + /// Creates a new instance from the specified KibiBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromKibiBits(T value) => CreateFromBinaryValue(value, 1, false); + + /// + /// Creates a new instance from the specified KibiBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromKibiBytes(T value) => CreateFromBinaryValue(value, 1, true); + + /// + /// Creates a new instance from the specified KiloBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromKiloBits(T value) => CreateFromMetricValue(value, 1, false); + + /// + /// Creates a new instance from the specified KiloBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromKiloBytes(T value) => CreateFromMetricValue(value, 1, true); + + /// + /// Creates a new instance from the specified MebiBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromMebiBits(T value) => CreateFromBinaryValue(value, 2, false); + + /// + /// Creates a new instance from the specified MebiBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromMebiBytes(T value) => CreateFromBinaryValue(value, 2, true); + + /// + /// Creates a new instance from the specified MegaBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromMegaBits(T value) => CreateFromMetricValue(value, 2, false); + + /// + /// Creates a new instance from the specified MegaBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromMegaBytes(T value) => CreateFromMetricValue(value, 2, true); + + /// + /// Creates a new instance from the specified GibiBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromGibiBits(T value) => CreateFromBinaryValue(value, 3, false); + + /// + /// Creates a new instance from the specified GibiBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromGibiBytes(T value) => CreateFromBinaryValue(value, 3, true); + + /// + /// Creates a new instance from the specified GigaBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromGigaBits(T value) => CreateFromMetricValue(value, 3, false); + + /// + /// Creates a new instance from the specified GigaBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromGigaBytes(T value) => CreateFromMetricValue(value, 3, true); + + /// + /// Creates a new instance from the specified TebiBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromTebiBits(T value) => CreateFromBinaryValue(value, 4, false); + + /// + /// Creates a new instance from the specified TebiBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromTebiBytes(T value) => CreateFromBinaryValue(value, 4, true); + + /// + /// Creates a new instance from the specified TeraBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromTeraBits(T value) => CreateFromMetricValue(value, 4, false); + + /// + /// Creates a new instance from the specified TeraBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromTeraBytes(T value) => CreateFromMetricValue(value, 4, true); + + /// + /// Creates a new instance from the specified PebiBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromPebiBits(T value) => CreateFromBinaryValue(value, 5, false); + + /// + /// Creates a new instance from the specified PebiBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromPebiBytes(T value) => CreateFromBinaryValue(value, 5, true); + + /// + /// Creates a new instance from the specified PetaBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromPetaBits(T value) => CreateFromMetricValue(value, 5, false); + + /// + /// Creates a new instance from the specified PetaBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromPetaBytes(T value) => CreateFromMetricValue(value, 5, true); + + /// + /// Creates a new instance from the specified ExbiBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromExbiBits(T value) => CreateFromBinaryValue(value, 6, false); + + /// + /// Creates a new instance from the specified ExbiBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromExbiBytes(T value) => CreateFromBinaryValue(value, 6, true); + + /// + /// Creates a new instance from the specified ExaBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromExaBits(T value) => CreateFromMetricValue(value, 6, false); + + /// + /// Creates a new instance from the specified ExaBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromExaBytes(T value) => CreateFromMetricValue(value, 6, true); + + /// + /// Creates a new instance from the specified ZebiBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromZebiBits(T value) => CreateFromBinaryValue(value, 7, false); + + /// + /// Creates a new instance from the specified ZebiBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromZebiBytes(T value) => CreateFromBinaryValue(value, 7, true); + + /// + /// Creates a new instance from the specified ZettaBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromZettaBits(T value) => CreateFromMetricValue(value, 7, false); + + /// + /// Creates a new instance from the specified ZettaBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromZettaBytes(T value) => CreateFromMetricValue(value, 7, true); + + /// + /// Creates a new instance from the specified YobiBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromYobiBits(T value) => CreateFromBinaryValue(value, 8, false); + + /// + /// Creates a new instance from the specified YobiBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromYobiBytes(T value) => CreateFromBinaryValue(value, 8, true); + + /// + /// Creates a new instance from the specified YottaBits value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromYottaBits(T value) => CreateFromMetricValue(value, 8, false); + + /// + /// Creates a new instance from the specified YottaBytes value. + /// + /// The value from which to construct a new instance. + /// Returns a new instance from the specified value. + public static DataSize FromYottaBytes(T value) => CreateFromMetricValue(value, 8, true); + + /// + /// Obtains the size in bits of the specified IEC (binary) value. + /// + /// The value to convert. + /// The exponent applied to 1024 when calculating the divisor (Ki=1, Mi=2, …). + /// If true, is in bytes; otherwise it is in bits. + /// Returns a representing the size in bits. + private static DataSize CreateFromBinaryValue(T value, int power, bool isByteValue) + { + T divisor = T.CreateChecked(Math.Pow(1024, power)); + return new DataSize(value * (isByteValue ? divisor * T.CreateChecked(8) : divisor)); + } + + /// + /// Obtains the size in bits of the specified SI (metric) value. + /// + /// The value to convert. + /// The exponent applied to 1000 when calculating the divisor (k=1, M=2, …). + /// If true, is in bytes; otherwise it is in bits. + /// Returns a representing the size in bits. + private static DataSize CreateFromMetricValue(T value, int power, bool isByteValue) + { + T divisor = T.CreateChecked(Math.Pow(1000, power)); + return new DataSize(value * (isByteValue ? divisor * T.CreateChecked(8) : divisor)); + } +} diff --git a/OnixLabs.Units/DataSize.To.cs b/OnixLabs.Units/DataSize.To.cs new file mode 100644 index 0000000..0fb45b9 --- /dev/null +++ b/OnixLabs.Units/DataSize.To.cs @@ -0,0 +1,79 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Globalization; + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + public override string ToString() => ToString(BitsSpecifier); + + /// + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); + + /// + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: BitsSpecifier); + + T value = specifier switch + { + BitsSpecifier => Bits, + BytesSpecifier => Bytes, + KibiBitsSpecifier => KibiBits, + KibiBytesSpecifier => KibiBytes, + KiloBitsSpecifier => KiloBits, + KiloBytesSpecifier => KiloBytes, + MebiBitsSpecifier => MebiBits, + MebiBytesSpecifier => MebiBytes, + MegaBitsSpecifier => MegaBits, + MegaBytesSpecifier => MegaBytes, + GibiBitsSpecifier => GibiBits, + GibiBytesSpecifier => GibiBytes, + GigaBitsSpecifier => GigaBits, + GigaBytesSpecifier => GigaBytes, + TebiBitsSpecifier => TebiBits, + TebiBytesSpecifier => TebiBytes, + TeraBitsSpecifier => TeraBits, + TeraBytesSpecifier => TeraBytes, + PebiBitsSpecifier => PebiBits, + PebiBytesSpecifier => PebiBytes, + PetaBitsSpecifier => PetaBits, + PetaBytesSpecifier => PetaBytes, + ExbiBitsSpecifier => ExbiBits, + ExbiBytesSpecifier => ExbiBytes, + ExaBitsSpecifier => ExaBits, + ExaBytesSpecifier => ExaBytes, + ZebiBitsSpecifier => ZebiBits, + ZebiBytesSpecifier => ZebiBytes, + ZettaBitsSpecifier => ZettaBits, + ZettaBytesSpecifier => ZettaBytes, + YobiBitsSpecifier => YobiBits, + YobiBytesSpecifier => YobiBytes, + YottaBitsSpecifier => YottaBits, + YottaBytesSpecifier => YottaBytes, + _ => throw ArgumentException.InvalidFormat(format, + "b, B, Kib, KiB, Kb, KB, Mib, MiB, Mb, MB, Gib, GiB, " + + "Gb, GB, Tib, TiB, Tb, TB, Pib, PiB, Pb, PB, Eib, EiB, " + + "Eb, EB, Zib, ZiB, Zb, ZB, Yib, YiB, Yb, and YB") + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/DataSize.cs b/OnixLabs.Units/DataSize.cs new file mode 100644 index 0000000..6b28cc9 --- /dev/null +++ b/OnixLabs.Units/DataSize.cs @@ -0,0 +1,230 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Numerics; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of data size. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +#pragma warning disable CA2231 +public readonly partial struct DataSize : IUnit> where T : IFloatingPoint +#pragma warning restore CA2231 +{ + /// + /// Initializes a new instance of the struct. + /// + /// The data size unit in . + private DataSize(T value) => Bits = value; + + /// + /// Gets the data size in bits (b). + /// + public T Bits { get; } + + /// + /// Gets the data size in bytes (B). + /// + public T Bytes => Bits / T.CreateChecked(8); + + /// + /// Gets the data size in kibibits (Kib). + /// + public T KibiBits => GetBinaryValue(1, false); + + /// + /// Gets the data size in kibibytes (KiB). + /// + public T KibiBytes => GetBinaryValue(1, true); + + /// + /// Gets the data size in kilobits (Kb). + /// + public T KiloBits => GetMetricValue(1, false); + + /// + /// Gets the data size in kilobytes (KB). + /// + public T KiloBytes => GetMetricValue(1, true); + + /// + /// Gets the data size in mebibits (Mib). + /// + public T MebiBits => GetBinaryValue(2, false); + + /// + /// Gets the data size in mebibytes (MiB). + /// + public T MebiBytes => GetBinaryValue(2, true); + + /// + /// Gets the data size in megabits (Mb). + /// + public T MegaBits => GetMetricValue(2, false); + + /// + /// Gets the data size in megabytes (MB). + /// + public T MegaBytes => GetMetricValue(2, true); + + /// + /// Gets the data size in gibibits (Gib). + /// + public T GibiBits => GetBinaryValue(3, false); + + /// + /// Gets the data size in gibibytes (GiB). + /// + public T GibiBytes => GetBinaryValue(3, true); + + /// + /// Gets the data size in gigabits (Gb). + /// + public T GigaBits => GetMetricValue(3, false); + + /// + /// Gets the data size in gigabytes (GB). + /// + public T GigaBytes => GetMetricValue(3, true); + + /// + /// Gets the data size in tebibits (Tib). + /// + public T TebiBits => GetBinaryValue(4, false); + + /// + /// Gets the data size in tebibytes (TiB). + /// + public T TebiBytes => GetBinaryValue(4, true); + + /// + /// Gets the data size in terabits (Tb). + /// + public T TeraBits => GetMetricValue(4, false); + + /// + /// Gets the data size in terabytes (TB). + /// + public T TeraBytes => GetMetricValue(4, true); + + /// + /// Gets the data size in pebibits (Pib). + /// + public T PebiBits => GetBinaryValue(5, false); + + /// + /// Gets the data size in pebibytes (PiB). + /// + public T PebiBytes => GetBinaryValue(5, true); + + /// + /// Gets the data size in petabits (Pb). + /// + public T PetaBits => GetMetricValue(5, false); + + /// + /// Gets the data size in petabytes (PB). + /// + public T PetaBytes => GetMetricValue(5, true); + + /// + /// Gets the data size in exbibits (Eib). + /// + public T ExbiBits => GetBinaryValue(6, false); + + /// + /// Gets the data size in exbibytes (EiB). + /// + public T ExbiBytes => GetBinaryValue(6, true); + + /// + /// Gets the data size in exabits (Eb). + /// + public T ExaBits => GetMetricValue(6, false); + + /// + /// Gets the data size in exabytes (EB). + /// + public T ExaBytes => GetMetricValue(6, true); + + /// + /// Gets the data size in zebibits (Zib). + /// + public T ZebiBits => GetBinaryValue(7, false); + + /// + /// Gets the data size in zebibytes (ZiB). + /// + public T ZebiBytes => GetBinaryValue(7, true); + + /// + /// Gets the data size in zettabits (Zb). + /// + public T ZettaBits => GetMetricValue(7, false); + + /// + /// Gets the data size in zettabytes (ZB). + /// + public T ZettaBytes => GetMetricValue(7, true); + + /// + /// Gets the data size in yobibits (Yib). + /// + public T YobiBits => GetBinaryValue(8, false); + + /// + /// Gets the data size in yobibytes (YiB). + /// + public T YobiBytes => GetBinaryValue(8, true); + + /// + /// Gets the data size in yottabits. (Yb) + /// + public T YottaBits => GetMetricValue(8, false); + + /// + /// Gets the data size in yottabytes (YB). + /// + public T YottaBytes => GetMetricValue(8, true); + + /// + /// Obtains the size represented by Bits converted into a binary scaled unit, based + /// on the specified power of 1024 and whether the value is expressed in bits or bytes. + /// + /// The exponent applied to 1024 when calculating the divisor. + /// Determines whether the calculation should be computed in bites or bytes. + /// Returns the size converted to the requested unit. + private T GetBinaryValue(int power, bool isByteValue) + { + T divisor = T.CreateChecked(Math.Pow(1024, power)); + return Bits / (isByteValue ? divisor * T.CreateChecked(8) : divisor); + } + + /// + /// Obtains the size represented by Bits converted into a metric scaled unit, based + /// on the specified power of 1000 and whether the value is expressed in bits or bytes. + /// + /// The exponent applied to 1000 when calculating the divisor. + /// Determines whether the calculation should be computed in bites or bytes. + /// Returns the size converted to the requested unit. + private T GetMetricValue(int power, bool isByteValue) + { + T divisor = T.CreateChecked(Math.Pow(1000, power)); + return Bits / (isByteValue ? divisor * T.CreateChecked(8) : divisor); + } +} diff --git a/OnixLabs.Units/Distance.Arithmetic.cs b/OnixLabs.Units/Distance.Arithmetic.cs new file mode 100644 index 0000000..fa22384 --- /dev/null +++ b/OnixLabs.Units/Distance.Arithmetic.cs @@ -0,0 +1,30 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + public static Distance Add(Distance left, Distance right) => new(left.QuectoMeters + right.QuectoMeters); + + /// + public static Distance Subtract(Distance left, Distance right) => new(left.QuectoMeters - right.QuectoMeters); + + /// + public static Distance Multiply(Distance left, Distance right) => new(left.QuectoMeters * right.QuectoMeters); + + /// + public static Distance Divide(Distance left, Distance right) => new(left.QuectoMeters / right.QuectoMeters); +} diff --git a/OnixLabs.Units/Distance.Comparable.cs b/OnixLabs.Units/Distance.Comparable.cs new file mode 100644 index 0000000..8b1f033 --- /dev/null +++ b/OnixLabs.Units/Distance.Comparable.cs @@ -0,0 +1,29 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + public static int Compare(Distance left, Distance right) => left.QuectoMeters.CompareTo(right.QuectoMeters); + + /// + public int CompareTo(Distance other) => Compare(this, other); + + /// + public int CompareTo(object? obj) => this.CompareToObject(obj); +} diff --git a/OnixLabs.Units/Distance.Constants.cs b/OnixLabs.Units/Distance.Constants.cs new file mode 100644 index 0000000..d50654b --- /dev/null +++ b/OnixLabs.Units/Distance.Constants.cs @@ -0,0 +1,57 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + public static Distance Zero => new(T.Zero); + + private const string QuectoMetersSpecifier = "qm"; + private const string RontoMetersSpecifier = "rm"; + private const string YoctoMetersSpecifier = "ym"; + private const string ZeptoMetersSpecifier = "zm"; + private const string AttoMetersSpecifier = "am"; + private const string FemtoMetersSpecifier = "fm"; + private const string PicoMetersSpecifier = "pm"; + private const string NanoMetersSpecifier = "nm"; + private const string MicroMetersSpecifier = "um"; + private const string MilliMetersSpecifier = "mm"; + private const string CentiMetersSpecifier = "cm"; + private const string DeciMetersSpecifier = "dm"; + private const string MetersSpecifier = "m"; + private const string DecaMetersSpecifier = "dam"; + private const string HectoMetersSpecifier = "hm"; + private const string KiloMetersSpecifier = "km"; + private const string MegaMetersSpecifier = "Mm"; + private const string GigaMetersSpecifier = "Gm"; + private const string TeraMetersSpecifier = "Tm"; + private const string PetaMetersSpecifier = "Pm"; + private const string ExaMetersSpecifier = "Em"; + private const string ZettaMetersSpecifier = "Zm"; + private const string YottaMetersSpecifier = "Ym"; + private const string RonnaMetersSpecifier = "Rm"; + private const string QuettaMetersSpecifier = "Qm"; + private const string InchesSpecifier = "in"; + private const string FeetSpecifier = "ft"; + private const string YardsSpecifier = "yd"; + private const string MilesSpecifier = "mi"; + private const string NauticalMilesSpecifier = "nmi"; + private const string FermisSpecifier = "fmi"; + private const string AngstromsSpecifier = "a"; + private const string AstronomicalUnitsSpecifier = "au"; + private const string LightYearsSpecifier = "ly"; + private const string ParsecsSpecifier = "pc"; +} diff --git a/OnixLabs.Units/Distance.Equatable.cs b/OnixLabs.Units/Distance.Equatable.cs new file mode 100644 index 0000000..7f31278 --- /dev/null +++ b/OnixLabs.Units/Distance.Equatable.cs @@ -0,0 +1,33 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + public static bool Equals(Distance left, Distance right) => left.QuectoMeters == right.QuectoMeters; + + /// + public bool Equals(Distance other) => Equals(this, other); + + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Distance other && Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(QuectoMeters); +} diff --git a/OnixLabs.Units/Distance.Format.cs b/OnixLabs.Units/Distance.Format.cs new file mode 100644 index 0000000..c3c0864 --- /dev/null +++ b/OnixLabs.Units/Distance.Format.cs @@ -0,0 +1,29 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + public bool TryFormat( + Span destination, + out int charsWritten, + ReadOnlySpan format, + IFormatProvider? provider + ) => ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Distance.From.cs b/OnixLabs.Units/Distance.From.cs new file mode 100644 index 0000000..ee92490 --- /dev/null +++ b/OnixLabs.Units/Distance.From.cs @@ -0,0 +1,268 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + /// Creates a new instance from the specified Quectometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromQuectometers(T value) => new(value); + + /// + /// Creates a new instance from the specified Rontometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromRontometers(T value) => new(value.FromRontoUnits()); + + /// + /// Creates a new instance from the specified Yoctometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromYoctometers(T value) => new(value.FromYoctoUnits()); + + /// + /// Creates a new instance from the specified Zeptometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromZeptometers(T value) => new(value.FromZeptoUnits()); + + /// + /// Creates a new instance from the specified Attometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromAttometers(T value) => new(value.FromAttoUnits()); + + /// + /// Creates a new instance from the specified Femtometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromFemtometers(T value) => new(value.FromFemtoUnits()); + + /// + /// Creates a new instance from the specified Picometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromPicometers(T value) => new(value.FromPicoUnits()); + + /// + /// Creates a new instance from the specified Nanometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromNanometers(T value) => new(value.FromNanoUnits()); + + /// + /// Creates a new instance from the specified Micrometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromMicrometers(T value) => new(value.FromMicroUnits()); + + /// + /// Creates a new instance from the specified Millimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromMillimeters(T value) => new(value.FromMilliUnits()); + + /// + /// Creates a new instance from the specified Centimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromCentimeters(T value) => new(value.FromCentiUnits()); + + /// + /// Creates a new instance from the specified Decimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromDecimeters(T value) => new(value.FromDeciUnits()); + + /// + /// Creates a new instance from the specified Meters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromMeters(T value) => new(value.FromBaseUnits()); + + /// + /// Creates a new instance from the specified Decameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromDecameters(T value) => new(value.FromDecaUnits()); + + /// + /// Creates a new instance from the specified Hectometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromHectometers(T value) => new(value.FromHectoUnits()); + + /// + /// Creates a new instance from the specified Kilometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromKilometers(T value) => new(value.FromKiloUnits()); + + /// + /// Creates a new instance from the specified Quectometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromMegameters(T value) => new(value.FromMegaUnits()); + + /// + /// Creates a new instance from the specified Gigameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromGigameters(T value) => new(value.FromGigaUnits()); + + /// + /// Creates a new instance from the specified Terameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromTerameters(T value) => new(value.FromTeraUnits()); + + /// + /// Creates a new instance from the specified Petameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromPetameters(T value) => new(value.FromPetaUnits()); + + /// + /// Creates a new instance from the specified Exameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromExameters(T value) => new(value.FromExaUnits()); + + /// + /// Creates a new instance from the specified Zettameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromZettameters(T value) => new(value.FromZettaUnits()); + + /// + /// Creates a new instance from the specified Yottameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromYottameters(T value) => new(value.FromYottaUnits()); + + /// + /// Creates a new instance from the specified Ronnameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromRonnameters(T value) => new(value.FromRonnaUnits()); + + /// + /// Creates a new instance from the specified Quettameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromQuettameters(T value) => new(value.FromQuettaUnits()); + + /// + /// Creates a new instance from the specified Inches value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromInches(T value) => new(value * T.CreateChecked(2.54e28)); + + /// + /// Creates a new instance from the specified Feet value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromFeet(T value) => new(value * T.CreateChecked(3.048e29)); + + /// + /// Creates a new instance from the specified Yards value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromYards(T value) => new(value * T.CreateChecked(9.144e29)); + + /// + /// Creates a new instance from the specified Miles value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromMiles(T value) => new(value * T.CreateChecked(1.609344e33)); + + /// + /// Creates a new instance from the specified Nautical Miles value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromNauticalMiles(T value) => new(value * T.CreateChecked(1.852e33)); + + /// + /// Creates a new instance from the specified Fermis value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromFermis(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from the specified Angstroms value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromAngstroms(T value) => new(value * T.CreateChecked(1e20)); + + /// + /// Creates a new instance from the specified Astronomical Units value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromAstronomicalUnits(T value) => new(value * T.CreateChecked(1.495978707e41)); + + /// + /// Creates a new instance from the specified Light Years value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromLightYears(T value) => new(value * T.CreateChecked(9.4607304725808e45)); + + /// + /// Creates a new instance from the specified Parsecs value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Distance FromParsecs(T value) + { + T metersPerParsec = T.CreateChecked(1.495978707e11) * T.CreateChecked(648000) / T.Pi; + T qmPerParsec = metersPerParsec * T.CreateChecked(1e30); + return new Distance(value * qmPerParsec); + } +} diff --git a/OnixLabs.Units/Distance.To.cs b/OnixLabs.Units/Distance.To.cs new file mode 100644 index 0000000..3669e5c --- /dev/null +++ b/OnixLabs.Units/Distance.To.cs @@ -0,0 +1,80 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Globalization; + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + public override string ToString() => ToString(QuectoMetersSpecifier); + + /// + public string ToString(string? format, IFormatProvider? formatProvider) => ToString(format.AsSpan(), formatProvider); + + /// + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: QuectoMetersSpecifier); + + T value = specifier switch + { + QuectoMetersSpecifier => QuectoMeters, + RontoMetersSpecifier => RontoMeters, + YoctoMetersSpecifier => YoctoMeters, + ZeptoMetersSpecifier => ZeptoMeters, + AttoMetersSpecifier => AttoMeters, + FemtoMetersSpecifier => FemtoMeters, + PicoMetersSpecifier => PicoMeters, + NanoMetersSpecifier => NanoMeters, + MicroMetersSpecifier => MicroMeters, + MilliMetersSpecifier => MilliMeters, + CentiMetersSpecifier => CentiMeters, + DeciMetersSpecifier => DeciMeters, + MetersSpecifier => Meters, + DecaMetersSpecifier => DecaMeters, + HectoMetersSpecifier => HectoMeters, + KiloMetersSpecifier => KiloMeters, + MegaMetersSpecifier => MegaMeters, + GigaMetersSpecifier => GigaMeters, + TeraMetersSpecifier => TeraMeters, + PetaMetersSpecifier => PetaMeters, + ExaMetersSpecifier => ExaMeters, + ZettaMetersSpecifier => ZettaMeters, + YottaMetersSpecifier => YottaMeters, + RonnaMetersSpecifier => RonnaMeters, + QuettaMetersSpecifier => QuettaMeters, + InchesSpecifier => Inches, + FeetSpecifier => Feet, + YardsSpecifier => Yards, + MilesSpecifier => Miles, + NauticalMilesSpecifier => NauticalMiles, + FermisSpecifier => Fermis, + AngstromsSpecifier => Angstroms, + AstronomicalUnitsSpecifier => AstronomicalUnits, + LightYearsSpecifier => LightYears, + ParsecsSpecifier => Parsecs, + _ => throw ArgumentException.InvalidFormat(format, + "qm, rm, ym, zm, am, fm, pm, nm, um, mm, cm, dm, " + + "m, dam, hm, km, Mm, Gm, Tm, Pm, Em, Zm, Ym, Rm, " + + "Qm, in, ft, yd, mi, nmi, fmi, a, au, ly, and pc") + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Distance.cs b/OnixLabs.Units/Distance.cs new file mode 100644 index 0000000..66199f8 --- /dev/null +++ b/OnixLabs.Units/Distance.cs @@ -0,0 +1,215 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Numerics; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of distance. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Distance : IUnit> where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The distance unit in . + private Distance(T value) => QuectoMeters = value; + + /// + /// Gets the distance in Quectometers (qm). + /// + public T QuectoMeters { get; } + + /// + /// Gets the distance in Rontometers (rm). + /// + public T RontoMeters => QuectoMeters.ToRontoUnits(); + + /// + /// Gets the distance in Yoctometers (ym). + /// + public T YoctoMeters => QuectoMeters.ToYoctoUnits(); + + /// + /// Gets the distance in Zeptometers (zm). + /// + public T ZeptoMeters => QuectoMeters.ToZeptoUnits(); + + /// + /// Gets the distance in Attometers (am). + /// + public T AttoMeters => QuectoMeters.ToAttoUnits(); + + /// + /// Gets the distance in Femtometers (fm). + /// + public T FemtoMeters => QuectoMeters.ToFemtoUnits(); + + /// + /// Gets the distance in Picometers (pm). + /// + public T PicoMeters => QuectoMeters.ToPicoUnits(); + + /// + /// Gets the distance in Nanometers (nm). + /// + public T NanoMeters => QuectoMeters.ToNanoUnits(); + + /// + /// Gets the distance in Micrometers (um). + /// + public T MicroMeters => QuectoMeters.ToMicroUnits(); + + /// + /// Gets the distance in Millimeters (mm). + /// + public T MilliMeters => QuectoMeters.ToMilliUnits(); + + /// + /// Gets the distance in Centimeters (cm). + /// + public T CentiMeters => QuectoMeters.ToCentiUnits(); + + /// + /// Gets the distance in Decimeters (dm). + /// + public T DeciMeters => QuectoMeters.ToDeciUnits(); + + /// + /// Gets the distance in Meters (m). + /// + public T Meters => QuectoMeters.ToBaseUnits(); + + /// + /// Gets the distance in Decameters (dam). + /// + public T DecaMeters => QuectoMeters.ToDecaUnits(); + + /// + /// Gets the distance in Hectometers (hm). + /// + public T HectoMeters => QuectoMeters.ToHectoUnits(); + + /// + /// Gets the distance in Kilometers (km). + /// + public T KiloMeters => QuectoMeters.ToKiloUnits(); + + /// + /// Gets the distance in Megameters (Mm). + /// + public T MegaMeters => QuectoMeters.ToMegaUnits(); + + /// + /// Gets the distance in Gigameters (Gm). + /// + public T GigaMeters => QuectoMeters.ToGigaUnits(); + + /// + /// Gets the distance in Terameters (Tm). + /// + public T TeraMeters => QuectoMeters.ToTeraUnits(); + + /// + /// Gets the distance in Petameters (Pm). + /// + public T PetaMeters => QuectoMeters.ToPetaUnits(); + + /// + /// Gets the distance in Exameters (Em). + /// + public T ExaMeters => QuectoMeters.ToExaUnits(); + + /// + /// Gets the distance in Zettameters (Zm). + /// + public T ZettaMeters => QuectoMeters.ToZettaUnits(); + + /// + /// Gets the distance in Yottameters (Ym). + /// + public T YottaMeters => QuectoMeters.ToYottaUnits(); + + /// + /// Gets the distance in Ronnameters (Rm). + /// + public T RonnaMeters => QuectoMeters.ToRonnaUnits(); + + /// + /// Gets the distance in Quettameters (Qm). + /// + public T QuettaMeters => QuectoMeters.ToQuettaUnits(); + + /// + /// Gets the distance in Inches (in). + /// + public T Inches => QuectoMeters / T.CreateChecked(0.0254e30); + + /// + /// Gets the distance in Feet (ft). + /// + public T Feet => QuectoMeters / T.CreateChecked(0.3048e30); + + /// + /// Gets the distance in Yards (yd). + /// + public T Yards => QuectoMeters / T.CreateChecked(0.9144e30); + + // ReSharper disable once GrammarMistakeInComment + /// + /// Gets the distance in Miles (mi). + /// + public T Miles => QuectoMeters / T.CreateChecked(1609.344e30); + + /// + /// Gets the distance in Nautical Miles (nmi). + /// + public T NauticalMiles => QuectoMeters / T.CreateChecked(1852e30); + + /// + /// Gets the distance in Nautical Fermis (fmi). + /// + public T Fermis => QuectoMeters / T.CreateChecked(1e15); + + /// + /// Gets the distance in Nautical Angstroms (a). + /// + public T Angstroms => QuectoMeters / T.CreateChecked(1e20); + + /// + /// Gets the distance in Astronomical Units (au). + /// + public T AstronomicalUnits => QuectoMeters / T.CreateChecked(149_597_870_700L * 1e30); + + /// + /// Gets the distance in Light Years (ly). + /// + public T LightYears => QuectoMeters / T.CreateChecked(9_460_730_472_580_800L * 1e30); + + /// + /// Gets the distance in Parsecs (pc). + /// + public T Parsecs + { + get + { + T metersPerParsec = T.CreateChecked(149_597_870_700L) * T.CreateChecked(648000) / T.Pi; + T qmPerParsec = metersPerParsec * T.CreateChecked(1e30); + return QuectoMeters / qmPerParsec; + } + } +} diff --git a/OnixLabs.Units/Extensions.ArgumentException.cs b/OnixLabs.Units/Extensions.ArgumentException.cs new file mode 100644 index 0000000..892cdf2 --- /dev/null +++ b/OnixLabs.Units/Extensions.ArgumentException.cs @@ -0,0 +1,40 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace OnixLabs.Units; + +/// +/// Provides extension methods for instances. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class ArgumentExceptionExtensions +{ + /// + /// Provides extension methods for instances. + /// + extension(ArgumentException) + { + public static ArgumentException InvalidFormat( + ReadOnlySpan format, + string specifiers, + [CallerArgumentExpression(nameof(format))] + string? parameterName = null + ) => new($"Format '{format.ToString()}' is invalid. Valid format specifiers are: {specifiers}. " + + "Format specifiers may also be suffixed with a scale value.", parameterName); + } +} diff --git a/OnixLabs.Units/Extensions.IFloatingPoint.cs b/OnixLabs.Units/Extensions.IFloatingPoint.cs new file mode 100644 index 0000000..ff02b69 --- /dev/null +++ b/OnixLabs.Units/Extensions.IFloatingPoint.cs @@ -0,0 +1,78 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.ComponentModel; +using System.Numerics; + +namespace OnixLabs.Units; + +/// +/// Provides extension methods for instances. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IFloatingPointExtensions +{ + extension(T receiver) where T : IFloatingPoint + { + public T FromRontoUnits() => receiver * T.CreateChecked(1e3); + public T FromYoctoUnits() => receiver * T.CreateChecked(1e6); + public T FromZeptoUnits() => receiver * T.CreateChecked(1e9); + public T FromAttoUnits() => receiver * T.CreateChecked(1e12); + public T FromFemtoUnits() => receiver * T.CreateChecked(1e15); + public T FromPicoUnits() => receiver * T.CreateChecked(1e18); + public T FromNanoUnits() => receiver * T.CreateChecked(1e21); + public T FromMicroUnits() => receiver * T.CreateChecked(1e24); + public T FromMilliUnits() => receiver * T.CreateChecked(1e27); + public T FromCentiUnits() => receiver * T.CreateChecked(1e28); + public T FromDeciUnits() => receiver * T.CreateChecked(1e29); + public T FromBaseUnits() => receiver * T.CreateChecked(1e30); + public T FromDecaUnits() => receiver * T.CreateChecked(1e31); + public T FromHectoUnits() => receiver * T.CreateChecked(1e32); + public T FromKiloUnits() => receiver * T.CreateChecked(1e33); + public T FromMegaUnits() => receiver * T.CreateChecked(1e36); + public T FromGigaUnits() => receiver * T.CreateChecked(1e39); + public T FromTeraUnits() => receiver * T.CreateChecked(1e42); + public T FromPetaUnits() => receiver * T.CreateChecked(1e45); + public T FromExaUnits() => receiver * T.CreateChecked(1e48); + public T FromZettaUnits() => receiver * T.CreateChecked(1e51); + public T FromYottaUnits() => receiver * T.CreateChecked(1e54); + public T FromRonnaUnits() => receiver * T.CreateChecked(1e57); + public T FromQuettaUnits() => receiver * T.CreateChecked(1e60); + + public T ToRontoUnits() => receiver / T.CreateChecked(1e3); + public T ToYoctoUnits() => receiver / T.CreateChecked(1e6); + public T ToZeptoUnits() => receiver / T.CreateChecked(1e9); + public T ToAttoUnits() => receiver / T.CreateChecked(1e12); + public T ToFemtoUnits() => receiver / T.CreateChecked(1e15); + public T ToPicoUnits() => receiver / T.CreateChecked(1e18); + public T ToNanoUnits() => receiver / T.CreateChecked(1e21); + public T ToMicroUnits() => receiver / T.CreateChecked(1e24); + public T ToMilliUnits() => receiver / T.CreateChecked(1e27); + public T ToCentiUnits() => receiver / T.CreateChecked(1e28); + public T ToDeciUnits() => receiver / T.CreateChecked(1e29); + public T ToBaseUnits() => receiver / T.CreateChecked(1e30); + public T ToDecaUnits() => receiver / T.CreateChecked(1e31); + public T ToHectoUnits() => receiver / T.CreateChecked(1e32); + public T ToKiloUnits() => receiver / T.CreateChecked(1e33); + public T ToMegaUnits() => receiver / T.CreateChecked(1e36); + public T ToGigaUnits() => receiver / T.CreateChecked(1e39); + public T ToTeraUnits() => receiver / T.CreateChecked(1e42); + public T ToPetaUnits() => receiver / T.CreateChecked(1e45); + public T ToExaUnits() => receiver / T.CreateChecked(1e48); + public T ToZettaUnits() => receiver / T.CreateChecked(1e51); + public T ToYottaUnits() => receiver / T.CreateChecked(1e54); + public T ToRonnaUnits() => receiver / T.CreateChecked(1e57); + public T ToQuettaUnits() => receiver / T.CreateChecked(1e60); + } +} diff --git a/OnixLabs.Units/Extensions.IUnit.cs b/OnixLabs.Units/Extensions.IUnit.cs new file mode 100644 index 0000000..9608d90 --- /dev/null +++ b/OnixLabs.Units/Extensions.IUnit.cs @@ -0,0 +1,147 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.ComponentModel; + +namespace OnixLabs.Units; + +/// +/// Provides extension methods for instances. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public static class IUnitExtensions +{ + /// + /// Provides extension methods for instances. + /// + /// The underlying type of the instance. + extension(T) where T : struct, IUnit + { + /// + /// Computes the sum of the specified values. + /// + /// The left-hand value to add to. + /// The right-hand value to add. + /// Returns the sum of the specified values. + public static T operator +(T left, T right) => T.Add(left, right); + + /// + /// Computes the difference of the specified values. + /// + /// The left-hand value to subtract from. + /// The right-hand value to subtract. + /// Returns the difference of the specified values. + public static T operator -(T left, T right) => T.Subtract(left, right); + + /// + /// Computes the product of the specified values. + /// + /// The left-hand value to multiply. + /// The right-hand value to multiply by. + /// Returns the product of the specified values. + public static T operator *(T left, T right) => T.Multiply(left, right); + + /// + /// Computes the quotient of the specified values. + /// + /// The left-hand value to divide. + /// The right-hand value to divide by. + /// Returns the quotient of the specified values. + public static T operator /(T left, T right) => T.Divide(left, right); + + /// + /// Determines whether the specified left-hand and right-hand values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the specified left-hand and right-hand values are equal; otherwise . + public static bool operator ==(T left, T right) => T.Equals(left, right); + + /// + /// Determines whether the specified left-hand and right-hand values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the specified left-hand and right-hand values are not equal; otherwise . + public static bool operator !=(T left, T right) => !T.Equals(left, right); + + /// + /// Determines whether the specified left-hand value is greater than the specified right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the specified left-hand value is greater than the specified right-hand value; otherwise . + public static bool operator >(T left, T right) => T.Compare(left, right) is 1; + + /// + /// Determines whether the specified left-hand value is greater than or equal to the specified right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the specified left-hand value is greater than or equal to the specified right-hand value; otherwise . + public static bool operator >=(T left, T right) => T.Compare(left, right) is 1 or 0; + + /// + /// Determines whether the specified left-hand value is less than the specified right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the specified left-hand value is less than the specified right-hand value; otherwise . + public static bool operator <(T left, T right) => T.Compare(left, right) is -1; + + /// + /// Determines whether the specified left-hand value is less than or equal to the specified right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the specified left-hand value is less than or equal to the specified right-hand value; otherwise . + public static bool operator <=(T left, T right) => T.Compare(left, right) is -1 or 0; + } + + /// + /// Provides extension methods for instances. + /// + /// The left-hand value of the arithmetic operation. + /// The underlying type of the instance. + extension(T left) where T : struct, IUnit + { + /// + /// Computes the sum of the current value and the specified other value. + /// + /// The value to add to the current value. + /// Returns the sum of the current value and the specified other value. + public T Add(T right) => T.Add(left, right); + + /// + /// Computes the difference of the current value and the specified other value. + /// + /// The value to subtract from the current value. + /// Returns the difference of the current value and the specified other value. + public T Subtract(T right) => T.Subtract(left, right); + + /// + /// Computes the product of the current value and the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value and the specified other value. + public T Multiply(T right) => T.Multiply(left, right); + + /// + /// Computes the quotient of the current value and the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value and the specified other value. + public T Divide(T right) => T.Divide(left, right); + } +} diff --git a/OnixLabs.Units/Extensions.ReadOnlySpan.cs b/OnixLabs.Units/Extensions.ReadOnlySpan.cs new file mode 100644 index 0000000..387a4f9 --- /dev/null +++ b/OnixLabs.Units/Extensions.ReadOnlySpan.cs @@ -0,0 +1,65 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.ComponentModel; +using System.Globalization; + +namespace OnixLabs.Units; + +/// +/// Provides extension methods for instances. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class ReadOnlySpanExtensions +{ + /// + /// Provides extension methods for instances. + /// + /// The current instance. + extension(ReadOnlySpan receiver) + { + public (string specifier, int scale) GetSpecifierAndScale(string defaultSpecifier) + { + const char plus = '+'; + const char minus = '-'; + + int defaultScale = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalDigits; + + if (receiver.IsEmpty) + return (defaultSpecifier, defaultScale); + + int index = 0; + + while (index < receiver.Length && char.IsLetter(receiver[index])) + index++; + + if (index == 0) + throw new FormatException("Format must start with a letter specifier."); + + string specifier = new(receiver[..index]); + ReadOnlySpan scaleCharacters = receiver[index..]; + + if (scaleCharacters.IsEmpty) + return (specifier, defaultScale); + + if (scaleCharacters[0] is plus or minus) + throw new FormatException($"Scale must not begin with a leading '{plus}' or '{minus}' sign."); + + return int.TryParse(scaleCharacters, NumberStyles.Integer, CultureInfo.InvariantCulture, out int scale) + ? (specifier, scale) + : throw new FormatException("Scale must contain only decimal digits."); + } + } +} diff --git a/OnixLabs.Units/IUnit.cs b/OnixLabs.Units/IUnit.cs new file mode 100644 index 0000000..1938f6f --- /dev/null +++ b/OnixLabs.Units/IUnit.cs @@ -0,0 +1,91 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; + +namespace OnixLabs.Units; + +/// +/// Defines a unit of measurement. +/// +/// The underlying type of the unit of measurement. +public interface IUnit : IEquatable, IComparable, IComparable, ISpanFormattable where TSelf : struct +{ + /// + /// Gets a zero 0 value. + /// + static abstract TSelf Zero { get; } + + /// + /// Computes the sum of the specified values. + /// + /// The left-hand value to add to. + /// The right-hand value to add. + /// Returns the sum of the specified values. + static abstract TSelf Add(TSelf left, TSelf right); + + /// + /// Computes the difference between the specified values. + /// + /// The left-hand value to subtract from. + /// The right-hand value to subtract. + /// Returns the difference between the specified values. + static abstract TSelf Subtract(TSelf left, TSelf right); + + /// + /// Computes the product of the specified values. + /// + /// The left-hand value to multiply. + /// The right-hand value multiply by. + /// Returns the product of the specified values. + static abstract TSelf Multiply(TSelf left, TSelf right); + + /// + /// Computes the quotient of the specified values. + /// + /// The left-hand value to divide. + /// The right-hand value to divide by. + /// Returns the quotient of the specified values. + static abstract TSelf Divide(TSelf left, TSelf right); + + /// + /// Determines whether the specified left-hand and right-hand values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the specified left-hand and right-hand values are equal; otherwise . + static abstract bool Equals(TSelf left, TSelf right); + + /// + /// Compares the specified left-hand and right-hand values and returns an integer + /// that indicates whether the left-hand value is less than, equal to, or greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns an integer value that indicates the relative order of the left-hand and right-hand values being compared. + /// The return value is less than zero if is less than , + /// zero if is equal to , + /// or greater than zero if is greater than . + /// + static abstract int Compare(TSelf left, TSelf right); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null); +} diff --git a/OnixLabs.Units/OnixLabs.Units.csproj b/OnixLabs.Units/OnixLabs.Units.csproj new file mode 100644 index 0000000..e057fd5 --- /dev/null +++ b/OnixLabs.Units/OnixLabs.Units.csproj @@ -0,0 +1,10 @@ + + + OnixLabs.Units + ONIXLabs Units API for .NET + + + + + + diff --git a/OnixLabs.Units/Temperature.Arithmetic.cs b/OnixLabs.Units/Temperature.Arithmetic.cs new file mode 100644 index 0000000..5cb4991 --- /dev/null +++ b/OnixLabs.Units/Temperature.Arithmetic.cs @@ -0,0 +1,30 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + public static Temperature Add(Temperature left, Temperature right) => new(left.Kelvin + right.Kelvin); + + /// + public static Temperature Subtract(Temperature left, Temperature right) => new(left.Kelvin - right.Kelvin); + + /// + public static Temperature Multiply(Temperature left, Temperature right) => new(left.Kelvin * right.Kelvin); + + /// + public static Temperature Divide(Temperature left, Temperature right) => new(left.Kelvin / right.Kelvin); +} diff --git a/OnixLabs.Units/Temperature.Comparable.cs b/OnixLabs.Units/Temperature.Comparable.cs new file mode 100644 index 0000000..3012f7a --- /dev/null +++ b/OnixLabs.Units/Temperature.Comparable.cs @@ -0,0 +1,29 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + public static int Compare(Temperature left, Temperature right) => left.Kelvin.CompareTo(right.Kelvin); + + /// + public int CompareTo(Temperature other) => Compare(this, other); + + /// + public int CompareTo(object? obj) => this.CompareToObject(obj); +} diff --git a/OnixLabs.Units/Temperature.Constants.cs b/OnixLabs.Units/Temperature.Constants.cs new file mode 100644 index 0000000..408c663 --- /dev/null +++ b/OnixLabs.Units/Temperature.Constants.cs @@ -0,0 +1,45 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + public static Temperature Zero => new(T.Zero); + + private const string CelsiusSpecifier = "C"; + private const string CelsiusSymbol = "°C"; + + private const string DelisleSpecifier = "DE"; + private const string DelisleSymbol = "°De"; + + private const string FahrenheitSpecifier = "F"; + private const string FahrenheitSymbol = "°F"; + + private const string KelvinSpecifier = "K"; + private const string KelvinSymbol = "K"; + + private const string NewtonSpecifier = "N"; + private const string NewtonSymbol = "°N"; + + private const string RankineSpecifier = "R"; + private const string RankineSymbol = "°R"; + + private const string ReaumurSpecifier = "RE"; + private const string ReaumurSymbol = "°Ré"; + + private const string RomerSpecifier = "RO"; + private const string RomerSymbol = "°Rø"; +} diff --git a/OnixLabs.Units/Temperature.Equatable.cs b/OnixLabs.Units/Temperature.Equatable.cs new file mode 100644 index 0000000..5a26e87 --- /dev/null +++ b/OnixLabs.Units/Temperature.Equatable.cs @@ -0,0 +1,33 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + public static bool Equals(Temperature left, Temperature right) => left.Kelvin == right.Kelvin; + + /// + public bool Equals(Temperature other) => Equals(this, other); + + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Temperature other && Equals(other); + + /// + public override int GetHashCode() => HashCode.Combine(Kelvin); +} diff --git a/OnixLabs.Units/Temperature.Format.cs b/OnixLabs.Units/Temperature.Format.cs new file mode 100644 index 0000000..5285178 --- /dev/null +++ b/OnixLabs.Units/Temperature.Format.cs @@ -0,0 +1,29 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + public bool TryFormat( + Span destination, + out int charsWritten, + ReadOnlySpan format, + IFormatProvider? provider + ) => ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Temperature.From.cs b/OnixLabs.Units/Temperature.From.cs new file mode 100644 index 0000000..0369aba --- /dev/null +++ b/OnixLabs.Units/Temperature.From.cs @@ -0,0 +1,74 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + /// Creates a new instance from the specified Celsius value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Temperature FromCelsius(T value) => new(value + T.CreateChecked(273.15)); + + /// + /// Creates a new instance from the specified Delisle value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Temperature FromDelisle(T value) => new(T.CreateChecked(373.15) - value * (T.CreateChecked(2.00) / T.CreateChecked(3.00))); + + /// + /// Creates a new instance from the specified Fahrenheit value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Temperature FromFahrenheit(T value) => new((value + T.CreateChecked(459.67)) / T.CreateChecked(1.80)); + + /// + /// Creates a new instance from the specified Kelvin value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Temperature FromKelvin(T value) => new(value); + + /// + /// Creates a new instance from the specified Newton value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Temperature FromNewton(T value) => new(value * (T.CreateChecked(100.00) / T.CreateChecked(33.00)) + T.CreateChecked(273.15)); + + /// + /// Creates a new instance from the specified Rankine value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Temperature FromRankine(T value) => new(value / T.CreateChecked(1.8)); + + /// + /// Creates a new instance from the specified Réaumur value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Temperature FromReaumur(T value) => new(value * T.CreateChecked(1.25) + T.CreateChecked(273.15)); + + /// + /// Creates a new instance from the specified Rømer value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Temperature FromRomer(T value) => new((value - T.CreateChecked(7.5)) * T.CreateChecked(40.0 / 21.0) + T.CreateChecked(273.15)); +} diff --git a/OnixLabs.Units/Temperature.To.cs b/OnixLabs.Units/Temperature.To.cs new file mode 100644 index 0000000..b2ac345 --- /dev/null +++ b/OnixLabs.Units/Temperature.To.cs @@ -0,0 +1,50 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Globalization; + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + public override string ToString() => ToString(KelvinSpecifier); + + /// + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); + + /// + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: KelvinSpecifier); + + (T value, string symbol) = specifier.ToUpperInvariant() switch + { + CelsiusSpecifier => (Celsius, CelsiusSymbol), + DelisleSpecifier => (Delisle, DelisleSymbol), + FahrenheitSpecifier => (Fahrenheit, FahrenheitSymbol), + KelvinSpecifier => (Kelvin, KelvinSymbol), + NewtonSpecifier => (Newton, NewtonSymbol), + RankineSpecifier => (Rankine, RankineSymbol), + ReaumurSpecifier => (Reaumur, ReaumurSymbol), + RomerSpecifier => (Romer, RomerSymbol), + _ => throw ArgumentException.InvalidFormat(format, "C, De, F, K, N, R, Re, and Ro") + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {symbol}"; + } +} diff --git a/OnixLabs.Units/Temperature.cs b/OnixLabs.Units/Temperature.cs new file mode 100644 index 0000000..9c6b06e --- /dev/null +++ b/OnixLabs.Units/Temperature.cs @@ -0,0 +1,73 @@ +// Copyright 2020-2025 ONIXLabs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Numerics; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of temperature. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +#pragma warning disable CA2231 +public readonly partial struct Temperature : IUnit> where T : IFloatingPoint +#pragma warning restore CA2231 +{ + /// + /// Initializes a new instance of the struct. + /// + /// The temperature unit in . + private Temperature(T value) => Kelvin = value; + + /// + /// Gets the temperature in Kelvin (K). + /// + public T Kelvin { get; } + + /// + /// Gets the temperature in Celsius (C). + /// + public T Celsius => Kelvin - T.CreateChecked(273.15); + + /// + /// Gets the temperature in Delisle (DE). + /// + public T Delisle => (T.CreateChecked(373.15) - Kelvin) * T.CreateChecked(1.50); + + /// + /// Gets the temperature in Fahrenheit (F). + /// + public T Fahrenheit => Kelvin * T.CreateChecked(1.80) - T.CreateChecked(459.67); + + /// + /// Gets the temperature in Newton (N). + /// + public T Newton => (Kelvin - T.CreateChecked(273.15)) * T.CreateChecked(33.00) / T.CreateChecked(100.00); + + /// + /// Gets the temperature in Rankine (R). + /// + public T Rankine => Kelvin * T.CreateChecked(1.8); + + /// + /// Gets the temperature in Réaumur (RE). + /// + public T Reaumur => (Kelvin - T.CreateChecked(273.15)) * T.CreateChecked(0.80); + + /// + /// Gets the temperature in Rømer (RO). + /// + public T Romer => (Kelvin - T.CreateChecked(273.15)) * T.CreateChecked(21.0 / 40.0) + T.CreateChecked(7.5); +} diff --git a/onixlabs-dotnet.sln b/onixlabs-dotnet.sln index 9c03580..02a63ea 100644 --- a/onixlabs-dotnet.sln +++ b/onixlabs-dotnet.sln @@ -40,6 +40,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnixLabs.DependencyInjectio EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnixLabs.DependencyInjection.UnitTests.Data", "OnixLabs.DependencyInjection.UnitTests.Data\OnixLabs.DependencyInjection.UnitTests.Data.csproj", "{F1841D46-F428-4A54-BA74-6BD2D38812D9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Units", "Units", "{AB736309-A490-44D9-A056-266C651FFB9B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnixLabs.Units", "OnixLabs.Units\OnixLabs.Units.csproj", "{6734ACA0-D81A-4509-9C53-D085A880BE63}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnixLabs.Units.UnitTests", "OnixLabs.Units.UnitTests\OnixLabs.Units.UnitTests.csproj", "{00D48F32-9B67-4628-828D-3496D8EA3BED}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +66,8 @@ Global {DD205971-F1AB-4D9C-A83F-17BC70F4529E} = {B994A750-D259-4CEB-B913-BAFF4B57EC51} {FCC4E51B-F3D2-402F-A530-7C8FD6693952} = {B994A750-D259-4CEB-B913-BAFF4B57EC51} {F1841D46-F428-4A54-BA74-6BD2D38812D9} = {B994A750-D259-4CEB-B913-BAFF4B57EC51} + {6734ACA0-D81A-4509-9C53-D085A880BE63} = {AB736309-A490-44D9-A056-266C651FFB9B} + {00D48F32-9B67-4628-828D-3496D8EA3BED} = {AB736309-A490-44D9-A056-266C651FFB9B} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {1EDC1164-0205-433D-A356-00DAD4686264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -122,5 +130,13 @@ Global {F1841D46-F428-4A54-BA74-6BD2D38812D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {F1841D46-F428-4A54-BA74-6BD2D38812D9}.Release|Any CPU.ActiveCfg = Release|Any CPU {F1841D46-F428-4A54-BA74-6BD2D38812D9}.Release|Any CPU.Build.0 = Release|Any CPU + {6734ACA0-D81A-4509-9C53-D085A880BE63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6734ACA0-D81A-4509-9C53-D085A880BE63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6734ACA0-D81A-4509-9C53-D085A880BE63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6734ACA0-D81A-4509-9C53-D085A880BE63}.Release|Any CPU.Build.0 = Release|Any CPU + {00D48F32-9B67-4628-828D-3496D8EA3BED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00D48F32-9B67-4628-828D-3496D8EA3BED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00D48F32-9B67-4628-828D-3496D8EA3BED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00D48F32-9B67-4628-828D-3496D8EA3BED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/onixlabs-dotnet.sln.DotSettings b/onixlabs-dotnet.sln.DotSettings index be92f32..58690fe 100644 --- a/onixlabs-dotnet.sln.DotSettings +++ b/onixlabs-dotnet.sln.DotSettings @@ -13,15 +13,19 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. True + True True + True True True True + True True True True True True + True True True True @@ -49,27 +53,38 @@ limitations under the License. True True True + True + True + True True + True + True + True True True True True True + True True True True True True + True True True True True + True True True True True True + True True True True - True \ No newline at end of file + True + True \ No newline at end of file