From 6c1fcb72d28c10d94900580088c0975a904d3375 Mon Sep 17 00:00:00 2001 From: Matthew Layton Date: Sun, 31 Aug 2025 11:50:10 +0100 Subject: [PATCH 01/23] Created Units project, implemented Temperature unit, and added unit tests. --- .../OnixLabs.Playground.csproj | 1 + .../OnixLabs.Units.UnitTests.csproj | 26 ++ OnixLabs.Units.UnitTests/TemperatureTests.cs | 433 ++++++++++++++++++ OnixLabs.Units/Extensions.ReadOnlySpan.cs | 53 +++ OnixLabs.Units/OnixLabs.Units.csproj | 10 + .../Temperature.Arithmetic.Addition.cs | 41 ++ .../Temperature.Arithmetic.Division.cs | 41 ++ .../Temperature.Arithmetic.Multiplication.cs | 41 ++ .../Temperature.Arithmetic.Subtraction.cs | 41 ++ OnixLabs.Units/Temperature.Comparable.cs | 80 ++++ OnixLabs.Units/Temperature.Constants.cs | 44 ++ OnixLabs.Units/Temperature.Equatable.cs | 64 +++ OnixLabs.Units/Temperature.Format.cs | 33 ++ OnixLabs.Units/Temperature.From.cs | 67 +++ OnixLabs.Units/Temperature.To.cs | 62 +++ OnixLabs.Units/Temperature.cs | 70 +++ onixlabs-dotnet.sln | 16 + 17 files changed, 1123 insertions(+) create mode 100644 OnixLabs.Units.UnitTests/OnixLabs.Units.UnitTests.csproj create mode 100644 OnixLabs.Units.UnitTests/TemperatureTests.cs create mode 100644 OnixLabs.Units/Extensions.ReadOnlySpan.cs create mode 100644 OnixLabs.Units/OnixLabs.Units.csproj create mode 100644 OnixLabs.Units/Temperature.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Temperature.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Temperature.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Temperature.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Temperature.Comparable.cs create mode 100644 OnixLabs.Units/Temperature.Constants.cs create mode 100644 OnixLabs.Units/Temperature.Equatable.cs create mode 100644 OnixLabs.Units/Temperature.Format.cs create mode 100644 OnixLabs.Units/Temperature.From.cs create mode 100644 OnixLabs.Units/Temperature.To.cs create mode 100644 OnixLabs.Units/Temperature.cs 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/OnixLabs.Units.UnitTests.csproj b/OnixLabs.Units.UnitTests/OnixLabs.Units.UnitTests.csproj new file mode 100644 index 0000000..a79746a --- /dev/null +++ b/OnixLabs.Units.UnitTests/OnixLabs.Units.UnitTests.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/OnixLabs.Units.UnitTests/TemperatureTests.cs b/OnixLabs.Units.UnitTests/TemperatureTests.cs new file mode 100644 index 0000000..d2d4cfb --- /dev/null +++ b/OnixLabs.Units.UnitTests/TemperatureTests.cs @@ -0,0 +1,433 @@ +// 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); + } + + [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)] + [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] + [InlineData(100.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] + [InlineData(-40.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] + [InlineData(20.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + public void TemperatureFromCelsiusShouldProduceExpectedResult( + double celsius, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur) + { + // 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); + } + + [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)] + [InlineData(150.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] + [InlineData(0.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] + [InlineData(210.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] + [InlineData(120.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + public void TemperatureFromDelisleShouldProduceExpectedResult( + double delisle, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur) + { + // 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); + } + + [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)] + [InlineData(32.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] + [InlineData(212.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] + [InlineData(-40.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] + [InlineData(68.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + public void TemperatureFromFahrenheitShouldProduceExpectedResult( + double fahrenheit, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur) + { + 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); + } + + [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)] + [InlineData(273.15, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] + [InlineData(373.15, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] + [InlineData(233.15, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] + [InlineData(293.15, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + public void TemperatureFromKelvinShouldProduceExpectedResult( + double kelvin, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur) + { + 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); + } + + [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)] + [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] + [InlineData(33.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] + [InlineData(-13.2, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] + [InlineData(6.6, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + public void TemperatureFromNewtonShouldProduceExpectedResult( + double newton, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur) + { + 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); + } + + [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)] + [InlineData(491.67, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] + [InlineData(671.67, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] + [InlineData(419.67, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] + [InlineData(527.67, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + public void TemperatureFromRankineShouldProduceExpectedResult( + double rankine, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur) + { + 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); + } + + [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)] + [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] + [InlineData(80.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] + [InlineData(-32.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] + [InlineData(16.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + public void TemperatureFromReaumurShouldProduceExpectedResult( + double reaumur, + double expectedCelsius, + double expectedKelvin, + double expectedFahrenheit, + double expectedDelisle, + double expectedNewton, + double expectedRankine, + double expectedReaumur) + { + 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); + } + + [Fact(DisplayName = "Temperature.Add should produce the expected result")] + public void TemperatureAddShouldProduceExpectedValue() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(100.0); + Temperature temperature2 = Temperature.FromKelvin(50.0); + + // When + Temperature result = temperature1.Add(temperature2); + + // Then + Assert.Equal(150.0, result.Kelvin, Tolerance); + } + + [Fact(DisplayName = "Temperature.Subtract should produce the expected result")] + public void TemperatureSubtractShouldProduceExpectedValue() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(100.0); + Temperature temperature2 = Temperature.FromKelvin(40.0); + + // When + Temperature result = temperature1.Subtract(temperature2); + + // Then + Assert.Equal(60.0, result.Kelvin, Tolerance); + } + + [Fact(DisplayName = "Temperature.Multiply should produce the expected result")] + public void TemperatureMultiplyShouldProduceExpectedValue() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(10.0); + Temperature temperature2 = Temperature.FromKelvin(3.0); + + // When + Temperature result = temperature1.Multiply(temperature2); + + // Then + Assert.Equal(30.0, result.Kelvin, Tolerance); + } + + [Fact(DisplayName = "Temperature.Divide should produce the expected result")] + public void TemperatureDivideShouldProduceExpectedValue() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(100.0); + Temperature temperature2 = Temperature.FromKelvin(20.0); + + // When + Temperature result = temperature1.Divide(temperature2); + + // 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 temperature1 = Temperature.FromKelvin(123); + Temperature temperature2 = Temperature.FromKelvin(123); + + // When / Then + Assert.Equal(0, Temperature.Compare(temperature1, temperature2)); + Assert.Equal(0, temperature1.CompareTo(temperature2)); + Assert.Equal(0, temperature1.CompareTo((object)temperature2)); + Assert.False(temperature1 > temperature2); + Assert.True(temperature1 >= temperature2); + Assert.False(temperature1 < temperature2); + Assert.True(temperature1 <= temperature2); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left greater than right)")] + public void TemperatureComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(456); + Temperature temperature2 = Temperature.FromKelvin(123); + + // When / Then + Assert.Equal(1, Temperature.Compare(temperature1, temperature2)); + Assert.Equal(1, temperature1.CompareTo(temperature2)); + Assert.Equal(1, temperature1.CompareTo((object)temperature2)); + Assert.True(temperature1 > temperature2); + Assert.True(temperature1 >= temperature2); + Assert.False(temperature1 < temperature2); + Assert.False(temperature1 <= temperature2); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left greater than or equal to right)")] + public void TemperatureComparisonShouldProduceExpectedLeftGreaterThanOrEqualToRight() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(456); + Temperature temperature2 = Temperature.FromKelvin(123); + + // When / Then + Assert.Equal(1, Temperature.Compare(temperature1, temperature2)); + Assert.Equal(1, temperature1.CompareTo(temperature2)); + Assert.Equal(1, temperature1.CompareTo((object)temperature2)); + Assert.True(temperature1 > temperature2); + Assert.True(temperature1 >= temperature2); + Assert.False(temperature1 < temperature2); + Assert.False(temperature1 <= temperature2); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left less than right)")] + public void TemperatureComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(123); + Temperature temperature2 = Temperature.FromKelvin(456); + + // When / Then + Assert.Equal(-1, Temperature.Compare(temperature1, temperature2)); + Assert.Equal(-1, temperature1.CompareTo(temperature2)); + Assert.Equal(-1, temperature1.CompareTo((object)temperature2)); + Assert.False(temperature1 > temperature2); + Assert.False(temperature1 >= temperature2); + Assert.True(temperature1 < temperature2); + Assert.True(temperature1 <= temperature2); + } + + [Fact(DisplayName = "Temperature comparison should produce the expected result (left less than or equal to right)")] + public void TemperatureComparisonShouldProduceExpectedLeftLessThanOrEqualToRight() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(123); + Temperature temperature2 = Temperature.FromKelvin(456); + + // When / Then + Assert.Equal(-1, Temperature.Compare(temperature1, temperature2)); + Assert.Equal(-1, temperature1.CompareTo(temperature2)); + Assert.Equal(-1, temperature1.CompareTo((object)temperature2)); + Assert.False(temperature1 > temperature2); + Assert.False(temperature1 >= temperature2); + Assert.True(temperature1 < temperature2); + Assert.True(temperature1 <= temperature2); + } + + [Fact(DisplayName = "Temperature equality should produce the expected result (left equal to right)")] + public void TemperatureEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(123); + Temperature temperature2 = Temperature.FromKelvin(123); + + // When / Then + Assert.True(Temperature.Equals(temperature1, temperature2)); + Assert.True(temperature1.Equals(temperature2)); + Assert.True(temperature1.Equals((object)temperature2)); + Assert.True(temperature1 == temperature2); + Assert.False(temperature1 != temperature2); + } + + [Fact(DisplayName = "Temperature equality should produce the expected result (left not equal to right)")] + public void TemperatureEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Temperature temperature1 = Temperature.FromKelvin(123); + Temperature temperature2 = Temperature.FromKelvin(456); + + // When / Then + Assert.False(Temperature.Equals(temperature1, temperature2)); + Assert.False(temperature1.Equals(temperature2)); + Assert.False(temperature1.Equals((object)temperature2)); + Assert.False(temperature1 == temperature2); + Assert.True(temperature1 != temperature2); + } + + [Fact(DisplayName = "Temperature.ToString should produce the expected result")] + public void TemperatureToStringShouldProduceExpectedResult() + { + // Given / When + Temperature temp = Temperature.FromCelsius(100.0); + + // Then + Assert.Equal("373.150 K", $"{temp:K}"); + Assert.Equal("100.000 °C", $"{temp:C}"); + Assert.Equal("0.000 °De", $"{temp:DE}"); + Assert.Equal("212.000 °F", $"{temp:F}"); + Assert.Equal("33.000 °N", $"{temp:N}"); + Assert.Equal("80.000 °Ré", $"{temp:RE}"); + Assert.Equal("671.670 °R", $"{temp:R}"); + } + + [Fact(DisplayName = "Temperature.ToString should honor custom culture separators")] + public void TemperatureToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Temperature temp = Temperature.FromKelvin(1234.56); + + // When + string formatted = temp.ToString("K2", customCulture); + + // Then + Assert.Equal("1.234,56 K", formatted); + } +} diff --git a/OnixLabs.Units/Extensions.ReadOnlySpan.cs b/OnixLabs.Units/Extensions.ReadOnlySpan.cs new file mode 100644 index 0000000..b387039 --- /dev/null +++ b/OnixLabs.Units/Extensions.ReadOnlySpan.cs @@ -0,0 +1,53 @@ +// 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; + +internal static class ReadOnlySpanExtensions +{ + public static (string specifier, int scale) GetSpecifierAndScale(this ReadOnlySpan format, string defaultSpecifier) + { + const char plus = '+'; + const char minus = '-'; + + int defaultScale = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalDigits; + + if (format.IsEmpty) + return (defaultSpecifier, defaultScale); + + int index = 0; + + while (index < format.Length && char.IsLetter(format[index])) + index++; + + if (index == 0) + throw new FormatException("Format must start with a letter specifier."); + + string specifier = new(format[..index]); + ReadOnlySpan scaleCharacters = format[index..]; + + if (scaleCharacters.IsEmpty) + return (specifier.ToUpperInvariant(), 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.None, CultureInfo.InvariantCulture, out int scale) + ? (specifier.ToUpperInvariant(), scale) + : throw new FormatException("Scale must contain only decimal digits."); + } +} 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.Addition.cs b/OnixLabs.Units/Temperature.Arithmetic.Addition.cs new file mode 100644 index 0000000..a2c5f9c --- /dev/null +++ b/OnixLabs.Units/Temperature.Arithmetic.Addition.cs @@ -0,0 +1,41 @@ +// 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 +{ + /// + /// 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 Temperature Add(Temperature left, Temperature right) => new(left.Kelvin + right.Kelvin); + + /// + /// 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 Temperature operator +(Temperature left, Temperature right) => Add(left, right); + + /// + /// 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 Temperature Add(Temperature other) => Add(this, other); +} diff --git a/OnixLabs.Units/Temperature.Arithmetic.Division.cs b/OnixLabs.Units/Temperature.Arithmetic.Division.cs new file mode 100644 index 0000000..0071c93 --- /dev/null +++ b/OnixLabs.Units/Temperature.Arithmetic.Division.cs @@ -0,0 +1,41 @@ +// 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 +{ + /// + /// 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 Temperature Divide(Temperature left, Temperature right) => new(left.Kelvin / right.Kelvin); + + /// + /// 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 Temperature operator /(Temperature left, Temperature right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value, divided by the specified other value. + public Temperature Divide(Temperature other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Temperature.Arithmetic.Multiplication.cs b/OnixLabs.Units/Temperature.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..9d8355a --- /dev/null +++ b/OnixLabs.Units/Temperature.Arithmetic.Multiplication.cs @@ -0,0 +1,41 @@ +// 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 +{ + /// + /// 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 Temperature Multiply(Temperature left, Temperature right) => new(left.Kelvin * right.Kelvin); + + /// + /// 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 Temperature operator *(Temperature left, Temperature right) => Multiply(left, right); + + /// + /// Computes the product of the current value, multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value, multiplied by the specified other value. + public Temperature Multiply(Temperature other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Temperature.Arithmetic.Subtraction.cs b/OnixLabs.Units/Temperature.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..81fcae4 --- /dev/null +++ b/OnixLabs.Units/Temperature.Arithmetic.Subtraction.cs @@ -0,0 +1,41 @@ +// 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 +{ + /// + /// 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 Temperature Subtract(Temperature left, Temperature right) => new(left.Kelvin - right.Kelvin); + + /// + /// 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 Temperature operator -(Temperature left, Temperature right) => Subtract(left, right); + + /// + /// Computes the difference of the specified other value, subtracted from the current value. + /// + /// The value to subtract from the current value. + /// Returns the difference of the specified other value, subtracted from the current value. + public Temperature Subtract(Temperature other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Temperature.Comparable.cs b/OnixLabs.Units/Temperature.Comparable.cs new file mode 100644 index 0000000..566676c --- /dev/null +++ b/OnixLabs.Units/Temperature.Comparable.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 OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(Temperature left, Temperature right) => left.Kelvin.CompareTo(right.Kelvin); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(Temperature other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than right-hand operand; otherwise, . + public static bool operator >(Temperature left, Temperature right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(Temperature left, Temperature right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(Temperature left, Temperature right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(Temperature left, Temperature right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Temperature.Constants.cs b/OnixLabs.Units/Temperature.Constants.cs new file mode 100644 index 0000000..9e6e92c --- /dev/null +++ b/OnixLabs.Units/Temperature.Constants.cs @@ -0,0 +1,44 @@ +// 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 +{ + /// + /// Gets a zero 0 value, equal to absolute zero, or 0 K. + /// + public static readonly 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é"; +} diff --git a/OnixLabs.Units/Temperature.Equatable.cs b/OnixLabs.Units/Temperature.Equatable.cs new file mode 100644 index 0000000..9a28d2d --- /dev/null +++ b/OnixLabs.Units/Temperature.Equatable.cs @@ -0,0 +1,64 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(Temperature left, Temperature right) => Equals(left.Kelvin, right.Kelvin); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// Returns if the current instance is equal to the specified other instance; otherwise, . + public bool Equals(Temperature other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Temperature other && Equals(other); + + /// + /// Serves as a hash code function for this instance. + /// + /// A hash code for this instance. + public override int GetHashCode() => Kelvin.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(Temperature left, Temperature right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(Temperature left, Temperature right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Temperature.Format.cs b/OnixLabs.Units/Temperature.Format.cs new file mode 100644 index 0000000..991e032 --- /dev/null +++ b/OnixLabs.Units/Temperature.Format.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.Globalization; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Temperature +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + 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..c3115fa --- /dev/null +++ b/OnixLabs.Units/Temperature.From.cs @@ -0,0 +1,67 @@ +// 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 a Celsius value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Temperature FromCelsius(T value) => new(value + T.CreateChecked(273.15)); + + /// + /// Creates a new instance from a Delisle value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + 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 a Fahrenheit value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Temperature FromFahrenheit(T value) => new((value + T.CreateChecked(459.67)) / T.CreateChecked(1.80)); + + /// + /// Creates a new instance from a Kelvin value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Temperature FromKelvin(T value) => new(value); + + /// + /// Creates a new instance from a Newton value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + 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 a Rankine value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Temperature FromRankine(T value) => new(value / T.CreateChecked(1.8)); + + /// + /// Creates a new instance from a Réaumur value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Temperature FromReaumur(T value) => new(value * T.CreateChecked(1.25) + T.CreateChecked(273.15)); +} diff --git a/OnixLabs.Units/Temperature.To.cs b/OnixLabs.Units/Temperature.To.cs new file mode 100644 index 0000000..0e3599e --- /dev/null +++ b/OnixLabs.Units/Temperature.To.cs @@ -0,0 +1,62 @@ +// 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 +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// The value of the current instance in the default format. + public override string ToString() => ToString(KelvinSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// The value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// The value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(KelvinSpecifier); + + (T value, string symbol) = specifier switch + { + CelsiusSpecifier => (Celsius, CelsiusSymbol), + DelisleSpecifier => (Delisle, DelisleSymbol), + FahrenheitSpecifier => (Fahrenheit, FahrenheitSymbol), + KelvinSpecifier => (Kelvin, KelvinSymbol), + NewtonSpecifier => (Newton, NewtonSymbol), + RankineSpecifier => (Rankine, RankineSymbol), + ReaumurSpecifier => (Reaumur, ReaumurSymbol), + _ => throw new ArgumentException($"Format '{format}' is invalid. Valid format specifiers are: C, De, F, K, N, Re, and R. Format specifiers may also be suffixed with a scale value.", nameof(format)) + }; + + 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..42f24b5 --- /dev/null +++ b/OnixLabs.Units/Temperature.cs @@ -0,0 +1,70 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of temperature. The default value is absolute zero, or 0 K. +/// +public readonly partial struct Temperature : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The value in Kelvin with which to initialize the current instance. + private Temperature(T value) => Kelvin = value; + + /// + /// Gets the temperature in Kelvin. + /// + public T Kelvin { get; } + + /// + /// Gets the temperature in Celsius. + /// + public T Celsius => Kelvin - T.CreateChecked(273.15); + + /// + /// Gets the temperature in Delisle. + /// + public T Delisle => (T.CreateChecked(373.15) - Kelvin) * T.CreateChecked(1.50); + + /// + /// Gets the temperature in Fahrenheit. + /// + public T Fahrenheit => Kelvin * T.CreateChecked(1.80) - T.CreateChecked(459.67); + + /// + /// Gets the temperature in Newton. + /// + public T Newton => (Kelvin - T.CreateChecked(273.15)) * T.CreateChecked(33.00) / T.CreateChecked(100.00); + + /// + /// Gets the temperature in Rankine. + /// + public T Rankine => Kelvin * T.CreateChecked(1.8); + + /// + /// Gets the temperature in Réaumur. + /// + public T Reaumur => (Kelvin - T.CreateChecked(273.15)) * T.CreateChecked(0.80); +} diff --git a/onixlabs-dotnet.sln b/onixlabs-dotnet.sln index 9c03580..00b356b 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", "{BCF89E1C-16DD-4EA7-911C-2AFEB663CE87}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnixLabs.Units", "OnixLabs.Units\OnixLabs.Units.csproj", "{85FDE227-D645-4D11-AFD1-4372977BFFEF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OnixLabs.Units.UnitTests", "OnixLabs.Units.UnitTests\OnixLabs.Units.UnitTests.csproj", "{8A0381A4-9A01-4890-ADB4-417DAE0C41C7}" +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} + {85FDE227-D645-4D11-AFD1-4372977BFFEF} = {BCF89E1C-16DD-4EA7-911C-2AFEB663CE87} + {8A0381A4-9A01-4890-ADB4-417DAE0C41C7} = {BCF89E1C-16DD-4EA7-911C-2AFEB663CE87} 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 + {85FDE227-D645-4D11-AFD1-4372977BFFEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85FDE227-D645-4D11-AFD1-4372977BFFEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85FDE227-D645-4D11-AFD1-4372977BFFEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85FDE227-D645-4D11-AFD1-4372977BFFEF}.Release|Any CPU.Build.0 = Release|Any CPU + {8A0381A4-9A01-4890-ADB4-417DAE0C41C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A0381A4-9A01-4890-ADB4-417DAE0C41C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A0381A4-9A01-4890-ADB4-417DAE0C41C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A0381A4-9A01-4890-ADB4-417DAE0C41C7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From ba4ed9a5ed3d71d197a0f5238057bacf09d4ec3b Mon Sep 17 00:00:00 2001 From: Matthew Layton Date: Mon, 1 Sep 2025 23:33:09 +0100 Subject: [PATCH 02/23] Added DataSize implementation and unit tests. --- OnixLabs.Units.UnitTests/DataSizeTests.cs | 613 ++++++++++++++++++ OnixLabs.Units.UnitTests/TemperatureTests.cs | 162 ++--- .../DataSize.Arithmetic.Addition.cs | 41 ++ .../DataSize.Arithmetic.Division.cs | 41 ++ .../DataSize.Arithmetic.Multiplication.cs | 41 ++ .../DataSize.Arithmetic.Subtraction.cs | 41 ++ OnixLabs.Units/DataSize.Comparable.cs | 80 +++ OnixLabs.Units/DataSize.Constants.cs | 58 ++ OnixLabs.Units/DataSize.Equatable.cs | 64 ++ OnixLabs.Units/DataSize.Format.cs | 33 + OnixLabs.Units/DataSize.From.cs | 284 ++++++++ OnixLabs.Units/DataSize.To.cs | 95 +++ OnixLabs.Units/DataSize.cs | 228 +++++++ OnixLabs.Units/Extensions.ReadOnlySpan.cs | 4 +- OnixLabs.Units/Temperature.To.cs | 4 +- OnixLabs.Units/Temperature.cs | 1 + onixlabs-dotnet.sln.DotSettings | 2 + 17 files changed, 1707 insertions(+), 85 deletions(-) create mode 100644 OnixLabs.Units.UnitTests/DataSizeTests.cs create mode 100755 OnixLabs.Units/DataSize.Arithmetic.Addition.cs create mode 100755 OnixLabs.Units/DataSize.Arithmetic.Division.cs create mode 100755 OnixLabs.Units/DataSize.Arithmetic.Multiplication.cs create mode 100755 OnixLabs.Units/DataSize.Arithmetic.Subtraction.cs create mode 100755 OnixLabs.Units/DataSize.Comparable.cs create mode 100755 OnixLabs.Units/DataSize.Constants.cs create mode 100755 OnixLabs.Units/DataSize.Equatable.cs create mode 100755 OnixLabs.Units/DataSize.Format.cs create mode 100755 OnixLabs.Units/DataSize.From.cs create mode 100755 OnixLabs.Units/DataSize.To.cs create mode 100755 OnixLabs.Units/DataSize.cs 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/TemperatureTests.cs b/OnixLabs.Units.UnitTests/TemperatureTests.cs index d2d4cfb..6ffb919 100644 --- a/OnixLabs.Units.UnitTests/TemperatureTests.cs +++ b/OnixLabs.Units.UnitTests/TemperatureTests.cs @@ -234,11 +234,11 @@ public void TemperatureFromReaumurShouldProduceExpectedResult( public void TemperatureAddShouldProduceExpectedValue() { // Given - Temperature temperature1 = Temperature.FromKelvin(100.0); - Temperature temperature2 = Temperature.FromKelvin(50.0); + Temperature left = Temperature.FromKelvin(100.0); + Temperature right = Temperature.FromKelvin(50.0); // When - Temperature result = temperature1.Add(temperature2); + Temperature result = left.Add(right); // Then Assert.Equal(150.0, result.Kelvin, Tolerance); @@ -248,11 +248,11 @@ public void TemperatureAddShouldProduceExpectedValue() public void TemperatureSubtractShouldProduceExpectedValue() { // Given - Temperature temperature1 = Temperature.FromKelvin(100.0); - Temperature temperature2 = Temperature.FromKelvin(40.0); + Temperature left = Temperature.FromKelvin(100.0); + Temperature right = Temperature.FromKelvin(40.0); // When - Temperature result = temperature1.Subtract(temperature2); + Temperature result = left.Subtract(right); // Then Assert.Equal(60.0, result.Kelvin, Tolerance); @@ -262,11 +262,11 @@ public void TemperatureSubtractShouldProduceExpectedValue() public void TemperatureMultiplyShouldProduceExpectedValue() { // Given - Temperature temperature1 = Temperature.FromKelvin(10.0); - Temperature temperature2 = Temperature.FromKelvin(3.0); + Temperature left = Temperature.FromKelvin(10.0); + Temperature right = Temperature.FromKelvin(3.0); // When - Temperature result = temperature1.Multiply(temperature2); + Temperature result = left.Multiply(right); // Then Assert.Equal(30.0, result.Kelvin, Tolerance); @@ -276,11 +276,11 @@ public void TemperatureMultiplyShouldProduceExpectedValue() public void TemperatureDivideShouldProduceExpectedValue() { // Given - Temperature temperature1 = Temperature.FromKelvin(100.0); - Temperature temperature2 = Temperature.FromKelvin(20.0); + Temperature left = Temperature.FromKelvin(100.0); + Temperature right = Temperature.FromKelvin(20.0); // When - Temperature result = temperature1.Divide(temperature2); + Temperature result = left.Divide(right); // Then Assert.Equal(5.0, result.Kelvin, Tolerance); @@ -290,131 +290,131 @@ public void TemperatureDivideShouldProduceExpectedValue() public void TemperatureComparisonShouldProduceExpectedResultLeftEqualToRight() { // Given - Temperature temperature1 = Temperature.FromKelvin(123); - Temperature temperature2 = Temperature.FromKelvin(123); + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(123); // When / Then - Assert.Equal(0, Temperature.Compare(temperature1, temperature2)); - Assert.Equal(0, temperature1.CompareTo(temperature2)); - Assert.Equal(0, temperature1.CompareTo((object)temperature2)); - Assert.False(temperature1 > temperature2); - Assert.True(temperature1 >= temperature2); - Assert.False(temperature1 < temperature2); - Assert.True(temperature1 <= temperature2); + 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 temperature1 = Temperature.FromKelvin(456); - Temperature temperature2 = Temperature.FromKelvin(123); + Temperature left = Temperature.FromKelvin(456); + Temperature right = Temperature.FromKelvin(123); // When / Then - Assert.Equal(1, Temperature.Compare(temperature1, temperature2)); - Assert.Equal(1, temperature1.CompareTo(temperature2)); - Assert.Equal(1, temperature1.CompareTo((object)temperature2)); - Assert.True(temperature1 > temperature2); - Assert.True(temperature1 >= temperature2); - Assert.False(temperature1 < temperature2); - Assert.False(temperature1 <= temperature2); + 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 temperature1 = Temperature.FromKelvin(456); - Temperature temperature2 = Temperature.FromKelvin(123); + Temperature left = Temperature.FromKelvin(456); + Temperature right = Temperature.FromKelvin(123); // When / Then - Assert.Equal(1, Temperature.Compare(temperature1, temperature2)); - Assert.Equal(1, temperature1.CompareTo(temperature2)); - Assert.Equal(1, temperature1.CompareTo((object)temperature2)); - Assert.True(temperature1 > temperature2); - Assert.True(temperature1 >= temperature2); - Assert.False(temperature1 < temperature2); - Assert.False(temperature1 <= temperature2); + 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 temperature1 = Temperature.FromKelvin(123); - Temperature temperature2 = Temperature.FromKelvin(456); + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(456); // When / Then - Assert.Equal(-1, Temperature.Compare(temperature1, temperature2)); - Assert.Equal(-1, temperature1.CompareTo(temperature2)); - Assert.Equal(-1, temperature1.CompareTo((object)temperature2)); - Assert.False(temperature1 > temperature2); - Assert.False(temperature1 >= temperature2); - Assert.True(temperature1 < temperature2); - Assert.True(temperature1 <= temperature2); + 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 temperature1 = Temperature.FromKelvin(123); - Temperature temperature2 = Temperature.FromKelvin(456); + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(456); // When / Then - Assert.Equal(-1, Temperature.Compare(temperature1, temperature2)); - Assert.Equal(-1, temperature1.CompareTo(temperature2)); - Assert.Equal(-1, temperature1.CompareTo((object)temperature2)); - Assert.False(temperature1 > temperature2); - Assert.False(temperature1 >= temperature2); - Assert.True(temperature1 < temperature2); - Assert.True(temperature1 <= temperature2); + 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 temperature1 = Temperature.FromKelvin(123); - Temperature temperature2 = Temperature.FromKelvin(123); + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(123); // When / Then - Assert.True(Temperature.Equals(temperature1, temperature2)); - Assert.True(temperature1.Equals(temperature2)); - Assert.True(temperature1.Equals((object)temperature2)); - Assert.True(temperature1 == temperature2); - Assert.False(temperature1 != temperature2); + 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 temperature1 = Temperature.FromKelvin(123); - Temperature temperature2 = Temperature.FromKelvin(456); + Temperature left = Temperature.FromKelvin(123); + Temperature right = Temperature.FromKelvin(456); // When / Then - Assert.False(Temperature.Equals(temperature1, temperature2)); - Assert.False(temperature1.Equals(temperature2)); - Assert.False(temperature1.Equals((object)temperature2)); - Assert.False(temperature1 == temperature2); - Assert.True(temperature1 != temperature2); + 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 temp = Temperature.FromCelsius(100.0); + Temperature temperature = Temperature.FromCelsius(100.0); // Then - Assert.Equal("373.150 K", $"{temp:K}"); - Assert.Equal("100.000 °C", $"{temp:C}"); - Assert.Equal("0.000 °De", $"{temp:DE}"); - Assert.Equal("212.000 °F", $"{temp:F}"); - Assert.Equal("33.000 °N", $"{temp:N}"); - Assert.Equal("80.000 °Ré", $"{temp:RE}"); - Assert.Equal("671.670 °R", $"{temp:R}"); + Assert.Equal("373.150 K", $"{temperature:K}"); + Assert.Equal("100.000 °C", $"{temperature:C}"); + Assert.Equal("0.000 °De", $"{temperature:DE}"); + Assert.Equal("212.000 °F", $"{temperature:F}"); + Assert.Equal("33.000 °N", $"{temperature:N}"); + Assert.Equal("80.000 °Ré", $"{temperature:RE}"); + Assert.Equal("671.670 °R", $"{temperature:R}"); } [Fact(DisplayName = "Temperature.ToString should honor custom culture separators")] @@ -422,10 +422,10 @@ public void TemperatureToStringShouldHonorCustomCulture() { // Given CultureInfo customCulture = new("de-DE"); - Temperature temp = Temperature.FromKelvin(1234.56); + Temperature temperature = Temperature.FromKelvin(1234.56); // When - string formatted = temp.ToString("K2", customCulture); + string formatted = temperature.ToString("K2", customCulture); // Then Assert.Equal("1.234,56 K", formatted); diff --git a/OnixLabs.Units/DataSize.Arithmetic.Addition.cs b/OnixLabs.Units/DataSize.Arithmetic.Addition.cs new file mode 100755 index 0000000..64e8bdb --- /dev/null +++ b/OnixLabs.Units/DataSize.Arithmetic.Addition.cs @@ -0,0 +1,41 @@ +// 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 +{ + /// + /// 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 DataSize Add(DataSize left, DataSize right) => new(left.Bits + right.Bits); + + /// + /// 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 DataSize operator +(DataSize left, DataSize right) => Add(left, right); + + /// + /// 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 DataSize Add(DataSize other) => Add(this, other); +} diff --git a/OnixLabs.Units/DataSize.Arithmetic.Division.cs b/OnixLabs.Units/DataSize.Arithmetic.Division.cs new file mode 100755 index 0000000..c1d5eee --- /dev/null +++ b/OnixLabs.Units/DataSize.Arithmetic.Division.cs @@ -0,0 +1,41 @@ +// 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 +{ + /// + /// 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 DataSize Divide(DataSize left, DataSize right) => new(left.Bits / right.Bits); + + /// + /// 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 DataSize operator /(DataSize left, DataSize right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value, divided by the specified other value. + public DataSize Divide(DataSize other) => Divide(this, other); +} diff --git a/OnixLabs.Units/DataSize.Arithmetic.Multiplication.cs b/OnixLabs.Units/DataSize.Arithmetic.Multiplication.cs new file mode 100755 index 0000000..10ad558 --- /dev/null +++ b/OnixLabs.Units/DataSize.Arithmetic.Multiplication.cs @@ -0,0 +1,41 @@ +// 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 +{ + /// + /// 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 DataSize Multiply(DataSize left, DataSize right) => new(left.Bits * right.Bits); + + /// + /// 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 DataSize operator *(DataSize left, DataSize right) => Multiply(left, right); + + /// + /// Computes the product of the current value, multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value, multiplied by the specified other value. + public DataSize Multiply(DataSize other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/DataSize.Arithmetic.Subtraction.cs b/OnixLabs.Units/DataSize.Arithmetic.Subtraction.cs new file mode 100755 index 0000000..e80ed27 --- /dev/null +++ b/OnixLabs.Units/DataSize.Arithmetic.Subtraction.cs @@ -0,0 +1,41 @@ +// 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 +{ + /// + /// 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 DataSize Subtract(DataSize left, DataSize right) => new(left.Bits - right.Bits); + + /// + /// 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 DataSize operator -(DataSize left, DataSize right) => Subtract(left, right); + + /// + /// Computes the difference of the specified other value, subtracted from the current value. + /// + /// The value to subtract from the current value. + /// Returns the difference of the specified other value, subtracted from the current value. + public DataSize Subtract(DataSize other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/DataSize.Comparable.cs b/OnixLabs.Units/DataSize.Comparable.cs new file mode 100755 index 0000000..0c2ac49 --- /dev/null +++ b/OnixLabs.Units/DataSize.Comparable.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 OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(DataSize left, DataSize right) => left.Bits.CompareTo(right.Bits); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(DataSize other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than right-hand operand; otherwise, . + public static bool operator >(DataSize left, DataSize right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(DataSize left, DataSize right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(DataSize left, DataSize right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(DataSize left, DataSize right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/DataSize.Constants.cs b/OnixLabs.Units/DataSize.Constants.cs new file mode 100755 index 0000000..48b03b9 --- /dev/null +++ b/OnixLabs.Units/DataSize.Constants.cs @@ -0,0 +1,58 @@ +// 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 +{ + /// + /// Gets a zero 0 value, equal to zero bits. + /// + public static readonly 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 100755 index 0000000..186edfa --- /dev/null +++ b/OnixLabs.Units/DataSize.Equatable.cs @@ -0,0 +1,64 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(DataSize left, DataSize right) => Equals(left.Bits, right.Bits); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// Returns if the current instance is equal to the specified other instance; otherwise, . + public bool Equals(DataSize other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is DataSize other && Equals(other); + + /// + /// Serves as a hash code function for this instance. + /// + /// A hash code for this instance. + public override int GetHashCode() => Bits.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(DataSize left, DataSize right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(DataSize left, DataSize right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/DataSize.Format.cs b/OnixLabs.Units/DataSize.Format.cs new file mode 100755 index 0000000..604f6a8 --- /dev/null +++ b/OnixLabs.Units/DataSize.Format.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.Numerics; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct DataSize +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + 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 100755 index 0000000..d1071c0 --- /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 a Bits value. + /// + /// The value to convert from bits. + /// Returns a new instance from a Bits value. + public static DataSize FromBits(T value) => new(value); + + /// + /// Creates a new instance from a Bytes value. + /// + /// The value to convert from bytes. + /// Returns a new instance from a Bytes value. + public static DataSize FromBytes(T value) => new(value * T.CreateChecked(8)); + + /// + /// Creates a new instance from a KibiBits value (IEC, 2^10). + /// + /// The value to convert from kibibits. + /// Returns a new instance from a KibiBits value. + public static DataSize FromKibiBits(T value) => CreateFromBinaryValue(value, 1, false); + + /// + /// Creates a new instance from a KibiBytes value (IEC, 2^10). + /// + /// The value to convert from kibibytes. + /// Returns a new instance from a KibiBytes value. + public static DataSize FromKibiBytes(T value) => CreateFromBinaryValue(value, 1, true); + + /// + /// Creates a new instance from a KiloBits value (SI, 10^3). + /// + /// The value to convert from kilobits. + /// Returns a new instance from a KiloBits value. + public static DataSize FromKiloBits(T value) => CreateFromMetricValue(value, 1, false); + + /// + /// Creates a new instance from a KiloBytes value (SI, 10^3). + /// + /// The value to convert from kilobytes. + /// Returns a new instance from a KiloBytes value. + public static DataSize FromKiloBytes(T value) => CreateFromMetricValue(value, 1, true); + + /// + /// Creates a new instance from a MebiBits value (IEC, 2^20). + /// + /// The value to convert from mebibits. + /// Returns a new instance from a MebiBits value. + public static DataSize FromMebiBits(T value) => CreateFromBinaryValue(value, 2, false); + + /// + /// Creates a new instance from a MebiBytes value (IEC, 2^20). + /// + /// The value to convert from mebibytes. + /// Returns a new instance from a MebiBytes value. + public static DataSize FromMebiBytes(T value) => CreateFromBinaryValue(value, 2, true); + + /// + /// Creates a new instance from a MegaBits value (SI, 10^6). + /// + /// The value to convert from megabits. + /// Returns a new instance from a MegaBits value. + public static DataSize FromMegaBits(T value) => CreateFromMetricValue(value, 2, false); + + /// + /// Creates a new instance from a MegaBytes value (SI, 10^6). + /// + /// The value to convert from megabytes. + /// Returns a new instance from a MegaBytes value. + public static DataSize FromMegaBytes(T value) => CreateFromMetricValue(value, 2, true); + + /// + /// Creates a new instance from a GibiBits value (IEC, 2^30). + /// + /// The value to convert from gibibits. + /// Returns a new instance from a GibiBits value. + public static DataSize FromGibiBits(T value) => CreateFromBinaryValue(value, 3, false); + + /// + /// Creates a new instance from a GibiBytes value (IEC, 2^30). + /// + /// The value to convert from gibibytes. + /// Returns a new instance from a GibiBytes value. + public static DataSize FromGibiBytes(T value) => CreateFromBinaryValue(value, 3, true); + + /// + /// Creates a new instance from a GigaBits value (SI, 10^9). + /// + /// The value to convert from gigabits. + /// Returns a new instance from a GigaBits value. + public static DataSize FromGigaBits(T value) => CreateFromMetricValue(value, 3, false); + + /// + /// Creates a new instance from a GigaBytes value (SI, 10^9). + /// + /// The value to convert from gigabytes. + /// Returns a new instance from a GigaBytes value. + public static DataSize FromGigaBytes(T value) => CreateFromMetricValue(value, 3, true); + + /// + /// Creates a new instance from a TebiBits value (IEC, 2^40). + /// + /// The value to convert from tebibits. + /// Returns a new instance from a TebiBits value. + public static DataSize FromTebiBits(T value) => CreateFromBinaryValue(value, 4, false); + + /// + /// Creates a new instance from a TebiBytes value (IEC, 2^40). + /// + /// The value to convert from tebibytes. + /// Returns a new instance from a TebiBytes value. + public static DataSize FromTebiBytes(T value) => CreateFromBinaryValue(value, 4, true); + + /// + /// Creates a new instance from a TeraBits value (SI, 10^12). + /// + /// The value to convert from terabits. + /// Returns a new instance from a TeraBits value. + public static DataSize FromTeraBits(T value) => CreateFromMetricValue(value, 4, false); + + /// + /// Creates a new instance from a TeraBytes value (SI, 10^12). + /// + /// The value to convert from terabytes. + /// Returns a new instance from a TeraBytes value. + public static DataSize FromTeraBytes(T value) => CreateFromMetricValue(value, 4, true); + + /// + /// Creates a new instance from a PebiBits value (IEC, 2^50). + /// + /// The value to convert from pebibits. + /// Returns a new instance from a PebiBits value. + public static DataSize FromPebiBits(T value) => CreateFromBinaryValue(value, 5, false); + + /// + /// Creates a new instance from a PebiBytes value (IEC, 2^50). + /// + /// The value to convert from pebibytes. + /// Returns a new instance from a PebiBytes value. + public static DataSize FromPebiBytes(T value) => CreateFromBinaryValue(value, 5, true); + + /// + /// Creates a new instance from a PetaBits value (SI, 10^15). + /// + /// The value to convert from petabits. + /// Returns a new instance from a PetaBits value. + public static DataSize FromPetaBits(T value) => CreateFromMetricValue(value, 5, false); + + /// + /// Creates a new instance from a PetaBytes value (SI, 10^15). + /// + /// The value to convert from petabytes. + /// Returns a new instance from a PetaBytes value. + public static DataSize FromPetaBytes(T value) => CreateFromMetricValue(value, 5, true); + + /// + /// Creates a new instance from an ExbiBits value (IEC, 2^60). + /// + /// The value to convert from exbibits. + /// Returns a new instance from an ExbiBits value. + public static DataSize FromExbiBits(T value) => CreateFromBinaryValue(value, 6, false); + + /// + /// Creates a new instance from an ExbiBytes value (IEC, 2^60). + /// + /// The value to convert from exbibytes. + /// Returns a new instance from an ExbiBytes value. + public static DataSize FromExbiBytes(T value) => CreateFromBinaryValue(value, 6, true); + + /// + /// Creates a new instance from an ExaBits value (SI, 10^18). + /// + /// The value to convert from exabits. + /// Returns a new instance from an ExaBits value. + public static DataSize FromExaBits(T value) => CreateFromMetricValue(value, 6, false); + + /// + /// Creates a new instance from an ExaBytes value (SI, 10^18). + /// + /// The value to convert from exabytes. + /// Returns a new instance from an ExaBytes value. + public static DataSize FromExaBytes(T value) => CreateFromMetricValue(value, 6, true); + + /// + /// Creates a new instance from a ZebiBits value (IEC, 2^70). + /// + /// The value to convert from zebibits. + /// Returns a new instance from a ZebiBits value. + public static DataSize FromZebiBits(T value) => CreateFromBinaryValue(value, 7, false); + + /// + /// Creates a new instance from a ZebiBytes value (IEC, 2^70). + /// + /// The value to convert from zebibytes. + /// Returns a new instance from a ZebiBytes value. + public static DataSize FromZebiBytes(T value) => CreateFromBinaryValue(value, 7, true); + + /// + /// Creates a new instance from a ZettaBits value (SI, 10^21). + /// + /// The value to convert from zettabits. + /// Returns a new instance from a ZettaBits value. + public static DataSize FromZettaBits(T value) => CreateFromMetricValue(value, 7, false); + + /// + /// Creates a new instance from a ZettaBytes value (SI, 10^21). + /// + /// The value to convert from zettabytes. + /// Returns a new instance from a ZettaBytes value. + public static DataSize FromZettaBytes(T value) => CreateFromMetricValue(value, 7, true); + + /// + /// Creates a new instance from a YobiBits value (IEC, 2^80). + /// + /// The value to convert from yobibits. + /// Returns a new instance from a YobiBits value. + public static DataSize FromYobiBits(T value) => CreateFromBinaryValue(value, 8, false); + + /// + /// Creates a new instance from a YobiBytes value (IEC, 2^80). + /// + /// The value to convert from yobibytes. + /// Returns a new instance from a YobiBytes value. + public static DataSize FromYobiBytes(T value) => CreateFromBinaryValue(value, 8, true); + + /// + /// Creates a new instance from a YottaBits value (SI, 10^24). + /// + /// The value to convert from yottabits. + /// Returns a new instance from a YottaBits value. + public static DataSize FromYottaBits(T value) => CreateFromMetricValue(value, 8, false); + + /// + /// Creates a new instance from a YottaBytes value (SI, 10^24). + /// + /// The value to convert from yottabytes. + /// Returns a new instance from a YottaBytes 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 100755 index 0000000..40758e3 --- /dev/null +++ b/OnixLabs.Units/DataSize.To.cs @@ -0,0 +1,95 @@ +// 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 +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// The value of the current instance in the default format. + public override string ToString() => ToString(BitsSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// The value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// The value of the current instance in the specified format. + 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 new ArgumentException( + $"Format '{format}' is invalid. Valid format specifiers are: " + + "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, YB. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + 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 100755 index 0000000..df0eb07 --- /dev/null +++ b/OnixLabs.Units/DataSize.cs @@ -0,0 +1,228 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of data size. +/// +/// The underlying floating point value. +public readonly partial struct DataSize : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + private DataSize(T value) => Bits = value; + + /// + /// Gets the data size in bits. + /// + public T Bits { get; } + + /// + /// Gets the data size in bytes. + /// + public T Bytes => Bits / T.CreateChecked(8); + + /// + /// Gets the data size in kibibits. + /// + public T KibiBits => GetBinaryValue(1, false); + + /// + /// Gets the data size in kibibytes. + /// + public T KibiBytes => GetBinaryValue(1, true); + + /// + /// Gets the data size in kilobits. + /// + public T KiloBits => GetMetricValue(1, false); + + /// + /// Gets the data size in kilobytes. + /// + public T KiloBytes => GetMetricValue(1, true); + + /// + /// Gets the data size in mebibits. + /// + public T MebiBits => GetBinaryValue(2, false); + + /// + /// Gets the data size in mebibytes. + /// + public T MebiBytes => GetBinaryValue(2, true); + + /// + /// Gets the data size in megabits. + /// + public T MegaBits => GetMetricValue(2, false); + + /// + /// Gets the data size in megabytes. + /// + public T MegaBytes => GetMetricValue(2, true); + + /// + /// Gets the data size in gibibits. + /// + public T GibiBits => GetBinaryValue(3, false); + + /// + /// Gets the data size in gibibytes. + /// + public T GibiBytes => GetBinaryValue(3, true); + + /// + /// Gets the data size in gigabits. + /// + public T GigaBits => GetMetricValue(3, false); + + /// + /// Gets the data size in gigabytes. + /// + public T GigaBytes => GetMetricValue(3, true); + + /// + /// Gets the data size in tebibits. + /// + public T TebiBits => GetBinaryValue(4, false); + + /// + /// Gets the data size in tebibytes. + /// + public T TebiBytes => GetBinaryValue(4, true); + + /// + /// Gets the data size in terabits. + /// + public T TeraBits => GetMetricValue(4, false); + + /// + /// Gets the data size in terabytes. + /// + public T TeraBytes => GetMetricValue(4, true); + + /// + /// Gets the data size in pebibits. + /// + public T PebiBits => GetBinaryValue(5, false); + + /// + /// Gets the data size in pebibytes. + /// + public T PebiBytes => GetBinaryValue(5, true); + + /// + /// Gets the data size in petabits. + /// + public T PetaBits => GetMetricValue(5, false); + + /// + /// Gets the data size in petabytes. + /// + public T PetaBytes => GetMetricValue(5, true); + + /// + /// Gets the data size in exbibits. + /// + public T ExbiBits => GetBinaryValue(6, false); + + /// + /// Gets the data size in exbibytes. + /// + public T ExbiBytes => GetBinaryValue(6, true); + + /// + /// Gets the data size in exabits. + /// + public T ExaBits => GetMetricValue(6, false); + + /// + /// Gets the data size in exabytes. + /// + public T ExaBytes => GetMetricValue(6, true); + + /// + /// Gets the data size in zebibits. + /// + public T ZebiBits => GetBinaryValue(7, false); + + /// + /// Gets the data size in zebibytes. + /// + public T ZebiBytes => GetBinaryValue(7, true); + + /// + /// Gets the data size in zettabits. + /// + public T ZettaBits => GetMetricValue(7, false); + + /// + /// Gets the data size in zettabytes. + /// + public T ZettaBytes => GetMetricValue(7, true); + + /// + /// Gets the data size in yobibits. + /// + public T YobiBits => GetBinaryValue(8, false); + + /// + /// Gets the data size in yobibytes. + /// + public T YobiBytes => GetBinaryValue(8, true); + + /// + /// Gets the data size in yottabits. + /// + public T YottaBits => GetMetricValue(8, false); + + /// + /// Gets the data size in yottabytes. + /// + 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/Extensions.ReadOnlySpan.cs b/OnixLabs.Units/Extensions.ReadOnlySpan.cs index b387039..b6745ad 100644 --- a/OnixLabs.Units/Extensions.ReadOnlySpan.cs +++ b/OnixLabs.Units/Extensions.ReadOnlySpan.cs @@ -41,13 +41,13 @@ public static (string specifier, int scale) GetSpecifierAndScale(this ReadOnlySp ReadOnlySpan scaleCharacters = format[index..]; if (scaleCharacters.IsEmpty) - return (specifier.ToUpperInvariant(), defaultScale); + 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.None, CultureInfo.InvariantCulture, out int scale) - ? (specifier.ToUpperInvariant(), scale) + ? (specifier, scale) : throw new FormatException("Scale must contain only decimal digits."); } } diff --git a/OnixLabs.Units/Temperature.To.cs b/OnixLabs.Units/Temperature.To.cs index 0e3599e..6a49c7d 100644 --- a/OnixLabs.Units/Temperature.To.cs +++ b/OnixLabs.Units/Temperature.To.cs @@ -41,9 +41,9 @@ public readonly partial struct Temperature /// The value of the current instance in the specified format. public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) { - (string specifier, int scale) = format.GetSpecifierAndScale(KelvinSpecifier); + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: KelvinSpecifier); - (T value, string symbol) = specifier switch + (T value, string symbol) = specifier.ToUpperInvariant() switch { CelsiusSpecifier => (Celsius, CelsiusSymbol), DelisleSpecifier => (Delisle, DelisleSymbol), diff --git a/OnixLabs.Units/Temperature.cs b/OnixLabs.Units/Temperature.cs index 42f24b5..98445af 100644 --- a/OnixLabs.Units/Temperature.cs +++ b/OnixLabs.Units/Temperature.cs @@ -21,6 +21,7 @@ namespace OnixLabs.Units; /// /// Represents a unit of temperature. The default value is absolute zero, or 0 K. /// +/// The underlying floating point value. public readonly partial struct Temperature : IValueEquatable>, IValueComparable>, diff --git a/onixlabs-dotnet.sln.DotSettings b/onixlabs-dotnet.sln.DotSettings index be92f32..68bf0bd 100644 --- a/onixlabs-dotnet.sln.DotSettings +++ b/onixlabs-dotnet.sln.DotSettings @@ -13,6 +13,7 @@ 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 @@ -23,6 +24,7 @@ limitations under the License. True True True + True True True True From 54d322f7436e3fe02ae35d452edb92d9d638e5d3 Mon Sep 17 00:00:00 2001 From: Matthew Layton Date: Tue, 2 Sep 2025 12:00:09 +0100 Subject: [PATCH 03/23] Added Romer scale to Temperature. --- OnixLabs.Units.UnitTests/TemperatureTests.cs | 131 +++++++++++++------ OnixLabs.Units/Temperature.Constants.cs | 3 + OnixLabs.Units/Temperature.From.cs | 7 + OnixLabs.Units/Temperature.To.cs | 3 +- OnixLabs.Units/Temperature.cs | 5 + 5 files changed, 105 insertions(+), 44 deletions(-) diff --git a/OnixLabs.Units.UnitTests/TemperatureTests.cs b/OnixLabs.Units.UnitTests/TemperatureTests.cs index 6ffb919..9718707 100644 --- a/OnixLabs.Units.UnitTests/TemperatureTests.cs +++ b/OnixLabs.Units.UnitTests/TemperatureTests.cs @@ -35,14 +35,15 @@ public void TemperatureZeroShouldProduceExpectedResult() 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)] - [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] - [InlineData(100.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] - [InlineData(-40.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] - [InlineData(20.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + [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, @@ -51,7 +52,8 @@ public void TemperatureFromCelsiusShouldProduceExpectedResult( double expectedDelisle, double expectedNewton, double expectedRankine, - double expectedReaumur) + double expectedReaumur, + double expectedRomer) { // When Temperature temperature = Temperature.FromCelsius(celsius); @@ -64,14 +66,15 @@ public void TemperatureFromCelsiusShouldProduceExpectedResult( 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)] - [InlineData(150.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] - [InlineData(0.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] - [InlineData(210.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] - [InlineData(120.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + [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, @@ -80,7 +83,8 @@ public void TemperatureFromDelisleShouldProduceExpectedResult( double expectedDelisle, double expectedNewton, double expectedRankine, - double expectedReaumur) + double expectedReaumur, + double expectedRomer) { // When Temperature temperature = Temperature.FromDelisle(delisle); @@ -93,14 +97,15 @@ public void TemperatureFromDelisleShouldProduceExpectedResult( 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)] - [InlineData(32.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] - [InlineData(212.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] - [InlineData(-40.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] - [InlineData(68.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + [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, @@ -109,7 +114,8 @@ public void TemperatureFromFahrenheitShouldProduceExpectedResult( double expectedDelisle, double expectedNewton, double expectedRankine, - double expectedReaumur) + double expectedReaumur, + double expectedRomer) { Temperature temperature = Temperature.FromFahrenheit(fahrenheit); @@ -120,14 +126,15 @@ public void TemperatureFromFahrenheitShouldProduceExpectedResult( 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)] - [InlineData(273.15, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] - [InlineData(373.15, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] - [InlineData(233.15, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] - [InlineData(293.15, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + [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, @@ -136,7 +143,8 @@ public void TemperatureFromKelvinShouldProduceExpectedResult( double expectedDelisle, double expectedNewton, double expectedRankine, - double expectedReaumur) + double expectedReaumur, + double expectedRomer) { Temperature temperature = Temperature.FromKelvin(kelvin); @@ -147,14 +155,15 @@ public void TemperatureFromKelvinShouldProduceExpectedResult( 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)] - [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] - [InlineData(33.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] - [InlineData(-13.2, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] - [InlineData(6.6, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + [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, @@ -163,7 +172,8 @@ public void TemperatureFromNewtonShouldProduceExpectedResult( double expectedDelisle, double expectedNewton, double expectedRankine, - double expectedReaumur) + double expectedReaumur, + double expectedRomer) { Temperature temperature = Temperature.FromNewton(newton); @@ -174,14 +184,15 @@ public void TemperatureFromNewtonShouldProduceExpectedResult( 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)] - [InlineData(491.67, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] - [InlineData(671.67, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] - [InlineData(419.67, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] - [InlineData(527.67, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + [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, @@ -190,7 +201,8 @@ public void TemperatureFromRankineShouldProduceExpectedResult( double expectedDelisle, double expectedNewton, double expectedRankine, - double expectedReaumur) + double expectedReaumur, + double expectedRomer) { Temperature temperature = Temperature.FromRankine(rankine); @@ -201,14 +213,15 @@ public void TemperatureFromRankineShouldProduceExpectedResult( 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)] - [InlineData(0.0, 0.0, 273.15, 32.0, 150.0, 0.0, 491.67, 0.0)] - [InlineData(80.0, 100.0, 373.15, 212.0, 0.0, 33.0, 671.67, 80.0)] - [InlineData(-32.0, -40.0, 233.15, -40.0, 210.0, -13.2, 419.67, -32.0)] - [InlineData(16.0, 20.0, 293.15, 68.0, 120.0, 6.6, 527.67, 16.0)] + [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, @@ -217,7 +230,8 @@ public void TemperatureFromReaumurShouldProduceExpectedResult( double expectedDelisle, double expectedNewton, double expectedRankine, - double expectedReaumur) + double expectedReaumur, + double expectedRomer) { Temperature temperature = Temperature.FromReaumur(reaumur); @@ -228,6 +242,36 @@ public void TemperatureFromReaumurShouldProduceExpectedResult( 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")] @@ -413,8 +457,9 @@ public void TemperatureToStringShouldProduceExpectedResult() Assert.Equal("0.000 °De", $"{temperature:DE}"); Assert.Equal("212.000 °F", $"{temperature:F}"); Assert.Equal("33.000 °N", $"{temperature:N}"); - Assert.Equal("80.000 °Ré", $"{temperature:RE}"); Assert.Equal("671.670 °R", $"{temperature:R}"); + Assert.Equal("80.000 °Ré", $"{temperature:RE}"); + Assert.Equal("60.000 °Rø", $"{temperature:RO}"); } [Fact(DisplayName = "Temperature.ToString should honor custom culture separators")] diff --git a/OnixLabs.Units/Temperature.Constants.cs b/OnixLabs.Units/Temperature.Constants.cs index 9e6e92c..21f5f4f 100644 --- a/OnixLabs.Units/Temperature.Constants.cs +++ b/OnixLabs.Units/Temperature.Constants.cs @@ -41,4 +41,7 @@ public readonly partial struct Temperature 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.From.cs b/OnixLabs.Units/Temperature.From.cs index c3115fa..d705164 100644 --- a/OnixLabs.Units/Temperature.From.cs +++ b/OnixLabs.Units/Temperature.From.cs @@ -64,4 +64,11 @@ public readonly partial struct Temperature /// The value from which to construct the new instance. /// A newly created instance. public static Temperature FromReaumur(T value) => new(value * T.CreateChecked(1.25) + T.CreateChecked(273.15)); + + /// + /// Creates a new instance from a Rømer value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + 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 index 6a49c7d..593e71a 100644 --- a/OnixLabs.Units/Temperature.To.cs +++ b/OnixLabs.Units/Temperature.To.cs @@ -52,7 +52,8 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide NewtonSpecifier => (Newton, NewtonSymbol), RankineSpecifier => (Rankine, RankineSymbol), ReaumurSpecifier => (Reaumur, ReaumurSymbol), - _ => throw new ArgumentException($"Format '{format}' is invalid. Valid format specifiers are: C, De, F, K, N, Re, and R. Format specifiers may also be suffixed with a scale value.", nameof(format)) + RomerSpecifier => (Romer, RomerSymbol), + _ => throw new ArgumentException($"Format '{format}' is invalid. Valid format specifiers are: C, De, F, K, N, R, Re, and Ro. Format specifiers may also be suffixed with a scale value.", nameof(format)) }; T rounded = scale > 0 ? T.Round(value, scale) : value; diff --git a/OnixLabs.Units/Temperature.cs b/OnixLabs.Units/Temperature.cs index 98445af..70bb4f2 100644 --- a/OnixLabs.Units/Temperature.cs +++ b/OnixLabs.Units/Temperature.cs @@ -68,4 +68,9 @@ namespace OnixLabs.Units; /// Gets the temperature in Réaumur. /// public T Reaumur => (Kelvin - T.CreateChecked(273.15)) * T.CreateChecked(0.80); + + /// + /// Gets the temperature in Rømer. + /// + public T Romer => (Kelvin - T.CreateChecked(273.15)) * T.CreateChecked(21.0 / 40.0) + T.CreateChecked(7.5); } From c88583e48f9e7536fe3858bc80409aaa8be19832 Mon Sep 17 00:00:00 2001 From: Matthew Layton Date: Sat, 6 Sep 2025 12:38:21 +0100 Subject: [PATCH 04/23] Added Distance implementation, and unit tests. --- OnixLabs.Units.UnitTests/DistanceTests.cs | 665 ++++++++++++++++++ .../OnixLabs.Units.UnitTests.csproj | 24 +- .../DataSize.Arithmetic.Addition.cs | 1 + .../DataSize.Arithmetic.Division.cs | 1 + .../DataSize.Arithmetic.Multiplication.cs | 1 + .../DataSize.Arithmetic.Subtraction.cs | 1 + OnixLabs.Units/DataSize.To.cs | 6 +- OnixLabs.Units/DataSize.cs | 1 + .../Distance.Arithmetic.Addition.cs | 42 ++ .../Distance.Arithmetic.Division.cs | 42 ++ .../Distance.Arithmetic.Multiplication.cs | 42 ++ .../Distance.Arithmetic.Subtraction.cs | 42 ++ OnixLabs.Units/Distance.Comparable.cs | 80 +++ OnixLabs.Units/Distance.Constants.cs | 60 ++ OnixLabs.Units/Distance.Equatable.cs | 64 ++ OnixLabs.Units/Distance.Format.cs | 32 + OnixLabs.Units/Distance.From.cs | 268 +++++++ OnixLabs.Units/Distance.To.cs | 97 +++ OnixLabs.Units/Distance.cs | 216 ++++++ .../Temperature.Arithmetic.Addition.cs | 1 + .../Temperature.Arithmetic.Division.cs | 1 + .../Temperature.Arithmetic.Multiplication.cs | 1 + .../Temperature.Arithmetic.Subtraction.cs | 1 + OnixLabs.Units/Temperature.From.cs | 16 +- OnixLabs.Units/Temperature.To.cs | 7 +- OnixLabs.Units/Temperature.cs | 1 + onixlabs-dotnet.sln.DotSettings | 19 +- 27 files changed, 1695 insertions(+), 37 deletions(-) create mode 100644 OnixLabs.Units.UnitTests/DistanceTests.cs create mode 100644 OnixLabs.Units/Distance.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Distance.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Distance.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Distance.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Distance.Comparable.cs create mode 100644 OnixLabs.Units/Distance.Constants.cs create mode 100644 OnixLabs.Units/Distance.Equatable.cs create mode 100644 OnixLabs.Units/Distance.Format.cs create mode 100644 OnixLabs.Units/Distance.From.cs create mode 100644 OnixLabs.Units/Distance.To.cs create mode 100644 OnixLabs.Units/Distance.cs diff --git a/OnixLabs.Units.UnitTests/DistanceTests.cs b/OnixLabs.Units.UnitTests/DistanceTests.cs new file mode 100644 index 0000000..78cd326 --- /dev/null +++ b/OnixLabs.Units.UnitTests/DistanceTests.cs @@ -0,0 +1,665 @@ +// 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 index a79746a..a828a8f 100644 --- a/OnixLabs.Units.UnitTests/OnixLabs.Units.UnitTests.csproj +++ b/OnixLabs.Units.UnitTests/OnixLabs.Units.UnitTests.csproj @@ -1,26 +1,6 @@  - - - net9.0 - enable - enable - false - - - - - - + + - - - - - - - - - - diff --git a/OnixLabs.Units/DataSize.Arithmetic.Addition.cs b/OnixLabs.Units/DataSize.Arithmetic.Addition.cs index 64e8bdb..0f33099 100755 --- a/OnixLabs.Units/DataSize.Arithmetic.Addition.cs +++ b/OnixLabs.Units/DataSize.Arithmetic.Addition.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct DataSize { /// diff --git a/OnixLabs.Units/DataSize.Arithmetic.Division.cs b/OnixLabs.Units/DataSize.Arithmetic.Division.cs index c1d5eee..a948162 100755 --- a/OnixLabs.Units/DataSize.Arithmetic.Division.cs +++ b/OnixLabs.Units/DataSize.Arithmetic.Division.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct DataSize { /// diff --git a/OnixLabs.Units/DataSize.Arithmetic.Multiplication.cs b/OnixLabs.Units/DataSize.Arithmetic.Multiplication.cs index 10ad558..144a5cd 100755 --- a/OnixLabs.Units/DataSize.Arithmetic.Multiplication.cs +++ b/OnixLabs.Units/DataSize.Arithmetic.Multiplication.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct DataSize { /// diff --git a/OnixLabs.Units/DataSize.Arithmetic.Subtraction.cs b/OnixLabs.Units/DataSize.Arithmetic.Subtraction.cs index e80ed27..aacface 100755 --- a/OnixLabs.Units/DataSize.Arithmetic.Subtraction.cs +++ b/OnixLabs.Units/DataSize.Arithmetic.Subtraction.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct DataSize { /// diff --git a/OnixLabs.Units/DataSize.To.cs b/OnixLabs.Units/DataSize.To.cs index 40758e3..d9e5e01 100755 --- a/OnixLabs.Units/DataSize.To.cs +++ b/OnixLabs.Units/DataSize.To.cs @@ -22,7 +22,7 @@ public readonly partial struct DataSize /// /// Formats the value of the current instance using the default format. /// - /// The value of the current instance in the default format. + /// Returns the value of the current instance in the default format. public override string ToString() => ToString(BitsSpecifier); /// @@ -30,7 +30,7 @@ public readonly partial struct DataSize /// /// The format to use, or null to use the default format. /// The provider to use to format the value. - /// The value of the current instance in the specified format. + /// Returns the value of the current instance in the specified format. public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); /// @@ -38,7 +38,7 @@ public readonly partial struct DataSize /// /// The format to use, or null to use the default format. /// The provider to use to format the value. - /// The value of the current instance in the specified format. + /// Returns the value of the current instance in the specified format. public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) { (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: BitsSpecifier); diff --git a/OnixLabs.Units/DataSize.cs b/OnixLabs.Units/DataSize.cs index df0eb07..fdeee83 100755 --- a/OnixLabs.Units/DataSize.cs +++ b/OnixLabs.Units/DataSize.cs @@ -22,6 +22,7 @@ namespace OnixLabs.Units; /// Represents a unit of data size. /// /// The underlying floating point value. +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct DataSize : IValueEquatable>, IValueComparable>, diff --git a/OnixLabs.Units/Distance.Arithmetic.Addition.cs b/OnixLabs.Units/Distance.Arithmetic.Addition.cs new file mode 100644 index 0000000..3a05a9c --- /dev/null +++ b/OnixLabs.Units/Distance.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Distance +{ + /// + /// 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 Distance Add(Distance left, Distance right) => new(left.QuectoMeters + right.QuectoMeters); + + /// + /// 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 Distance operator +(Distance left, Distance right) => Add(left, right); + + /// + /// 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 Distance Add(Distance other) => Add(this, other); +} diff --git a/OnixLabs.Units/Distance.Arithmetic.Division.cs b/OnixLabs.Units/Distance.Arithmetic.Division.cs new file mode 100644 index 0000000..123ce2f --- /dev/null +++ b/OnixLabs.Units/Distance.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Distance +{ + /// + /// 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 Distance Divide(Distance left, Distance right) => new(left.QuectoMeters / right.QuectoMeters); + + /// + /// 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 Distance operator /(Distance left, Distance right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value, divided by the specified other value. + public Distance Divide(Distance other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Distance.Arithmetic.Multiplication.cs b/OnixLabs.Units/Distance.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..f501eb8 --- /dev/null +++ b/OnixLabs.Units/Distance.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Distance +{ + /// + /// 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 Distance Multiply(Distance left, Distance right) => new(left.QuectoMeters * right.QuectoMeters); + + /// + /// 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 Distance operator *(Distance left, Distance right) => Multiply(left, right); + + /// + /// Computes the product of the current value, multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value, multiplied by the specified other value. + public Distance Multiply(Distance other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Distance.Arithmetic.Subtraction.cs b/OnixLabs.Units/Distance.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..8b6eca3 --- /dev/null +++ b/OnixLabs.Units/Distance.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Distance +{ + /// + /// 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 Distance Subtract(Distance left, Distance right) => new(left.QuectoMeters - right.QuectoMeters); + + /// + /// 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 Distance operator -(Distance left, Distance right) => Subtract(left, right); + + /// + /// Computes the difference of the specified other value, subtracted from the current value. + /// + /// The value to subtract from the current value. + /// Returns the difference of the specified other value, subtracted from the current value. + public Distance Subtract(Distance other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Distance.Comparable.cs b/OnixLabs.Units/Distance.Comparable.cs new file mode 100644 index 0000000..f956236 --- /dev/null +++ b/OnixLabs.Units/Distance.Comparable.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 OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(Distance left, Distance right) => left.QuectoMeters.CompareTo(right.QuectoMeters); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(Distance other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than right-hand operand; otherwise, . + public static bool operator >(Distance left, Distance right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(Distance left, Distance right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(Distance left, Distance right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(Distance left, Distance right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Distance.Constants.cs b/OnixLabs.Units/Distance.Constants.cs new file mode 100644 index 0000000..8e7e932 --- /dev/null +++ b/OnixLabs.Units/Distance.Constants.cs @@ -0,0 +1,60 @@ +// 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 +{ + /// + /// Gets a zero 0 value. + /// + public static readonly 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..1fd5ec0 --- /dev/null +++ b/OnixLabs.Units/Distance.Equatable.cs @@ -0,0 +1,64 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct Distance +{ + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(Distance left, Distance right) => Equals(left.QuectoMeters, right.QuectoMeters); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// Returns if the current instance is equal to the specified other instance; otherwise, . + public bool Equals(Distance other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Distance other && Equals(other); + + /// + /// Serves as a hash code function for this instance. + /// + /// A hash code for this instance. + public override int GetHashCode() => QuectoMeters.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(Distance left, Distance right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(Distance left, Distance right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Distance.Format.cs b/OnixLabs.Units/Distance.Format.cs new file mode 100644 index 0000000..9fbb9a0 --- /dev/null +++ b/OnixLabs.Units/Distance.Format.cs @@ -0,0 +1,32 @@ +// 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 +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + 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..a370887 --- /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 a Quectometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromQuectometers(T value) => new(value); + + /// + /// Creates a new instance from a Rontometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromRontometers(T value) => new(value * T.CreateChecked(1e3)); + + /// + /// Creates a new instance from a Yoctometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromYoctometers(T value) => new(value * T.CreateChecked(1e6)); + + /// + /// Creates a new instance from a Zeptometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromZeptometers(T value) => new(value * T.CreateChecked(1e9)); + + /// + /// Creates a new instance from a Attometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromAttometers(T value) => new(value * T.CreateChecked(1e12)); + + /// + /// Creates a new instance from a Femtometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromFemtometers(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from a Picometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromPicometers(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from a Nanometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromNanometers(T value) => new(value * T.CreateChecked(1e21)); + + /// + /// Creates a new instance from a Micrometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromMicrometers(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from a Millimeters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromMillimeters(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from a Centimeters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromCentimeters(T value) => new(value * T.CreateChecked(1e28)); + + /// + /// Creates a new instance from a Decimeters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromDecimeters(T value) => new(value * T.CreateChecked(1e29)); + + /// + /// Creates a new instance from a Meters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromMeters(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from a Decameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromDecameters(T value) => new(value * T.CreateChecked(1e31)); + + /// + /// Creates a new instance from a Hectometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromHectometers(T value) => new(value * T.CreateChecked(1e32)); + + /// + /// Creates a new instance from a Kilometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromKilometers(T value) => new(value * T.CreateChecked(1e33)); + + /// + /// Creates a new instance from a Quectometers value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromMegameters(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from a Gigameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromGigameters(T value) => new(value * T.CreateChecked(1e39)); + + /// + /// Creates a new instance from a Terameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromTerameters(T value) => new(value * T.CreateChecked(1e42)); + + /// + /// Creates a new instance from a Petameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromPetameters(T value) => new(value * T.CreateChecked(1e45)); + + /// + /// Creates a new instance from a Exameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromExameters(T value) => new(value * T.CreateChecked(1e48)); + + /// + /// Creates a new instance from a Zettameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromZettameters(T value) => new(value * T.CreateChecked(1e51)); + + /// + /// Creates a new instance from a Yottameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromYottameters(T value) => new(value * T.CreateChecked(1e54)); + + /// + /// Creates a new instance from a Ronnameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromRonnameters(T value) => new(value * T.CreateChecked(1e57)); + + /// + /// Creates a new instance from a Quettameters value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromQuettameters(T value) => new(value * T.CreateChecked(1e60)); + + /// + /// Creates a new instance from a Inches value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromInches(T value) => new(value * T.CreateChecked(2.54e28)); + + /// + /// Creates a new instance from a Feet value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromFeet(T value) => new(value * T.CreateChecked(3.048e29)); + + /// + /// Creates a new instance from a Yards value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromYards(T value) => new(value * T.CreateChecked(9.144e29)); + + /// + /// Creates a new instance from a Miles value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromMiles(T value) => new(value * T.CreateChecked(1.609344e33)); + + /// + /// Creates a new instance from a Nautical Miles value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromNauticalMiles(T value) => new(value * T.CreateChecked(1.852e33)); + + /// + /// Creates a new instance from a Fermis value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromFermis(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from an Angstroms value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromAngstroms(T value) => new(value * T.CreateChecked(1e20)); + + /// + /// Creates a new instance from a Astronomical Units value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromAstronomicalUnits(T value) => new(value * T.CreateChecked(1.495978707e41)); + + /// + /// Creates a new instance from a Light Years value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + public static Distance FromLightYears(T value) => new(value * T.CreateChecked(9.4607304725808e45)); + + /// + /// Creates a new instance from a Parsecs value. + /// + /// The value from which to construct the new instance. + /// A newly created instance. + 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..b5c5165 --- /dev/null +++ b/OnixLabs.Units/Distance.To.cs @@ -0,0 +1,97 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Distance +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(QuectoMetersSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: MetersSpecifier); + + 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 new ArgumentException( + $"Format '{format.ToString()}' is invalid. " + + "Valid format specifiers are: " + + "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, pc. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + 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..e781875 --- /dev/null +++ b/OnixLabs.Units/Distance.cs @@ -0,0 +1,216 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of distance. +/// +/// The underlying floating point value. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Distance : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + private Distance(T value) => QuectoMeters = value; + + /// + /// Gets the distance in Quectometers. + /// + public T QuectoMeters { get; } + + /// + /// Gets the distance in Rontometers. + /// + public T RontoMeters => QuectoMeters / T.CreateChecked(1e3); + + /// + /// Gets the distance in Yoctometers. + /// + public T YoctoMeters => QuectoMeters / T.CreateChecked(1e6); + + /// + /// Gets the distance in Zeptometers. + /// + public T ZeptoMeters => QuectoMeters / T.CreateChecked(1e9); + + /// + /// Gets the distance in Attometers. + /// + public T AttoMeters => QuectoMeters / T.CreateChecked(1e12); + + /// + /// Gets the distance in Femtometers. + /// + public T FemtoMeters => QuectoMeters / T.CreateChecked(1e15); + + /// + /// Gets the distance in Picometers. + /// + public T PicoMeters => QuectoMeters / T.CreateChecked(1e18); + + /// + /// Gets the distance in Nanometers. + /// + public T NanoMeters => QuectoMeters / T.CreateChecked(1e21); + + /// + /// Gets the distance in Micrometers. + /// + public T MicroMeters => QuectoMeters / T.CreateChecked(1e24); + + /// + /// Gets the distance in Millimeters. + /// + public T MilliMeters => QuectoMeters / T.CreateChecked(1e27); + + /// + /// Gets the distance in Centimeters. + /// + public T CentiMeters => QuectoMeters / T.CreateChecked(1e28); + + /// + /// Gets the distance in Decimeters. + /// + public T DeciMeters => QuectoMeters / T.CreateChecked(1e29); + + /// + /// Gets the distance in Meters. + /// + public T Meters => QuectoMeters / T.CreateChecked(1e30); + + /// + /// Gets the distance in Decameters. + /// + public T DecaMeters => QuectoMeters / T.CreateChecked(1e31); + + /// + /// Gets the distance in Hectometers. + /// + public T HectoMeters => QuectoMeters / T.CreateChecked(1e32); + + /// + /// Gets the distance in Kilometers. + /// + public T KiloMeters => QuectoMeters / T.CreateChecked(1e33); + + /// + /// Gets the distance in Megameters. + /// + public T MegaMeters => QuectoMeters / T.CreateChecked(1e36); + + /// + /// Gets the distance in Gigameters. + /// + public T GigaMeters => QuectoMeters / T.CreateChecked(1e39); + + /// + /// Gets the distance in Terameters. + /// + public T TeraMeters => QuectoMeters / T.CreateChecked(1e42); + + /// + /// Gets the distance in Petameters. + /// + public T PetaMeters => QuectoMeters / T.CreateChecked(1e45); + + /// + /// Gets the distance in Exameters. + /// + public T ExaMeters => QuectoMeters / T.CreateChecked(1e48); + + /// + /// Gets the distance in Zettameters. + /// + public T ZettaMeters => QuectoMeters / T.CreateChecked(1e51); + + /// + /// Gets the distance in Yottameters. + /// + public T YottaMeters => QuectoMeters / T.CreateChecked(1e54); + + /// + /// Gets the distance in Ronnameters. + /// + public T RonnaMeters => QuectoMeters / T.CreateChecked(1e57); + + /// + /// Gets the distance in Quettameters. + /// + public T QuettaMeters => QuectoMeters / T.CreateChecked(1e60); + + /// + /// Gets the distance in Inches. + /// + public T Inches => QuectoMeters / T.CreateChecked(0.0254e30); + + /// + /// Gets the distance in Feet. + /// + public T Feet => QuectoMeters / T.CreateChecked(0.3048e30); + + /// + /// Gets the distance in Yards. + /// + public T Yards => QuectoMeters / T.CreateChecked(0.9144e30); + + /// + /// Gets the distance in Miles. + /// + public T Miles => QuectoMeters / T.CreateChecked(1609.344e30); + + /// + /// Gets the distance in Nautical Miles. + /// + public T NauticalMiles => QuectoMeters / T.CreateChecked(1852e30); + + /// + /// Gets the distance in Nautical Fermis. + /// + public T Fermis => QuectoMeters / T.CreateChecked(1e15); + + /// + /// Gets the distance in Nautical Angstroms. + /// + public T Angstroms => QuectoMeters / T.CreateChecked(1e20); + + /// + /// Gets the distance in Astronomical Units. + /// + public T AstronomicalUnits => QuectoMeters / T.CreateChecked(149_597_870_700L * 1e30); + + /// + /// Gets the distance in Light Years. + /// + public T LightYears => QuectoMeters / T.CreateChecked(9_460_730_472_580_800L * 1e30); + + /// + /// Gets the distance in Parsecs. + /// + 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/Temperature.Arithmetic.Addition.cs b/OnixLabs.Units/Temperature.Arithmetic.Addition.cs index a2c5f9c..3414402 100644 --- a/OnixLabs.Units/Temperature.Arithmetic.Addition.cs +++ b/OnixLabs.Units/Temperature.Arithmetic.Addition.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct Temperature { /// diff --git a/OnixLabs.Units/Temperature.Arithmetic.Division.cs b/OnixLabs.Units/Temperature.Arithmetic.Division.cs index 0071c93..cda17cd 100644 --- a/OnixLabs.Units/Temperature.Arithmetic.Division.cs +++ b/OnixLabs.Units/Temperature.Arithmetic.Division.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct Temperature { /// diff --git a/OnixLabs.Units/Temperature.Arithmetic.Multiplication.cs b/OnixLabs.Units/Temperature.Arithmetic.Multiplication.cs index 9d8355a..f0d74ae 100644 --- a/OnixLabs.Units/Temperature.Arithmetic.Multiplication.cs +++ b/OnixLabs.Units/Temperature.Arithmetic.Multiplication.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct Temperature { /// diff --git a/OnixLabs.Units/Temperature.Arithmetic.Subtraction.cs b/OnixLabs.Units/Temperature.Arithmetic.Subtraction.cs index 81fcae4..a9ecb1c 100644 --- a/OnixLabs.Units/Temperature.Arithmetic.Subtraction.cs +++ b/OnixLabs.Units/Temperature.Arithmetic.Subtraction.cs @@ -14,6 +14,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct Temperature { /// diff --git a/OnixLabs.Units/Temperature.From.cs b/OnixLabs.Units/Temperature.From.cs index d705164..2994874 100644 --- a/OnixLabs.Units/Temperature.From.cs +++ b/OnixLabs.Units/Temperature.From.cs @@ -20,55 +20,55 @@ public readonly partial struct Temperature /// Creates a new instance from a Celsius value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a newly created instance. public static Temperature FromCelsius(T value) => new(value + T.CreateChecked(273.15)); /// /// Creates a new instance from a Delisle value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a newly created instance. 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 a Fahrenheit value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a newly created instance. public static Temperature FromFahrenheit(T value) => new((value + T.CreateChecked(459.67)) / T.CreateChecked(1.80)); /// /// Creates a new instance from a Kelvin value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a newly created instance. public static Temperature FromKelvin(T value) => new(value); /// /// Creates a new instance from a Newton value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a newly created instance. 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 a Rankine value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a newly created instance. public static Temperature FromRankine(T value) => new(value / T.CreateChecked(1.8)); /// /// Creates a new instance from a Réaumur value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a newly created instance. public static Temperature FromReaumur(T value) => new(value * T.CreateChecked(1.25) + T.CreateChecked(273.15)); /// /// Creates a new instance from a Rømer value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a newly created instance. 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 index 593e71a..47aa77e 100644 --- a/OnixLabs.Units/Temperature.To.cs +++ b/OnixLabs.Units/Temperature.To.cs @@ -17,12 +17,13 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct Temperature { /// /// Formats the value of the current instance using the default format. /// - /// The value of the current instance in the default format. + /// Returns the value of the current instance in the default format. public override string ToString() => ToString(KelvinSpecifier); /// @@ -30,7 +31,7 @@ public readonly partial struct Temperature /// /// The format to use, or null to use the default format. /// The provider to use to format the value. - /// The value of the current instance in the specified format. + /// Returns the value of the current instance in the specified format. public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); /// @@ -38,7 +39,7 @@ public readonly partial struct Temperature /// /// The format to use, or null to use the default format. /// The provider to use to format the value. - /// The value of the current instance in the specified format. + /// Returns the value of the current instance in the specified format. public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) { (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: KelvinSpecifier); diff --git a/OnixLabs.Units/Temperature.cs b/OnixLabs.Units/Temperature.cs index 70bb4f2..60e7722 100644 --- a/OnixLabs.Units/Temperature.cs +++ b/OnixLabs.Units/Temperature.cs @@ -22,6 +22,7 @@ namespace OnixLabs.Units; /// Represents a unit of temperature. The default value is absolute zero, or 0 K. /// /// The underlying floating point value. +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct Temperature : IValueEquatable>, IValueComparable>, diff --git a/onixlabs-dotnet.sln.DotSettings b/onixlabs-dotnet.sln.DotSettings index 68bf0bd..60f2b83 100644 --- a/onixlabs-dotnet.sln.DotSettings +++ b/onixlabs-dotnet.sln.DotSettings @@ -13,16 +13,21 @@ 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 True True @@ -51,27 +56,39 @@ 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 - True \ No newline at end of file + True + True \ No newline at end of file From 344e9742485e668f276bfbbe6455bf07053ecccd Mon Sep 17 00:00:00 2001 From: matthew Date: Sun, 16 Nov 2025 10:30:25 +0000 Subject: [PATCH 05/23] Converted extension method to extension block. --- OnixLabs.Units/Extensions.ReadOnlySpan.cs | 43 ++++++++++++----------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/OnixLabs.Units/Extensions.ReadOnlySpan.cs b/OnixLabs.Units/Extensions.ReadOnlySpan.cs index b6745ad..9643c46 100644 --- a/OnixLabs.Units/Extensions.ReadOnlySpan.cs +++ b/OnixLabs.Units/Extensions.ReadOnlySpan.cs @@ -19,35 +19,38 @@ namespace OnixLabs.Units; internal static class ReadOnlySpanExtensions { - public static (string specifier, int scale) GetSpecifierAndScale(this ReadOnlySpan format, string defaultSpecifier) + extension(ReadOnlySpan format) { - const char plus = '+'; - const char minus = '-'; + public (string specifier, int scale) GetSpecifierAndScale(string defaultSpecifier) + { + const char plus = '+'; + const char minus = '-'; - int defaultScale = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalDigits; + int defaultScale = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalDigits; - if (format.IsEmpty) - return (defaultSpecifier, defaultScale); + if (format.IsEmpty) + return (defaultSpecifier, defaultScale); - int index = 0; + int index = 0; - while (index < format.Length && char.IsLetter(format[index])) - index++; + while (index < format.Length && char.IsLetter(format[index])) + index++; - if (index == 0) - throw new FormatException("Format must start with a letter specifier."); + if (index == 0) + throw new FormatException("Format must start with a letter specifier."); - string specifier = new(format[..index]); - ReadOnlySpan scaleCharacters = format[index..]; + string specifier = new(format[..index]); + ReadOnlySpan scaleCharacters = format[index..]; - if (scaleCharacters.IsEmpty) - return (specifier, defaultScale); + 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."); + 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.None, CultureInfo.InvariantCulture, out int scale) - ? (specifier, scale) - : throw new FormatException("Scale must contain only decimal digits."); + return int.TryParse(scaleCharacters, NumberStyles.None, CultureInfo.InvariantCulture, out int scale) + ? (specifier, scale) + : throw new FormatException("Scale must contain only decimal digits."); + } } } From 3ba170468e854e919fbc2b522b64fb0bd33496bc Mon Sep 17 00:00:00 2001 From: matthew Date: Sun, 16 Nov 2025 10:42:27 +0000 Subject: [PATCH 06/23] Removed verbosity flag from GitHub Action workflow. --- .github/workflows/dotnet.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 9975a7d..794d0ab 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -29,8 +29,8 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test (.NET 8.0) - run: dotnet test --no-build --framework net8.0 --verbosity quiet + run: dotnet test --no-build --framework net8.0 - name: Test (.NET 9.0) - run: dotnet test --no-build --framework net9.0 --verbosity quiet + run: dotnet test --no-build --framework net9.0 - name: Test (.NET 10.0) - run: dotnet test --no-build --framework net10.0 --verbosity quiet + run: dotnet test --no-build --framework net10.0 From 399d780c9946b8c531bd9ed27adec4ad2ac8ccfe Mon Sep 17 00:00:00 2001 From: matthew Date: Sun, 16 Nov 2025 10:55:28 +0000 Subject: [PATCH 07/23] Added verbosity back in, but made it detailed to try and sniff out the failing tests. --- .github/workflows/dotnet.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 794d0ab..72b4d51 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -29,8 +29,8 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test (.NET 8.0) - run: dotnet test --no-build --framework net8.0 + run: dotnet test --no-build --framework net8.0 --verbosity detailed - name: Test (.NET 9.0) - run: dotnet test --no-build --framework net9.0 + run: dotnet test --no-build --framework net9.0 --verbosity detailed - name: Test (.NET 10.0) - run: dotnet test --no-build --framework net10.0 + run: dotnet test --no-build --framework net10.0 --verbosity detailed From 282485163061dd3c34c0e27802e5dbfdb1ba13cb Mon Sep 17 00:00:00 2001 From: matthew Date: Sun, 16 Nov 2025 11:02:28 +0000 Subject: [PATCH 08/23] GitHub Action updated to include detailed logging in the console. --- .github/workflows/dotnet.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 72b4d51..6fd1db3 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -29,8 +29,11 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test (.NET 8.0) - run: dotnet test --no-build --framework net8.0 --verbosity detailed + run: dotnet test --no-build --framework net8.0 \ + --logger "console;verbosity=detailed" - name: Test (.NET 9.0) - run: dotnet test --no-build --framework net9.0 --verbosity detailed + run: dotnet test --no-build --framework net9.0 \ + --logger "console;verbosity=detailed" - name: Test (.NET 10.0) - run: dotnet test --no-build --framework net10.0 --verbosity detailed + run: dotnet test --no-build --framework net10.0 \ + --logger "console;verbosity=detailed" From 9f7d5d5be4bc6ad847cb65ca50a3d957ebe9c053 Mon Sep 17 00:00:00 2001 From: matthew Date: Sun, 16 Nov 2025 11:11:13 +0000 Subject: [PATCH 09/23] GitHub Action updated to include detailed output. --- .github/workflows/dotnet.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 6fd1db3..e5783f7 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -29,11 +29,8 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test (.NET 8.0) - run: dotnet test --no-build --framework net8.0 \ - --logger "console;verbosity=detailed" + run: dotnet test --no-build --framework net8.0 --verbosity detailed --output detailed - name: Test (.NET 9.0) - run: dotnet test --no-build --framework net9.0 \ - --logger "console;verbosity=detailed" + run: dotnet test --no-build --framework net9.0 --verbosity detailed --output detailed - name: Test (.NET 10.0) - run: dotnet test --no-build --framework net10.0 \ - --logger "console;verbosity=detailed" + run: dotnet test --no-build --framework net10.0 --verbosity detailed --output detailed From 96a24d01a0e8e4f8035ed94ead53c9b32066b083 Mon Sep 17 00:00:00 2001 From: matthew Date: Sun, 16 Nov 2025 11:47:20 +0000 Subject: [PATCH 10/23] GitHub Actions workflow updated again in an attempt to reduce log size, but still show detailed errors. --- .github/workflows/dotnet.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index e5783f7..a46d563 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -29,8 +29,8 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test (.NET 8.0) - run: dotnet test --no-build --framework net8.0 --verbosity detailed --output detailed + run: dotnet test --no-build --output Normal --no-progress --framework net8.0 - name: Test (.NET 9.0) - run: dotnet test --no-build --framework net9.0 --verbosity detailed --output detailed + run: dotnet test --no-build --output Normal --no-progress --framework net9.0 - name: Test (.NET 10.0) - run: dotnet test --no-build --framework net10.0 --verbosity detailed --output detailed + run: dotnet test --no-build --output Normal --no-progress --framework net10.0 From 7ac67741cc9aed8180f78e475847e6600cec486d Mon Sep 17 00:00:00 2001 From: matthew Date: Sun, 16 Nov 2025 12:09:24 +0000 Subject: [PATCH 11/23] Attempt to fix failing test. --- OnixLabs.Units.UnitTests/TemperatureTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OnixLabs.Units.UnitTests/TemperatureTests.cs b/OnixLabs.Units.UnitTests/TemperatureTests.cs index 9718707..71ade4c 100644 --- a/OnixLabs.Units.UnitTests/TemperatureTests.cs +++ b/OnixLabs.Units.UnitTests/TemperatureTests.cs @@ -452,14 +452,14 @@ public void TemperatureToStringShouldProduceExpectedResult() Temperature temperature = Temperature.FromCelsius(100.0); // Then - Assert.Equal("373.150 K", $"{temperature:K}"); - Assert.Equal("100.000 °C", $"{temperature:C}"); - Assert.Equal("0.000 °De", $"{temperature:DE}"); - Assert.Equal("212.000 °F", $"{temperature:F}"); - Assert.Equal("33.000 °N", $"{temperature:N}"); - Assert.Equal("671.670 °R", $"{temperature:R}"); - Assert.Equal("80.000 °Ré", $"{temperature:RE}"); - Assert.Equal("60.000 °Rø", $"{temperature:RO}"); + 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")] From b838b8659632699b9e41f5ab9402288c15acdf77 Mon Sep 17 00:00:00 2001 From: matthew Date: Mon, 17 Nov 2025 23:12:11 +0000 Subject: [PATCH 12/23] Added Mass unit to Units library, and updated xmldocs for consistency. --- OnixLabs.Units/DataSize.Equatable.cs | 2 +- OnixLabs.Units/DataSize.From.cs | 204 ++++++++-------- OnixLabs.Units/DataSize.To.cs | 1 + OnixLabs.Units/DataSize.cs | 74 +++--- OnixLabs.Units/Distance.Equatable.cs | 2 +- OnixLabs.Units/Distance.From.cs | 140 +++++------ OnixLabs.Units/Distance.To.cs | 2 +- OnixLabs.Units/Distance.cs | 76 +++--- OnixLabs.Units/Mass.Arithmetic.Addition.cs | 42 ++++ OnixLabs.Units/Mass.Arithmetic.Division.cs | 42 ++++ .../Mass.Arithmetic.Multiplication.cs | 42 ++++ OnixLabs.Units/Mass.Arithmetic.Subtraction.cs | 42 ++++ OnixLabs.Units/Mass.Comparable.cs | 81 +++++++ OnixLabs.Units/Mass.Constants.cs | 54 +++++ OnixLabs.Units/Mass.Equatable.cs | 65 +++++ OnixLabs.Units/Mass.Format.cs | 32 +++ OnixLabs.Units/Mass.From.cs | 229 ++++++++++++++++++ OnixLabs.Units/Mass.To.cs | 92 +++++++ OnixLabs.Units/Mass.cs | 190 +++++++++++++++ OnixLabs.Units/Temperature.Equatable.cs | 2 +- OnixLabs.Units/Temperature.From.cs | 32 +-- OnixLabs.Units/Temperature.cs | 27 ++- 22 files changed, 1198 insertions(+), 275 deletions(-) create mode 100644 OnixLabs.Units/Mass.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Mass.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Mass.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Mass.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Mass.Comparable.cs create mode 100644 OnixLabs.Units/Mass.Constants.cs create mode 100644 OnixLabs.Units/Mass.Equatable.cs create mode 100644 OnixLabs.Units/Mass.Format.cs create mode 100644 OnixLabs.Units/Mass.From.cs create mode 100644 OnixLabs.Units/Mass.To.cs create mode 100644 OnixLabs.Units/Mass.cs diff --git a/OnixLabs.Units/DataSize.Equatable.cs b/OnixLabs.Units/DataSize.Equatable.cs index 186edfa..d686ced 100755 --- a/OnixLabs.Units/DataSize.Equatable.cs +++ b/OnixLabs.Units/DataSize.Equatable.cs @@ -43,7 +43,7 @@ public readonly partial struct DataSize /// /// Serves as a hash code function for this instance. /// - /// A hash code for this instance. + /// Returns a hash code for this instance. public override int GetHashCode() => Bits.GetHashCode(); /// diff --git a/OnixLabs.Units/DataSize.From.cs b/OnixLabs.Units/DataSize.From.cs index d1071c0..4fc4b3c 100755 --- a/OnixLabs.Units/DataSize.From.cs +++ b/OnixLabs.Units/DataSize.From.cs @@ -19,241 +19,241 @@ namespace OnixLabs.Units; public readonly partial struct DataSize { /// - /// Creates a new instance from a Bits value. + /// Creates a new instance from the specified Bits value. /// - /// The value to convert from bits. - /// Returns a new instance from a 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 a Bytes value. + /// Creates a new instance from the specified Bytes value. /// - /// The value to convert from bytes. - /// Returns a new instance from a 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 a KibiBits value (IEC, 2^10). + /// Creates a new instance from the specified KibiBits value. /// - /// The value to convert from kibibits. - /// Returns a new instance from a 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 a KibiBytes value (IEC, 2^10). + /// Creates a new instance from the specified KibiBytes value. /// - /// The value to convert from kibibytes. - /// Returns a new instance from a 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 a KiloBits value (SI, 10^3). + /// Creates a new instance from the specified KiloBits value. /// - /// The value to convert from kilobits. - /// Returns a new instance from a 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 a KiloBytes value (SI, 10^3). + /// Creates a new instance from the specified KiloBytes value. /// - /// The value to convert from kilobytes. - /// Returns a new instance from a 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 a MebiBits value (IEC, 2^20). + /// Creates a new instance from the specified MebiBits value. /// - /// The value to convert from mebibits. - /// Returns a new instance from a 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 a MebiBytes value (IEC, 2^20). + /// Creates a new instance from the specified MebiBytes value. /// - /// The value to convert from mebibytes. - /// Returns a new instance from a 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 a MegaBits value (SI, 10^6). + /// Creates a new instance from the specified MegaBits value. /// - /// The value to convert from megabits. - /// Returns a new instance from a 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 a MegaBytes value (SI, 10^6). + /// Creates a new instance from the specified MegaBytes value. /// - /// The value to convert from megabytes. - /// Returns a new instance from a 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 a GibiBits value (IEC, 2^30). + /// Creates a new instance from the specified GibiBits value. /// - /// The value to convert from gibibits. - /// Returns a new instance from a 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 a GibiBytes value (IEC, 2^30). + /// Creates a new instance from the specified GibiBytes value. /// - /// The value to convert from gibibytes. - /// Returns a new instance from a 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 a GigaBits value (SI, 10^9). + /// Creates a new instance from the specified GigaBits value. /// - /// The value to convert from gigabits. - /// Returns a new instance from a 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 a GigaBytes value (SI, 10^9). + /// Creates a new instance from the specified GigaBytes value. /// - /// The value to convert from gigabytes. - /// Returns a new instance from a 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 a TebiBits value (IEC, 2^40). + /// Creates a new instance from the specified TebiBits value. /// - /// The value to convert from tebibits. - /// Returns a new instance from a 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 a TebiBytes value (IEC, 2^40). + /// Creates a new instance from the specified TebiBytes value. /// - /// The value to convert from tebibytes. - /// Returns a new instance from a 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 a TeraBits value (SI, 10^12). + /// Creates a new instance from the specified TeraBits value. /// - /// The value to convert from terabits. - /// Returns a new instance from a 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 a TeraBytes value (SI, 10^12). + /// Creates a new instance from the specified TeraBytes value. /// - /// The value to convert from terabytes. - /// Returns a new instance from a 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 a PebiBits value (IEC, 2^50). + /// Creates a new instance from the specified PebiBits value. /// - /// The value to convert from pebibits. - /// Returns a new instance from a 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 a PebiBytes value (IEC, 2^50). + /// Creates a new instance from the specified PebiBytes value. /// - /// The value to convert from pebibytes. - /// Returns a new instance from a 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 a PetaBits value (SI, 10^15). + /// Creates a new instance from the specified PetaBits value. /// - /// The value to convert from petabits. - /// Returns a new instance from a 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 a PetaBytes value (SI, 10^15). + /// Creates a new instance from the specified PetaBytes value. /// - /// The value to convert from petabytes. - /// Returns a new instance from a 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 an ExbiBits value (IEC, 2^60). + /// Creates a new instance from the specified ExbiBits value. /// - /// The value to convert from exbibits. - /// Returns a new instance from an 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 an ExbiBytes value (IEC, 2^60). + /// Creates a new instance from the specified ExbiBytes value. /// - /// The value to convert from exbibytes. - /// Returns a new instance from an 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 an ExaBits value (SI, 10^18). + /// Creates a new instance from the specified ExaBits value. /// - /// The value to convert from exabits. - /// Returns a new instance from an 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 an ExaBytes value (SI, 10^18). + /// Creates a new instance from the specified ExaBytes value. /// - /// The value to convert from exabytes. - /// Returns a new instance from an 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 a ZebiBits value (IEC, 2^70). + /// Creates a new instance from the specified ZebiBits value. /// - /// The value to convert from zebibits. - /// Returns a new instance from a 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 a ZebiBytes value (IEC, 2^70). + /// Creates a new instance from the specified ZebiBytes value. /// - /// The value to convert from zebibytes. - /// Returns a new instance from a 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 a ZettaBits value (SI, 10^21). + /// Creates a new instance from the specified ZettaBits value. /// - /// The value to convert from zettabits. - /// Returns a new instance from a 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 a ZettaBytes value (SI, 10^21). + /// Creates a new instance from the specified ZettaBytes value. /// - /// The value to convert from zettabytes. - /// Returns a new instance from a 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 a YobiBits value (IEC, 2^80). + /// Creates a new instance from the specified YobiBits value. /// - /// The value to convert from yobibits. - /// Returns a new instance from a 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 a YobiBytes value (IEC, 2^80). + /// Creates a new instance from the specified YobiBytes value. /// - /// The value to convert from yobibytes. - /// Returns a new instance from a 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 a YottaBits value (SI, 10^24). + /// Creates a new instance from the specified YottaBits value. /// - /// The value to convert from yottabits. - /// Returns a new instance from a 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 a YottaBytes value (SI, 10^24). + /// Creates a new instance from the specified YottaBytes value. /// - /// The value to convert from yottabytes. - /// Returns a new instance from a 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); /// diff --git a/OnixLabs.Units/DataSize.To.cs b/OnixLabs.Units/DataSize.To.cs index d9e5e01..e06a355 100755 --- a/OnixLabs.Units/DataSize.To.cs +++ b/OnixLabs.Units/DataSize.To.cs @@ -17,6 +17,7 @@ namespace OnixLabs.Units; +// ReSharper disable MemberCanBePrivate.Global public readonly partial struct DataSize { /// diff --git a/OnixLabs.Units/DataSize.cs b/OnixLabs.Units/DataSize.cs index fdeee83..58bd6f5 100755 --- a/OnixLabs.Units/DataSize.cs +++ b/OnixLabs.Units/DataSize.cs @@ -21,7 +21,7 @@ namespace OnixLabs.Units; /// /// Represents a unit of data size. /// -/// The underlying floating point value. +/// The underlying value type. // ReSharper disable MemberCanBePrivate.Global public readonly partial struct DataSize : IValueEquatable>, @@ -29,175 +29,179 @@ namespace OnixLabs.Units; ISpanFormattable where T : IFloatingPoint { + /// + /// Initializes a new instance of the struct. + /// + /// The data size unit in . private DataSize(T value) => Bits = value; /// - /// Gets the data size in bits. + /// Gets the data size in bits (b). /// public T Bits { get; } /// - /// Gets the data size in bytes. + /// Gets the data size in bytes (B). /// public T Bytes => Bits / T.CreateChecked(8); /// - /// Gets the data size in kibibits. + /// Gets the data size in kibibits (Kib). /// public T KibiBits => GetBinaryValue(1, false); /// - /// Gets the data size in kibibytes. + /// Gets the data size in kibibytes (KiB). /// public T KibiBytes => GetBinaryValue(1, true); /// - /// Gets the data size in kilobits. + /// Gets the data size in kilobits (Kb). /// public T KiloBits => GetMetricValue(1, false); /// - /// Gets the data size in kilobytes. + /// Gets the data size in kilobytes (KB). /// public T KiloBytes => GetMetricValue(1, true); /// - /// Gets the data size in mebibits. + /// Gets the data size in mebibits (Mib). /// public T MebiBits => GetBinaryValue(2, false); /// - /// Gets the data size in mebibytes. + /// Gets the data size in mebibytes (MiB). /// public T MebiBytes => GetBinaryValue(2, true); /// - /// Gets the data size in megabits. + /// Gets the data size in megabits (Mb). /// public T MegaBits => GetMetricValue(2, false); /// - /// Gets the data size in megabytes. + /// Gets the data size in megabytes (MB). /// public T MegaBytes => GetMetricValue(2, true); /// - /// Gets the data size in gibibits. + /// Gets the data size in gibibits (Gib). /// public T GibiBits => GetBinaryValue(3, false); /// - /// Gets the data size in gibibytes. + /// Gets the data size in gibibytes (GiB). /// public T GibiBytes => GetBinaryValue(3, true); /// - /// Gets the data size in gigabits. + /// Gets the data size in gigabits (Gb). /// public T GigaBits => GetMetricValue(3, false); /// - /// Gets the data size in gigabytes. + /// Gets the data size in gigabytes (GB). /// public T GigaBytes => GetMetricValue(3, true); /// - /// Gets the data size in tebibits. + /// Gets the data size in tebibits (Tib). /// public T TebiBits => GetBinaryValue(4, false); /// - /// Gets the data size in tebibytes. + /// Gets the data size in tebibytes (TiB). /// public T TebiBytes => GetBinaryValue(4, true); /// - /// Gets the data size in terabits. + /// Gets the data size in terabits (Tb). /// public T TeraBits => GetMetricValue(4, false); /// - /// Gets the data size in terabytes. + /// Gets the data size in terabytes (TB). /// public T TeraBytes => GetMetricValue(4, true); /// - /// Gets the data size in pebibits. + /// Gets the data size in pebibits (Pib). /// public T PebiBits => GetBinaryValue(5, false); /// - /// Gets the data size in pebibytes. + /// Gets the data size in pebibytes (PiB). /// public T PebiBytes => GetBinaryValue(5, true); /// - /// Gets the data size in petabits. + /// Gets the data size in petabits (Pb). /// public T PetaBits => GetMetricValue(5, false); /// - /// Gets the data size in petabytes. + /// Gets the data size in petabytes (PB). /// public T PetaBytes => GetMetricValue(5, true); /// - /// Gets the data size in exbibits. + /// Gets the data size in exbibits (Eib). /// public T ExbiBits => GetBinaryValue(6, false); /// - /// Gets the data size in exbibytes. + /// Gets the data size in exbibytes (EiB). /// public T ExbiBytes => GetBinaryValue(6, true); /// - /// Gets the data size in exabits. + /// Gets the data size in exabits (Eb). /// public T ExaBits => GetMetricValue(6, false); /// - /// Gets the data size in exabytes. + /// Gets the data size in exabytes (EB). /// public T ExaBytes => GetMetricValue(6, true); /// - /// Gets the data size in zebibits. + /// Gets the data size in zebibits (Zib). /// public T ZebiBits => GetBinaryValue(7, false); /// - /// Gets the data size in zebibytes. + /// Gets the data size in zebibytes (ZiB). /// public T ZebiBytes => GetBinaryValue(7, true); /// - /// Gets the data size in zettabits. + /// Gets the data size in zettabits (Zb). /// public T ZettaBits => GetMetricValue(7, false); /// - /// Gets the data size in zettabytes. + /// Gets the data size in zettabytes (ZB). /// public T ZettaBytes => GetMetricValue(7, true); /// - /// Gets the data size in yobibits. + /// Gets the data size in yobibits (Yib). /// public T YobiBits => GetBinaryValue(8, false); /// - /// Gets the data size in yobibytes. + /// Gets the data size in yobibytes (YiB). /// public T YobiBytes => GetBinaryValue(8, true); /// - /// Gets the data size in yottabits. + /// Gets the data size in yottabits. (Yb) /// public T YottaBits => GetMetricValue(8, false); /// - /// Gets the data size in yottabytes. + /// Gets the data size in yottabytes (YB). /// public T YottaBytes => GetMetricValue(8, true); diff --git a/OnixLabs.Units/Distance.Equatable.cs b/OnixLabs.Units/Distance.Equatable.cs index 1fd5ec0..ef86520 100644 --- a/OnixLabs.Units/Distance.Equatable.cs +++ b/OnixLabs.Units/Distance.Equatable.cs @@ -43,7 +43,7 @@ public readonly partial struct Distance /// /// Serves as a hash code function for this instance. /// - /// A hash code for this instance. + /// Returns a hash code for this instance. public override int GetHashCode() => QuectoMeters.GetHashCode(); /// diff --git a/OnixLabs.Units/Distance.From.cs b/OnixLabs.Units/Distance.From.cs index a370887..bd90677 100644 --- a/OnixLabs.Units/Distance.From.cs +++ b/OnixLabs.Units/Distance.From.cs @@ -17,248 +17,248 @@ namespace OnixLabs.Units; public readonly partial struct Distance { /// - /// Creates a new instance from a Quectometers value. + /// Creates a new instance from the specified Quectometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromQuectometers(T value) => new(value); /// - /// Creates a new instance from a Rontometers value. + /// Creates a new instance from the specified Rontometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromRontometers(T value) => new(value * T.CreateChecked(1e3)); /// - /// Creates a new instance from a Yoctometers value. + /// Creates a new instance from the specified Yoctometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromYoctometers(T value) => new(value * T.CreateChecked(1e6)); /// - /// Creates a new instance from a Zeptometers value. + /// Creates a new instance from the specified Zeptometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromZeptometers(T value) => new(value * T.CreateChecked(1e9)); /// - /// Creates a new instance from a Attometers value. + /// Creates a new instance from the specified Attometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromAttometers(T value) => new(value * T.CreateChecked(1e12)); /// - /// Creates a new instance from a Femtometers value. + /// Creates a new instance from the specified Femtometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromFemtometers(T value) => new(value * T.CreateChecked(1e15)); /// - /// Creates a new instance from a Picometers value. + /// Creates a new instance from the specified Picometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromPicometers(T value) => new(value * T.CreateChecked(1e18)); /// - /// Creates a new instance from a Nanometers value. + /// Creates a new instance from the specified Nanometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromNanometers(T value) => new(value * T.CreateChecked(1e21)); /// - /// Creates a new instance from a Micrometers value. + /// Creates a new instance from the specified Micrometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromMicrometers(T value) => new(value * T.CreateChecked(1e24)); /// - /// Creates a new instance from a Millimeters value. + /// Creates a new instance from the specified Millimeters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromMillimeters(T value) => new(value * T.CreateChecked(1e27)); /// - /// Creates a new instance from a Centimeters value. + /// Creates a new instance from the specified Centimeters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromCentimeters(T value) => new(value * T.CreateChecked(1e28)); /// - /// Creates a new instance from a Decimeters value. + /// Creates a new instance from the specified Decimeters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromDecimeters(T value) => new(value * T.CreateChecked(1e29)); /// - /// Creates a new instance from a Meters value. + /// Creates a new instance from the specified Meters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromMeters(T value) => new(value * T.CreateChecked(1e30)); /// - /// Creates a new instance from a Decameters value. + /// Creates a new instance from the specified Decameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromDecameters(T value) => new(value * T.CreateChecked(1e31)); /// - /// Creates a new instance from a Hectometers value. + /// Creates a new instance from the specified Hectometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromHectometers(T value) => new(value * T.CreateChecked(1e32)); /// - /// Creates a new instance from a Kilometers value. + /// Creates a new instance from the specified Kilometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromKilometers(T value) => new(value * T.CreateChecked(1e33)); /// - /// Creates a new instance from a Quectometers value. + /// Creates a new instance from the specified Quectometers value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromMegameters(T value) => new(value * T.CreateChecked(1e36)); /// - /// Creates a new instance from a Gigameters value. + /// Creates a new instance from the specified Gigameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromGigameters(T value) => new(value * T.CreateChecked(1e39)); /// - /// Creates a new instance from a Terameters value. + /// Creates a new instance from the specified Terameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromTerameters(T value) => new(value * T.CreateChecked(1e42)); /// - /// Creates a new instance from a Petameters value. + /// Creates a new instance from the specified Petameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromPetameters(T value) => new(value * T.CreateChecked(1e45)); /// - /// Creates a new instance from a Exameters value. + /// Creates a new instance from the specified Exameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromExameters(T value) => new(value * T.CreateChecked(1e48)); /// - /// Creates a new instance from a Zettameters value. + /// Creates a new instance from the specified Zettameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromZettameters(T value) => new(value * T.CreateChecked(1e51)); /// - /// Creates a new instance from a Yottameters value. + /// Creates a new instance from the specified Yottameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromYottameters(T value) => new(value * T.CreateChecked(1e54)); /// - /// Creates a new instance from a Ronnameters value. + /// Creates a new instance from the specified Ronnameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromRonnameters(T value) => new(value * T.CreateChecked(1e57)); /// - /// Creates a new instance from a Quettameters value. + /// Creates a new instance from the specified Quettameters value. /// /// The value from which to construct the new instance. - /// A newly created instance. + /// Returns a new instance from the specified value. public static Distance FromQuettameters(T value) => new(value * T.CreateChecked(1e60)); /// - /// Creates a new instance from a Inches value. + /// Creates a new instance from the specified Inches value. /// /// The value from which to construct the new instance. - /// A newly created 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 a Feet value. + /// Creates a new instance from the specified Feet value. /// /// The value from which to construct the new instance. - /// A newly created 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 a Yards value. + /// Creates a new instance from the specified Yards value. /// /// The value from which to construct the new instance. - /// A newly created 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 a Miles value. + /// Creates a new instance from the specified Miles value. /// /// The value from which to construct the new instance. - /// A newly created 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 a Nautical Miles value. + /// Creates a new instance from the specified Nautical Miles value. /// /// The value from which to construct the new instance. - /// A newly created 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 a Fermis value. + /// Creates a new instance from the specified Fermis value. /// /// The value from which to construct the new instance. - /// A newly created 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 an Angstroms value. + /// Creates a new instance from the specified Angstroms value. /// /// The value from which to construct the new instance. - /// A newly created 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 a Astronomical Units value. + /// Creates a new instance from the specified Astronomical Units value. /// /// The value from which to construct the new instance. - /// A newly created 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 a Light Years value. + /// Creates a new instance from the specified Light Years value. /// /// The value from which to construct the new instance. - /// A newly created 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 a Parsecs value. + /// Creates a new instance from the specified Parsecs value. /// /// The value from which to construct the new instance. - /// A newly created 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; diff --git a/OnixLabs.Units/Distance.To.cs b/OnixLabs.Units/Distance.To.cs index b5c5165..f7aece5 100644 --- a/OnixLabs.Units/Distance.To.cs +++ b/OnixLabs.Units/Distance.To.cs @@ -42,7 +42,7 @@ public readonly partial struct Distance /// Returns the value of the current instance in the specified format. public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) { - (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: MetersSpecifier); + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: QuectoMetersSpecifier); T value = specifier switch { diff --git a/OnixLabs.Units/Distance.cs b/OnixLabs.Units/Distance.cs index e781875..794d0e9 100644 --- a/OnixLabs.Units/Distance.cs +++ b/OnixLabs.Units/Distance.cs @@ -21,7 +21,7 @@ namespace OnixLabs.Units; /// /// Represents a unit of distance. /// -/// The underlying floating point value. +/// The underlying value type. // ReSharper disable MemberCanBePrivate.Global public readonly partial struct Distance : IValueEquatable>, @@ -29,180 +29,184 @@ namespace OnixLabs.Units; ISpanFormattable 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. + /// Gets the distance in Quectometers (qm). /// public T QuectoMeters { get; } /// - /// Gets the distance in Rontometers. + /// Gets the distance in Rontometers (rm). /// public T RontoMeters => QuectoMeters / T.CreateChecked(1e3); /// - /// Gets the distance in Yoctometers. + /// Gets the distance in Yoctometers (ym). /// public T YoctoMeters => QuectoMeters / T.CreateChecked(1e6); /// - /// Gets the distance in Zeptometers. + /// Gets the distance in Zeptometers (zm). /// public T ZeptoMeters => QuectoMeters / T.CreateChecked(1e9); /// - /// Gets the distance in Attometers. + /// Gets the distance in Attometers (am). /// public T AttoMeters => QuectoMeters / T.CreateChecked(1e12); /// - /// Gets the distance in Femtometers. + /// Gets the distance in Femtometers (fm). /// public T FemtoMeters => QuectoMeters / T.CreateChecked(1e15); /// - /// Gets the distance in Picometers. + /// Gets the distance in Picometers (pm). /// public T PicoMeters => QuectoMeters / T.CreateChecked(1e18); /// - /// Gets the distance in Nanometers. + /// Gets the distance in Nanometers (nm). /// public T NanoMeters => QuectoMeters / T.CreateChecked(1e21); /// - /// Gets the distance in Micrometers. + /// Gets the distance in Micrometers (um). /// public T MicroMeters => QuectoMeters / T.CreateChecked(1e24); /// - /// Gets the distance in Millimeters. + /// Gets the distance in Millimeters (mm). /// public T MilliMeters => QuectoMeters / T.CreateChecked(1e27); /// - /// Gets the distance in Centimeters. + /// Gets the distance in Centimeters (cm). /// public T CentiMeters => QuectoMeters / T.CreateChecked(1e28); /// - /// Gets the distance in Decimeters. + /// Gets the distance in Decimeters (dm). /// public T DeciMeters => QuectoMeters / T.CreateChecked(1e29); /// - /// Gets the distance in Meters. + /// Gets the distance in Meters (m). /// public T Meters => QuectoMeters / T.CreateChecked(1e30); /// - /// Gets the distance in Decameters. + /// Gets the distance in Decameters (dam). /// public T DecaMeters => QuectoMeters / T.CreateChecked(1e31); /// - /// Gets the distance in Hectometers. + /// Gets the distance in Hectometers (hm). /// public T HectoMeters => QuectoMeters / T.CreateChecked(1e32); /// - /// Gets the distance in Kilometers. + /// Gets the distance in Kilometers (km). /// public T KiloMeters => QuectoMeters / T.CreateChecked(1e33); /// - /// Gets the distance in Megameters. + /// Gets the distance in Megameters (Mm). /// public T MegaMeters => QuectoMeters / T.CreateChecked(1e36); /// - /// Gets the distance in Gigameters. + /// Gets the distance in Gigameters (Gm). /// public T GigaMeters => QuectoMeters / T.CreateChecked(1e39); /// - /// Gets the distance in Terameters. + /// Gets the distance in Terameters (Tm). /// public T TeraMeters => QuectoMeters / T.CreateChecked(1e42); /// - /// Gets the distance in Petameters. + /// Gets the distance in Petameters (Pm). /// public T PetaMeters => QuectoMeters / T.CreateChecked(1e45); /// - /// Gets the distance in Exameters. + /// Gets the distance in Exameters (Em). /// public T ExaMeters => QuectoMeters / T.CreateChecked(1e48); /// - /// Gets the distance in Zettameters. + /// Gets the distance in Zettameters (Zm). /// public T ZettaMeters => QuectoMeters / T.CreateChecked(1e51); /// - /// Gets the distance in Yottameters. + /// Gets the distance in Yottameters (Ym). /// public T YottaMeters => QuectoMeters / T.CreateChecked(1e54); /// - /// Gets the distance in Ronnameters. + /// Gets the distance in Ronnameters (Rm). /// public T RonnaMeters => QuectoMeters / T.CreateChecked(1e57); /// - /// Gets the distance in Quettameters. + /// Gets the distance in Quettameters (Qm). /// public T QuettaMeters => QuectoMeters / T.CreateChecked(1e60); /// - /// Gets the distance in Inches. + /// Gets the distance in Inches (in). /// public T Inches => QuectoMeters / T.CreateChecked(0.0254e30); /// - /// Gets the distance in Feet. + /// Gets the distance in Feet (ft). /// public T Feet => QuectoMeters / T.CreateChecked(0.3048e30); /// - /// Gets the distance in Yards. + /// Gets the distance in Yards (yd). /// public T Yards => QuectoMeters / T.CreateChecked(0.9144e30); /// - /// Gets the distance in Miles. + /// Gets the distance in Miles (mi). /// public T Miles => QuectoMeters / T.CreateChecked(1609.344e30); /// - /// Gets the distance in Nautical Miles. + /// Gets the distance in Nautical Miles (nmi). /// public T NauticalMiles => QuectoMeters / T.CreateChecked(1852e30); /// - /// Gets the distance in Nautical Fermis. + /// Gets the distance in Nautical Fermis (fmi). /// public T Fermis => QuectoMeters / T.CreateChecked(1e15); /// - /// Gets the distance in Nautical Angstroms. + /// Gets the distance in Nautical Angstroms (a). /// public T Angstroms => QuectoMeters / T.CreateChecked(1e20); /// - /// Gets the distance in Astronomical Units. + /// Gets the distance in Astronomical Units (au). /// public T AstronomicalUnits => QuectoMeters / T.CreateChecked(149_597_870_700L * 1e30); /// - /// Gets the distance in Light Years. + /// 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. + /// Gets the distance in Parsecs (pc). /// public T Parsecs { diff --git a/OnixLabs.Units/Mass.Arithmetic.Addition.cs b/OnixLabs.Units/Mass.Arithmetic.Addition.cs new file mode 100644 index 0000000..2dc81d8 --- /dev/null +++ b/OnixLabs.Units/Mass.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass +{ + /// + /// 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 Mass Add(Mass left, Mass right) => new(left.YoctoGrams + right.YoctoGrams); + + /// + /// 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 Mass operator +(Mass left, Mass right) => Add(left, right); + + /// + /// 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 Mass Add(Mass other) => Add(this, other); +} diff --git a/OnixLabs.Units/Mass.Arithmetic.Division.cs b/OnixLabs.Units/Mass.Arithmetic.Division.cs new file mode 100644 index 0000000..dd7fb5a --- /dev/null +++ b/OnixLabs.Units/Mass.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass +{ + /// + /// 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 Mass Divide(Mass left, Mass right) => new(left.YoctoGrams / right.YoctoGrams); + + /// + /// 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 Mass operator /(Mass left, Mass right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value, divided by the specified other value. + public Mass Divide(Mass other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Mass.Arithmetic.Multiplication.cs b/OnixLabs.Units/Mass.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..f498a0e --- /dev/null +++ b/OnixLabs.Units/Mass.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass +{ + /// + /// 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 Mass Multiply(Mass left, Mass right) => new(left.YoctoGrams * right.YoctoGrams); + + /// + /// 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 Mass operator *(Mass left, Mass right) => Multiply(left, right); + + /// + /// Computes the product of the current value, multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value, multiplied by the specified other value. + public Mass Multiply(Mass other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Mass.Arithmetic.Subtraction.cs b/OnixLabs.Units/Mass.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..291b59c --- /dev/null +++ b/OnixLabs.Units/Mass.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass +{ + /// + /// 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 Mass Subtract(Mass left, Mass right) => new(left.YoctoGrams - right.YoctoGrams); + + /// + /// 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 Mass operator -(Mass left, Mass right) => Subtract(left, right); + + /// + /// Computes the difference of the specified other value, subtracted from the current value. + /// + /// The value to subtract from the current value. + /// Returns the difference of the specified other value, subtracted from the current value. + public Mass Subtract(Mass other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Mass.Comparable.cs b/OnixLabs.Units/Mass.Comparable.cs new file mode 100644 index 0000000..e2486a9 --- /dev/null +++ b/OnixLabs.Units/Mass.Comparable.cs @@ -0,0 +1,81 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(Mass left, Mass right) => left.YoctoGrams.CompareTo(right.YoctoGrams); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(Mass other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than right-hand operand; otherwise, . + public static bool operator >(Mass left, Mass right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(Mass left, Mass right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(Mass left, Mass right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(Mass left, Mass right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Mass.Constants.cs b/OnixLabs.Units/Mass.Constants.cs new file mode 100644 index 0000000..c83bf96 --- /dev/null +++ b/OnixLabs.Units/Mass.Constants.cs @@ -0,0 +1,54 @@ +// 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 Mass +{ + /// + /// Gets a zero value, equal to zero yoctograms. + /// + public static readonly Mass Zero = new(T.Zero); + + private const string YoctoGramsSpecifier = "yg"; + private const string ZeptoGramsSpecifier = "zg"; + private const string AttoGramsSpecifier = "ag"; + private const string FemtoGramsSpecifier = "fg"; + private const string PicoGramsSpecifier = "pg"; + private const string NanoGramsSpecifier = "ng"; + private const string MicroGramsSpecifier = "ug"; + private const string MilliGramsSpecifier = "mg"; + private const string GramsSpecifier = "g"; + private const string KiloGramsSpecifier = "kg"; + private const string MegaGramsSpecifier = "Mg"; + private const string TonneSpecifier = "t"; + private const string GigaGramsSpecifier = "Gg"; + private const string TeraGramsSpecifier = "Tg"; + private const string PetaGramsSpecifier = "Pg"; + private const string ExaGramsSpecifier = "Eg"; + private const string ZettaGramsSpecifier = "Zg"; + private const string YottaGramsSpecifier = "Yg"; + private const string PoundsSpecifier = "lb"; + private const string OuncesSpecifier = "oz"; + private const string StonesSpecifier = "st"; + private const string GrainsSpecifier = "gr"; + private const string ShortTonsSpecifier = "ton"; + private const string LongTonsSpecifier = "lt"; + private const string HundredweightUsSpecifier = "cwtUS"; + private const string HundredweightUkSpecifier = "cwtUK"; + private const string QuartersSpecifier = "qr"; + private const string TroyPoundsSpecifier = "lbt"; + private const string TroyOuncesSpecifier = "ozt"; + private const string PennyweightsSpecifier = "dwt"; +} diff --git a/OnixLabs.Units/Mass.Equatable.cs b/OnixLabs.Units/Mass.Equatable.cs new file mode 100644 index 0000000..08d3675 --- /dev/null +++ b/OnixLabs.Units/Mass.Equatable.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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass +{ + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(Mass left, Mass right) => Equals(left.YoctoGrams, right.YoctoGrams); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// Returns if the current instance is equal to the specified other instance; otherwise, . + public bool Equals(Mass other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Mass other && Equals(other); + + /// + /// Serves as a hash code function for this instance. + /// + /// Returns a hash code for this instance. + public override int GetHashCode() => YoctoGrams.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(Mass left, Mass right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(Mass left, Mass right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Mass.Format.cs b/OnixLabs.Units/Mass.Format.cs new file mode 100644 index 0000000..db7f6d1 --- /dev/null +++ b/OnixLabs.Units/Mass.Format.cs @@ -0,0 +1,32 @@ +// 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 Mass +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Mass.From.cs b/OnixLabs.Units/Mass.From.cs new file mode 100644 index 0000000..d824d27 --- /dev/null +++ b/OnixLabs.Units/Mass.From.cs @@ -0,0 +1,229 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass +{ + /// + /// Creates a new instance from the specified Yoctograms value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromYoctograms(T value) => new(value); + + /// + /// Creates a new instance from the specified Zeptograms value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromZeptograms(T value) => new(value * T.CreateChecked(1e3)); + + /// + /// Creates a new instance from the specified Attograms value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromAttograms(T value) => new(value * T.CreateChecked(1e6)); + + /// + /// Creates a new instance from the specified Femtograms value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromFemtograms(T value) => new(value * T.CreateChecked(1e9)); + + /// + /// Creates a new instance from the specified Picograms value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromPicograms(T value) => new(value * T.CreateChecked(1e12)); + + /// + /// Creates a new instance from the specified Nanograms value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromNanograms(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from the specified Micrograms value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromMicrograms(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from the specified Milligrams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromMilligrams(T value) => new(value * T.CreateChecked(1e21)); + + /// + /// Creates a new instance from the specified Grams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromGrams(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from the specified Kilograms value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromKilograms(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from the specified Megagrams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromMegagrams(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified Tonnes value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromTonnes(T value) => FromMegagrams(value); + + /// + /// Creates a new instance from the specified Gigagrams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromGigagrams(T value) => new(value * T.CreateChecked(1e33)); + + /// + /// Creates a new instance from the specified Teragrams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromTeragrams(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from the specified Petagrams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromPetagrams(T value) => new(value * T.CreateChecked(1e39)); + + /// + /// Creates a new instance from the specified Exagrams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromExagrams(T value) => new(value * T.CreateChecked(1e42)); + + /// + /// Creates a new instance from the specified Zettagrams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromZettagrams(T value) => new(value * T.CreateChecked(1e45)); + + /// + /// Creates a new instance from the specified Yottagrams value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromYottagrams(T value) => new(value * T.CreateChecked(1e48)); + + /// + /// Creates a new instance from the specified Pounds value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromPounds(T value) => new(value * T.CreateChecked(4.5359237e26)); + + /// + /// Creates a new instance from the specified Ounces value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromOunces(T value) => new(value * T.CreateChecked(2.8349523125e25)); + + /// + /// Creates a new instance from the specified Stones value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromStones(T value) => new(value * T.CreateChecked(6.35029318e27)); + + /// + /// Creates a new instance from the specified Grains value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromGrains(T value) => new(value * T.CreateChecked(6.479891e22)); + + /// + /// Creates a new instance from the specified ShortTons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromShortTons(T value) => new(value * T.CreateChecked(9.0718474e29)); + + /// + /// Creates a new instance from the specified LongTons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromLongTons(T value) => new(value * T.CreateChecked(1.0160469088e30)); + + /// + /// Creates a new instance from the specified HundredweightUs value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromHundredweightUs(T value) => new(value * T.CreateChecked(4.5359237e28)); + + /// + /// Creates a new instance from the specified HundredweightUk value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromHundredweightUk(T value) => new(value * T.CreateChecked(5.080234544e28)); + + /// + /// Creates a new instance from the specified Quarters value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromQuarters(T value) => new(value * T.CreateChecked(1.270058636e28)); + + /// + /// Creates a new instance from the specified TroyPounds value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromTroyPounds(T value) => new(value * T.CreateChecked(3.732417216e26)); + + /// + /// Creates a new instance from the specified TroyOunces value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromTroyOunces(T value) => new(value * T.CreateChecked(3.11034768e25)); + + /// + /// Creates a new instance from the specified Pennyweights value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Mass FromPennyweights(T value) => new(value * T.CreateChecked(1.55517384e24)); +} diff --git a/OnixLabs.Units/Mass.To.cs b/OnixLabs.Units/Mass.To.cs new file mode 100644 index 0000000..bb09f87 --- /dev/null +++ b/OnixLabs.Units/Mass.To.cs @@ -0,0 +1,92 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(YoctoGramsSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: YoctoGramsSpecifier); + + T value = specifier switch + { + YoctoGramsSpecifier => YoctoGrams, + ZeptoGramsSpecifier => ZeptoGrams, + AttoGramsSpecifier => AttoGrams, + FemtoGramsSpecifier => FemtoGrams, + PicoGramsSpecifier => PicoGrams, + NanoGramsSpecifier => NanoGrams, + MicroGramsSpecifier => MicroGrams, + MilliGramsSpecifier => MilliGrams, + GramsSpecifier => Grams, + KiloGramsSpecifier => KiloGrams, + MegaGramsSpecifier => MegaGrams, + TonneSpecifier => Tonnes, + GigaGramsSpecifier => GigaGrams, + TeraGramsSpecifier => TeraGrams, + PetaGramsSpecifier => PetaGrams, + ExaGramsSpecifier => ExaGrams, + ZettaGramsSpecifier => ZettaGrams, + YottaGramsSpecifier => YottaGrams, + PoundsSpecifier => Pounds, + OuncesSpecifier => Ounces, + StonesSpecifier => Stones, + GrainsSpecifier => Grains, + ShortTonsSpecifier => ShortTons, + LongTonsSpecifier => LongTons, + HundredweightUsSpecifier => HundredweightUs, + HundredweightUkSpecifier => HundredweightUk, + QuartersSpecifier => Quarters, + TroyPoundsSpecifier => TroyPounds, + TroyOuncesSpecifier => TroyOunces, + PennyweightsSpecifier => Pennyweights, + _ => throw new ArgumentException( + $"Format '{format.ToString()}' is invalid. " + + "Valid format specifiers are: " + + "yg, zg, ag, fg, pg, ng, ug, mg, g, kg, Mg, t, Gg, Tg, Pg, Eg, Zg, Yg, " + + "lb, oz, st, gr, ton, lt, cwtUS, cwtUK, qr, lbt, ozt, dwt. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Mass.cs b/OnixLabs.Units/Mass.cs new file mode 100644 index 0000000..47cceb5 --- /dev/null +++ b/OnixLabs.Units/Mass.cs @@ -0,0 +1,190 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of mass. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Mass : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The mass unit in . + private Mass(T value) => YoctoGrams = value; + + /// + /// Gets the mass in yoctograms (yg). + /// + public T YoctoGrams { get; } + + /// + /// Gets the mass in zeptograms (zg). + /// + public T ZeptoGrams => YoctoGrams / T.CreateChecked(1e3); + + /// + /// Gets the mass in attograms (ag). + /// + public T AttoGrams => YoctoGrams / T.CreateChecked(1e6); + + /// + /// Gets the mass in femtograms (fg). + /// + public T FemtoGrams => YoctoGrams / T.CreateChecked(1e9); + + /// + /// Gets the mass in picograms (pg). + /// + public T PicoGrams => YoctoGrams / T.CreateChecked(1e12); + + /// + /// Gets the mass in nanograms (ng). + /// + public T NanoGrams => YoctoGrams / T.CreateChecked(1e15); + + /// + /// Gets the mass in micrograms (ug). + /// + public T MicroGrams => YoctoGrams / T.CreateChecked(1e18); + + /// + /// Gets the mass in milligrams (mg). + /// + public T MilliGrams => YoctoGrams / T.CreateChecked(1e21); + + /// + /// Gets the mass in grams (g). + /// + public T Grams => YoctoGrams / T.CreateChecked(1e24); + + /// + /// Gets the mass in kilograms (kg). + /// + public T KiloGrams => YoctoGrams / T.CreateChecked(1e27); + + /// + /// Gets the mass in megagrams (Mg). + /// + public T MegaGrams => YoctoGrams / T.CreateChecked(1e30); + + /// + /// Gets the mass in tonnes (t). + /// + /// + /// This value is the same as . + /// + public T Tonnes => MegaGrams; + + /// + /// Gets the mass in gigagrams (Gg). + /// + public T GigaGrams => YoctoGrams / T.CreateChecked(1e33); + + /// + /// Gets the mass in teragrams (Tg). + /// + public T TeraGrams => YoctoGrams / T.CreateChecked(1e36); + + /// + /// Gets the mass in petagrams (Pg). + /// + public T PetaGrams => YoctoGrams / T.CreateChecked(1e39); + + /// + /// Gets the mass in exagrams (Eg). + /// + public T ExaGrams => YoctoGrams / T.CreateChecked(1e42); + + /// + /// Gets the mass in zettagrams (Zg). + /// + public T ZettaGrams => YoctoGrams / T.CreateChecked(1e45); + + /// + /// Gets the mass in yottagrams (Yg). + /// + public T YottaGrams => YoctoGrams / T.CreateChecked(1e48); + + /// + /// Gets the mass in avoirdupois pounds (lb). + /// + public T Pounds => YoctoGrams / T.CreateChecked(4.5359237e26); + + /// + /// Gets the mass in avoirdupois ounces (oz). + /// + public T Ounces => YoctoGrams / T.CreateChecked(2.8349523125e25); + + /// + /// Gets the mass in stones (st). + /// + public T Stones => YoctoGrams / T.CreateChecked(6.35029318e27); + + /// + /// Gets the mass in grains (gr). + /// + public T Grains => YoctoGrams / T.CreateChecked(6.479891e22); + + /// + /// Gets the mass in US short tons (ton). + /// + public T ShortTons => YoctoGrams / T.CreateChecked(9.0718474e29); + + /// + /// Gets the mass in imperial long tons (lt). + /// + public T LongTons => YoctoGrams / T.CreateChecked(1.0160469088e30); + + /// + /// Gets the mass in US hundredweight (cwtUS). + /// + public T HundredweightUs => YoctoGrams / T.CreateChecked(4.5359237e28); + + /// + /// Gets the mass in UK (imperial) hundredweight (cwtUK). + /// + public T HundredweightUk => YoctoGrams / T.CreateChecked(5.080234544e28); + + /// + /// Gets the mass in quarters (qr). + /// + public T Quarters => YoctoGrams / T.CreateChecked(1.270058636e28); + + /// + /// Gets the mass in troy pounds (lbt). + /// + public T TroyPounds => YoctoGrams / T.CreateChecked(3.732417216e26); + + /// + /// Gets the mass in troy ounces (ozt). + /// + public T TroyOunces => YoctoGrams / T.CreateChecked(3.11034768e25); + + /// + /// Gets the mass in pennyweights (dwt). + /// + public T Pennyweights => YoctoGrams / T.CreateChecked(1.55517384e24); +} diff --git a/OnixLabs.Units/Temperature.Equatable.cs b/OnixLabs.Units/Temperature.Equatable.cs index 9a28d2d..a1413e5 100644 --- a/OnixLabs.Units/Temperature.Equatable.cs +++ b/OnixLabs.Units/Temperature.Equatable.cs @@ -43,7 +43,7 @@ public readonly partial struct Temperature /// /// Serves as a hash code function for this instance. /// - /// A hash code for this instance. + /// Returns a hash code for this instance. public override int GetHashCode() => Kelvin.GetHashCode(); /// diff --git a/OnixLabs.Units/Temperature.From.cs b/OnixLabs.Units/Temperature.From.cs index 2994874..0369aba 100644 --- a/OnixLabs.Units/Temperature.From.cs +++ b/OnixLabs.Units/Temperature.From.cs @@ -17,58 +17,58 @@ namespace OnixLabs.Units; public readonly partial struct Temperature { /// - /// Creates a new instance from a Celsius value. + /// Creates a new instance from the specified Celsius value. /// /// The value from which to construct the new instance. - /// Returns a newly created 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 a Delisle value. + /// Creates a new instance from the specified Delisle value. /// /// The value from which to construct the new instance. - /// Returns a newly created 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 a Fahrenheit value. + /// Creates a new instance from the specified Fahrenheit value. /// /// The value from which to construct the new instance. - /// Returns a newly created 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 a Kelvin value. + /// Creates a new instance from the specified Kelvin value. /// /// The value from which to construct the new instance. - /// Returns a newly created instance. + /// Returns a new instance from the specified value. public static Temperature FromKelvin(T value) => new(value); /// - /// Creates a new instance from a Newton value. + /// Creates a new instance from the specified Newton value. /// /// The value from which to construct the new instance. - /// Returns a newly created 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 a Rankine value. + /// Creates a new instance from the specified Rankine value. /// /// The value from which to construct the new instance. - /// Returns a newly created 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 a Réaumur value. + /// Creates a new instance from the specified Réaumur value. /// /// The value from which to construct the new instance. - /// Returns a newly created 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 a Rømer value. + /// Creates a new instance from the specified Rømer value. /// /// The value from which to construct the new instance. - /// Returns a newly created 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.cs b/OnixLabs.Units/Temperature.cs index 60e7722..b0228e1 100644 --- a/OnixLabs.Units/Temperature.cs +++ b/OnixLabs.Units/Temperature.cs @@ -19,9 +19,12 @@ namespace OnixLabs.Units; /// -/// Represents a unit of temperature. The default value is absolute zero, or 0 K. +/// Represents a unit of temperature. /// -/// The underlying floating point value. +/// +/// The default value is absolute zero, or 0 K. +/// +/// The underlying value type. // ReSharper disable MemberCanBePrivate.Global public readonly partial struct Temperature : IValueEquatable>, @@ -30,48 +33,48 @@ namespace OnixLabs.Units; where T : IFloatingPoint { /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - /// The value in Kelvin with which to initialize the current instance. + /// The temperature unit in . private Temperature(T value) => Kelvin = value; /// - /// Gets the temperature in Kelvin. + /// Gets the temperature in Kelvin (K). /// public T Kelvin { get; } /// - /// Gets the temperature in Celsius. + /// Gets the temperature in Celsius (C). /// public T Celsius => Kelvin - T.CreateChecked(273.15); /// - /// Gets the temperature in Delisle. + /// Gets the temperature in Delisle (DE). /// public T Delisle => (T.CreateChecked(373.15) - Kelvin) * T.CreateChecked(1.50); /// - /// Gets the temperature in Fahrenheit. + /// Gets the temperature in Fahrenheit (F). /// public T Fahrenheit => Kelvin * T.CreateChecked(1.80) - T.CreateChecked(459.67); /// - /// Gets the temperature in Newton. + /// 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. + /// Gets the temperature in Rankine (R). /// public T Rankine => Kelvin * T.CreateChecked(1.8); /// - /// Gets the temperature in Réaumur. + /// 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. + /// 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); } From 7f2c6564ddaeb67840f20d2559e0d7084caa778e Mon Sep 17 00:00:00 2001 From: matthew Date: Tue, 18 Nov 2025 10:10:11 +0000 Subject: [PATCH 13/23] Added Mass tests --- OnixLabs.Units.UnitTests/MassTests.cs | 594 ++++++++++++++++++++++ OnixLabs.Units/Extensions.ReadOnlySpan.cs | 2 +- 2 files changed, 595 insertions(+), 1 deletion(-) create mode 100644 OnixLabs.Units.UnitTests/MassTests.cs diff --git a/OnixLabs.Units.UnitTests/MassTests.cs b/OnixLabs.Units.UnitTests/MassTests.cs new file mode 100644 index 0000000..97788f3 --- /dev/null +++ b/OnixLabs.Units.UnitTests/MassTests.cs @@ -0,0 +1,594 @@ +// 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; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class MassTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+42; + + [Fact(DisplayName = "Mass.Zero should produce the expected result")] + public void MassZeroShouldProduceExpectedResult() + { + // Given / When + Mass mass = Mass.Zero; + + // Then + Assert.Equal(0.0, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromYoctograms should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void MassFromYoctogramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromYoctograms(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromZeptograms should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e3)] + [InlineData(2.5, 2.5e3)] + public void MassFromZeptogramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + Mass mass = Mass.FromZeptograms(value); + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromAttograms should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void MassFromAttogramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromAttograms(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromFemtograms should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e9)] + [InlineData(2.5, 2.5e9)] + public void MassFromFemtogramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromFemtograms(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromPicograms should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void MassFromPicogramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromPicograms(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromNanograms should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e15)] + [InlineData(2.5, 2.5e15)] + public void MassFromNanogramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromNanograms(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromMicrograms should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void MassFromMicrogramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromMicrograms(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromMilligrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e21)] + [InlineData(2.5, 2.5e21)] + public void MassFromMilligramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromMilligrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromGrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void MassFromGramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromGrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromKilograms should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void MassFromKilogramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromKilograms(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromMegagrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void MassFromMegagramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromMegagrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromTonnes should be equivalent to FromMegagrams")] + [InlineData(0.0)] + [InlineData(1.0)] + [InlineData(2.5)] + public void MassFromTonnesShouldBeEquivalentToFromMegagrams(double value) + { + // Given / When + Mass megagrams = Mass.FromMegagrams(value); + Mass tonnes = Mass.FromTonnes(value); + + // Then + Assert.Equal(megagrams.YoctoGrams, tonnes.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromGigagrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e33)] + [InlineData(2.5, 2.5e33)] + public void MassFromGigagramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromGigagrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromTeragrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void MassFromTeragramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromTeragrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromPetagrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e39)] + [InlineData(2.5, 2.5e39)] + public void MassFromPetagramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromPetagrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromExagrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void MassFromExagramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromExagrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromZettagrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e45)] + [InlineData(2.5, 2.5e45)] + public void MassFromZettagramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromZettagrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromYottagrams should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void MassFromYottagramsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromYottagrams(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromPounds should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.5359237e26)] + [InlineData(2.5, 1.133980925e27)] + public void MassFromPoundsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromPounds(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromOunces should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.8349523125e25)] + [InlineData(2.5, 7.08738078125e25)] + public void MassFromOuncesShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromOunces(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromStones should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 6.35029318e27)] + [InlineData(2.5, 1.587573295e28)] + public void MassFromStonesShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromStones(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromGrains should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 6.479891e22)] + [InlineData(2.5, 1.61997275e23)] + public void MassFromGrainsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromGrains(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromShortTons should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.0718474e29)] + [InlineData(2.5, 2.26796185e30)] + public void MassFromShortTonsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromShortTons(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromLongTons should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0160469088e30)] + [InlineData(2.5, 2.540117272e30)] + public void MassFromLongTonsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromLongTons(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromHundredweightUs should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.5359237e28)] + [InlineData(2.5, 1.133980925e29)] + public void MassFromHundredweightUsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromHundredweightUs(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromHundredweightUk should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 5.080234544e28)] + [InlineData(2.5, 1.270058636e29)] + public void MassFromHundredweightUkShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromHundredweightUk(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromQuarters should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.270058636e28)] + [InlineData(2.5, 3.17514659e28)] + public void MassFromQuartersShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromQuarters(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromTroyPounds should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.732417216e26)] + [InlineData(2.5, 9.33104304e26)] + public void MassFromTroyPoundsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromTroyPounds(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromTroyOunces should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.11034768e25)] + [InlineData(2.5, 7.7758692e25)] + public void MassFromTroyOuncesShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromTroyOunces(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Theory(DisplayName = "Mass.FromPennyweights should produce the expected YoctoGrams")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.55517384e24)] + [InlineData(2.5, 3.8879346e24)] + public void MassFromPennyweightsShouldProduceExpectedYoctoGrams(double value, double expectedYoctoGrams) + { + // Given / When + Mass mass = Mass.FromPennyweights(value); + + // Then + Assert.Equal(expectedYoctoGrams, mass.YoctoGrams, Tolerance); + } + + [Fact(DisplayName = "Mass.Add should produce the expected result")] + public void MassAddShouldProduceExpectedValue() + { + // Given + Mass left = Mass.FromKilograms(1500.0); + Mass right = Mass.FromKilograms(500.0); + + // When + Mass result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.KiloGrams, Tolerance); + } + + [Fact(DisplayName = "Mass.Subtract should produce the expected result")] + public void MassSubtractShouldProduceExpectedValue() + { + // Given + Mass left = Mass.FromKilograms(1500.0); + Mass right = Mass.FromKilograms(400.0); + + // When + Mass result = left.Subtract(right); + + // Then + Assert.Equal(1100.0, result.KiloGrams, Tolerance); + } + + [Fact(DisplayName = "Mass.Multiply should produce the expected result")] + public void MassMultiplyShouldProduceExpectedValue() + { + // Given + Mass left = Mass.FromGrams(10.0); // 1e25 yg + Mass right = Mass.FromGrams(3.0); // 3e24 yg + + // When + Mass result = left.Multiply(right); // 1e25 * 3e24 = 3e49 yg + + // Then + Assert.Equal(1e25, left.YoctoGrams, Tolerance); + Assert.Equal(3e24, right.YoctoGrams, Tolerance); + Assert.Equal(3e49, result.YoctoGrams, Tolerance); + Assert.Equal(3e25, result.Grams, Tolerance); + } + + [Fact(DisplayName = "Mass.Divide should produce the expected result")] + public void MassDivideShouldProduceExpectedValue() + { + // Given + Mass left = Mass.FromGrams(100.0); // 1e26 yg + Mass right = Mass.FromGrams(20.0); // 2e25 yg + + // When + Mass result = left.Divide(right); // 1e26 / 2e25 = 5 yg + + // Then + Assert.Equal(5.0, result.YoctoGrams, Tolerance); + Assert.Equal(5e-24, result.Grams, Tolerance); + } + + [Fact(DisplayName = "Mass comparison should produce the expected result (left equal to right)")] + public void MassComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Mass left = Mass.FromKilograms(1234.0); + Mass right = Mass.FromKilograms(1234.0); + + // When / Then + Assert.Equal(0, Mass.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 = "Mass comparison should produce the expected result (left greater than right)")] + public void MassComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Mass left = Mass.FromKilograms(4567.0); + Mass right = Mass.FromKilograms(1234.0); + + // When / Then + Assert.Equal(1, Mass.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 = "Mass comparison should produce the expected result (left less than right)")] + public void MassComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Mass left = Mass.FromKilograms(1234.0); + Mass right = Mass.FromKilograms(4567.0); + + // When / Then + Assert.Equal(-1, Mass.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 = "Mass equality should produce the expected result (left equal to right)")] + public void MassEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Mass left = Mass.FromKilograms(2.0); + Mass right = Mass.FromGrams(2000.0); + + // When / Then + Assert.True(Mass.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Mass equality should produce the expected result (left not equal to right)")] + public void MassEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Mass left = Mass.FromKilograms(2.0); + Mass right = Mass.FromGrams(2500.0); + + // When / Then + Assert.False(Mass.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Mass.ToString should produce the expected result")] + public void MassToStringShouldProduceExpectedResult() + { + // Given + Mass mass = Mass.FromGrams(1000.0); // 1 kg + + // When / Then + Assert.Equal("1,000.000 g", $"{mass:g3}"); + Assert.Equal("1.000 kg", $"{mass:kg3}"); + Assert.Equal("1,000,000.000 mg", $"{mass:mg3}"); + Assert.Equal("0.001 Mg", $"{mass:Mg3}"); + Assert.Equal("0.001 t", $"{mass:t3}"); + Assert.Equal("2.205 lb", $"{mass:lb3}"); + Assert.Equal("35.274 oz", $"{mass:oz3}"); + } + + [Fact(DisplayName = "Mass.ToString should honor custom culture separators")] + public void MassToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Mass mass = Mass.FromGrams(1234.56); + + // When + string formatted = mass.ToString("g2", customCulture); + + // Then + // German uses '.' for thousands and ',' for decimals. + Assert.Equal("1.234,56 g", formatted); + } +} diff --git a/OnixLabs.Units/Extensions.ReadOnlySpan.cs b/OnixLabs.Units/Extensions.ReadOnlySpan.cs index 9643c46..86d028c 100644 --- a/OnixLabs.Units/Extensions.ReadOnlySpan.cs +++ b/OnixLabs.Units/Extensions.ReadOnlySpan.cs @@ -48,7 +48,7 @@ internal static class ReadOnlySpanExtensions 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.None, CultureInfo.InvariantCulture, out int scale) + return int.TryParse(scaleCharacters, NumberStyles.Integer, CultureInfo.InvariantCulture, out int scale) ? (specifier, scale) : throw new FormatException("Scale must contain only decimal digits."); } From 0d789c0e9363823721289c64ca58a0acec3d6b79 Mon Sep 17 00:00:00 2001 From: matthew Date: Tue, 18 Nov 2025 13:40:30 +0000 Subject: [PATCH 14/23] Added Force unit and unit tests. --- OnixLabs.Units.UnitTests/ForceTests.cs | 545 ++++++++++++++++++ OnixLabs.Units/Force.Arithmetic.Addition.cs | 42 ++ OnixLabs.Units/Force.Arithmetic.Division.cs | 42 ++ .../Force.Arithmetic.Multiplication.cs | 42 ++ .../Force.Arithmetic.Subtraction.cs | 42 ++ OnixLabs.Units/Force.Comparable.cs | 80 +++ OnixLabs.Units/Force.Constants.cs | 50 ++ OnixLabs.Units/Force.Equatable.cs | 64 ++ OnixLabs.Units/Force.Format.cs | 32 + OnixLabs.Units/Force.From.cs | 200 +++++++ OnixLabs.Units/Force.To.cs | 89 +++ OnixLabs.Units/Force.cs | 167 ++++++ onixlabs-dotnet.sln.DotSettings | 2 + 13 files changed, 1397 insertions(+) create mode 100644 OnixLabs.Units.UnitTests/ForceTests.cs create mode 100644 OnixLabs.Units/Force.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Force.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Force.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Force.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Force.Comparable.cs create mode 100644 OnixLabs.Units/Force.Constants.cs create mode 100644 OnixLabs.Units/Force.Equatable.cs create mode 100644 OnixLabs.Units/Force.Format.cs create mode 100644 OnixLabs.Units/Force.From.cs create mode 100644 OnixLabs.Units/Force.To.cs create mode 100644 OnixLabs.Units/Force.cs diff --git a/OnixLabs.Units.UnitTests/ForceTests.cs b/OnixLabs.Units.UnitTests/ForceTests.cs new file mode 100644 index 0000000..e47f795 --- /dev/null +++ b/OnixLabs.Units.UnitTests/ForceTests.cs @@ -0,0 +1,545 @@ +// 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; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class ForceTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+42; + + [Fact(DisplayName = "Force.Zero should produce the expected result")] + public void ForceZeroShouldProduceExpectedResult() + { + // Given / When + Force force = Force.Zero; + + // Then + Assert.Equal(0.0, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromYoctoNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void ForceFromYoctoNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromYoctoNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromZeptoNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e3)] + [InlineData(2.5, 2.5e3)] + public void ForceFromZeptoNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromZeptoNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromAttoNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void ForceFromAttoNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromAttoNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromFemtoNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e9)] + [InlineData(2.5, 2.5e9)] + public void ForceFromFemtoNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromFemtoNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromPicoNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void ForceFromPicoNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromPicoNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromNanoNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e15)] + [InlineData(2.5, 2.5e15)] + public void ForceFromNanoNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromNanoNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromMicroNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void ForceFromMicroNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromMicroNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromMilliNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e21)] + [InlineData(2.5, 2.5e21)] + public void ForceFromMilliNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromMilliNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void ForceFromNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromKiloNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void ForceFromKiloNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromKiloNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromMegaNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void ForceFromMegaNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromMegaNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromGigaNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e33)] + [InlineData(2.5, 2.5e33)] + public void ForceFromGigaNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromGigaNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromTeraNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void ForceFromTeraNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromTeraNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromPetaNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e39)] + [InlineData(2.5, 2.5e39)] + public void ForceFromPetaNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromPetaNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromExaNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void ForceFromExaNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromExaNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromZettaNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e45)] + [InlineData(2.5, 2.5e45)] + public void ForceFromZettaNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromZettaNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromYottaNewtons should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void ForceFromYottaNewtonsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromYottaNewtons(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromDynes should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e19)] + [InlineData(2.5, 2.5e19)] + public void ForceFromDynesShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromDynes(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromKilogramForce should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.80665e24)] + [InlineData(2.5, 2.4516625e25)] + public void ForceFromKilogramForceShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromKilogramForce(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromGramForce should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.80665e21)] + [InlineData(2.5, 2.4516625e22)] + public void ForceFromGramForceShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromGramForce(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromTonneForce should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.80665e27)] + [InlineData(2.5, 2.4516625e28)] + public void ForceFromTonneForceShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromTonneForce(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromPoundForce should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.4482216152605e24)] + [InlineData(2.5, 1.112055403815125e25)] + public void ForceFromPoundForceShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromPoundForce(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromOunceForce should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.780138850953781e23)] + [InlineData(2.5, 6.950347127384452e23)] + public void ForceFromOunceForceShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromOunceForce(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromPoundals should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.38255e23)] + [InlineData(2.5, 3.456375e23)] + public void ForceFromPoundalsShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromPoundals(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromShortTonForce should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 8.896443230521e27)] + [InlineData(2.5, 2.22411080763025e28)] + public void ForceFromShortTonForceShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromShortTonForce(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Theory(DisplayName = "Force.FromLongTonForce should produce the expected YoctoNewtons")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.964016418183519e27)] + [InlineData(2.5, 2.4910041045458797e28)] + public void ForceFromLongTonForceShouldProduceExpectedYoctoNewtons(double value, double expectedYoctoNewtons) + { + // Given / When + Force force = Force.FromLongTonForce(value); + + // Then + Assert.Equal(expectedYoctoNewtons, force.YoctoNewtons, Tolerance); + } + + [Fact(DisplayName = "Force.Add should produce the expected result")] + public void ForceAddShouldProduceExpectedValue() + { + // Given + Force left = Force.FromNewtons(1500.0); + Force right = Force.FromNewtons(500.0); + + // When + Force result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.Newtons, Tolerance); + } + + [Fact(DisplayName = "Force.Subtract should produce the expected result")] + public void ForceSubtractShouldProduceExpectedValue() + { + // Given + Force left = Force.FromNewtons(1500.0); + Force right = Force.FromNewtons(400.0); + + // When + Force result = left.Subtract(right); + + // Then + Assert.Equal(1100.0, result.Newtons, Tolerance); + } + + [Fact(DisplayName = "Force.Multiply should produce the expected result")] + public void ForceMultiplyShouldProduceExpectedValue() + { + // Given + Force left = Force.FromNewtons(10.0); // 1e25 yN + Force right = Force.FromNewtons(3.0); // 3e24 yN + + // When + Force result = left.Multiply(right); // 1e25 * 3e24 = 3e49 yN + + // Then + Assert.Equal(1e25, left.YoctoNewtons, Tolerance); + Assert.Equal(3e24, right.YoctoNewtons, Tolerance); + Assert.Equal(3e49, result.YoctoNewtons, Tolerance); + Assert.Equal(3e25, result.Newtons, Tolerance); + } + + [Fact(DisplayName = "Force.Divide should produce the expected result")] + public void ForceDivideShouldProduceExpectedValue() + { + // Given + Force left = Force.FromNewtons(100.0); // 1e26 yN + Force right = Force.FromNewtons(20.0); // 2e25 yN + + // When + Force result = left.Divide(right); // 1e26 / 2e25 = 5 yN + + // Then + Assert.Equal(5.0, result.YoctoNewtons, Tolerance); + Assert.Equal(5e-24, result.Newtons, Tolerance); + } + + [Fact(DisplayName = "Force comparison should produce the expected result (left equal to right)")] + public void ForceComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Force left = Force.FromNewtons(1234.0); + Force right = Force.FromNewtons(1234.0); + + // When / Then + Assert.Equal(0, Force.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 = "Force comparison should produce the expected result (left greater than right)")] + public void ForceComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Force left = Force.FromNewtons(4567.0); + Force right = Force.FromNewtons(1234.0); + + // When / Then + Assert.Equal(1, Force.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 = "Force comparison should produce the expected result (left less than right)")] + public void ForceComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Force left = Force.FromNewtons(1234.0); + Force right = Force.FromNewtons(4567.0); + + // When / Then + Assert.Equal(-1, Force.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 = "Force equality should produce the expected result (left equal to right)")] + public void ForceEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Force left = Force.FromKiloNewtons(2.0); + Force right = Force.FromNewtons(2000.0); + + // When / Then + Assert.True(Force.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Force equality should produce the expected result (left not equal to right)")] + public void ForceEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Force left = Force.FromKiloNewtons(2.0); + Force right = Force.FromNewtons(2500.0); + + // When / Then + Assert.False(Force.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Force.ToString should produce the expected result")] + public void ForceToStringShouldProduceExpectedResult() + { + // Given + Force force = Force.FromNewtons(1000.0); + + // When / Then + Assert.Equal("1,000.000 N", $"{force:N3}"); + Assert.Equal("1.000 kN", $"{force:kN3}"); + Assert.Equal("0.001 MN", $"{force:MN3}"); + Assert.Equal("100,000,000.000 dyn", $"{force:dyn3}"); + Assert.Equal("101.972 kgf", $"{force:kgf3}"); + Assert.Equal("101,971.621 gf", $"{force:gf3}"); + Assert.Equal("224.809 lbf", $"{force:lbf3}"); + Assert.Equal("3,596.943 ozf", $"{force:ozf3}"); + } + + [Fact(DisplayName = "Force.ToString should honor custom culture separators")] + public void ForceToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Force force = Force.FromNewtons(1234.56); + + // When + string formatted = force.ToString("N2", customCulture); + + // Then + // German uses '.' for thousands and ',' for decimals. + Assert.Equal("1.234,56 N", formatted); + } +} diff --git a/OnixLabs.Units/Force.Arithmetic.Addition.cs b/OnixLabs.Units/Force.Arithmetic.Addition.cs new file mode 100644 index 0000000..b19ceed --- /dev/null +++ b/OnixLabs.Units/Force.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Force +{ + /// + /// 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 Force Add(Force left, Force right) => new(left.YoctoNewtons + right.YoctoNewtons); + + /// + /// 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 Force operator +(Force left, Force right) => Add(left, right); + + /// + /// 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 Force Add(Force other) => Add(this, other); +} diff --git a/OnixLabs.Units/Force.Arithmetic.Division.cs b/OnixLabs.Units/Force.Arithmetic.Division.cs new file mode 100644 index 0000000..2f4876b --- /dev/null +++ b/OnixLabs.Units/Force.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Force +{ + /// + /// 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 Force Divide(Force left, Force right) => new(left.YoctoNewtons / right.YoctoNewtons); + + /// + /// 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 Force operator /(Force left, Force right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value, divided by the specified other value. + public Force Divide(Force other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Force.Arithmetic.Multiplication.cs b/OnixLabs.Units/Force.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..81f7126 --- /dev/null +++ b/OnixLabs.Units/Force.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Force +{ + /// + /// 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 Force Multiply(Force left, Force right) => new(left.YoctoNewtons * right.YoctoNewtons); + + /// + /// 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 Force operator *(Force left, Force right) => Multiply(left, right); + + /// + /// Computes the product of the current value, multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value, multiplied by the specified other value. + public Force Multiply(Force other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Force.Arithmetic.Subtraction.cs b/OnixLabs.Units/Force.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..ef451a6 --- /dev/null +++ b/OnixLabs.Units/Force.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Force +{ + /// + /// 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 Force Subtract(Force left, Force right) => new(left.YoctoNewtons - right.YoctoNewtons); + + /// + /// 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 Force operator -(Force left, Force right) => Subtract(left, right); + + /// + /// Computes the difference of the specified other value, subtracted from the current value. + /// + /// The value to subtract from the current value. + /// Returns the difference of the specified other value, subtracted from the current value. + public Force Subtract(Force other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Force.Comparable.cs b/OnixLabs.Units/Force.Comparable.cs new file mode 100644 index 0000000..9daee01 --- /dev/null +++ b/OnixLabs.Units/Force.Comparable.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 OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Force +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(Force left, Force right) => left.YoctoNewtons.CompareTo(right.YoctoNewtons); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(Force other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than right-hand operand; otherwise, . + public static bool operator >(Force left, Force right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(Force left, Force right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(Force left, Force right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(Force left, Force right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Force.Constants.cs b/OnixLabs.Units/Force.Constants.cs new file mode 100644 index 0000000..81048c6 --- /dev/null +++ b/OnixLabs.Units/Force.Constants.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. + +namespace OnixLabs.Units; + +public readonly partial struct Force +{ + /// + /// Gets a zero value, equal to zero yoctonewtons. + /// + public static readonly Force Zero = new(T.Zero); + + private const string YoctoNewtonsSpecifier = "yN"; + private const string ZeptoNewtonsSpecifier = "zN"; + private const string AttoNewtonsSpecifier = "aN"; + private const string FemtoNewtonsSpecifier = "fN"; + private const string PicoNewtonsSpecifier = "pN"; + private const string NanoNewtonsSpecifier = "nN"; + private const string MicroNewtonsSpecifier = "uN"; + private const string MilliNewtonsSpecifier = "mN"; + private const string NewtonsSpecifier = "N"; + private const string KiloNewtonsSpecifier = "kN"; + private const string MegaNewtonsSpecifier = "MN"; + private const string GigaNewtonsSpecifier = "GN"; + private const string TeraNewtonsSpecifier = "TN"; + private const string PetaNewtonsSpecifier = "PN"; + private const string ExaNewtonsSpecifier = "EN"; + private const string ZettaNewtonsSpecifier = "ZN"; + private const string YottaNewtonsSpecifier = "YN"; + private const string DynesSpecifier = "dyn"; + private const string KilogramForceSpecifier = "kgf"; + private const string GramForceSpecifier = "gf"; + private const string TonneForceSpecifier = "tf"; + private const string PoundForceSpecifier = "lbf"; + private const string OunceForceSpecifier = "ozf"; + private const string PoundalsSpecifier = "pdl"; + private const string ShortTonForceSpecifier = "tonf"; + private const string LongTonForceSpecifier = "ltf"; +} diff --git a/OnixLabs.Units/Force.Equatable.cs b/OnixLabs.Units/Force.Equatable.cs new file mode 100644 index 0000000..9ada49d --- /dev/null +++ b/OnixLabs.Units/Force.Equatable.cs @@ -0,0 +1,64 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct Force +{ + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(Force left, Force right) => Equals(left.YoctoNewtons, right.YoctoNewtons); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// Returns if the current instance is equal to the specified other instance; otherwise, . + public bool Equals(Force other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Force other && Equals(other); + + /// + /// Serves as a hash code function for this instance. + /// + /// Returns a hash code for this instance. + public override int GetHashCode() => YoctoNewtons.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(Force left, Force right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(Force left, Force right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Force.Format.cs b/OnixLabs.Units/Force.Format.cs new file mode 100644 index 0000000..9aeec56 --- /dev/null +++ b/OnixLabs.Units/Force.Format.cs @@ -0,0 +1,32 @@ +// 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 Force +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written. + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format. + /// An optional object that supplies culture-specific formatting information. + /// Returns if the formatting was successful; otherwise, . + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Force.From.cs b/OnixLabs.Units/Force.From.cs new file mode 100644 index 0000000..1901458 --- /dev/null +++ b/OnixLabs.Units/Force.From.cs @@ -0,0 +1,200 @@ +// 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 Force +{ + /// + /// Creates a new instance from the specified yoctonewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromYoctoNewtons(T value) => new(value); + + /// + /// Creates a new instance from the specified zeptonewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromZeptoNewtons(T value) => new(value * T.CreateChecked(1e3)); + + /// + /// Creates a new instance from the specified attonewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromAttoNewtons(T value) => new(value * T.CreateChecked(1e6)); + + /// + /// Creates a new instance from the specified femtonewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromFemtoNewtons(T value) => new(value * T.CreateChecked(1e9)); + + /// + /// Creates a new instance from the specified piconewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromPicoNewtons(T value) => new(value * T.CreateChecked(1e12)); + + /// + /// Creates a new instance from the specified nanonewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromNanoNewtons(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from the specified micronewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromMicroNewtons(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from the specified millinewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromMilliNewtons(T value) => new(value * T.CreateChecked(1e21)); + + /// + /// Creates a new instance from the specified newtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromNewtons(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from the specified kilonewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromKiloNewtons(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from the specified meganewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromMegaNewtons(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified giganewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromGigaNewtons(T value) => new(value * T.CreateChecked(1e33)); + + /// + /// Creates a new instance from the specified teranewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromTeraNewtons(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from the specified petanewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromPetaNewtons(T value) => new(value * T.CreateChecked(1e39)); + + /// + /// Creates a new instance from the specified exanewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromExaNewtons(T value) => new(value * T.CreateChecked(1e42)); + + /// + /// Creates a new instance from the specified zettanewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromZettaNewtons(T value) => new(value * T.CreateChecked(1e45)); + + /// + /// Creates a new instance from the specified yottanewtons value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromYottaNewtons(T value) => new(value * T.CreateChecked(1e48)); + + /// + /// Creates a new instance from the specified dynes value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromDynes(T value) => new(value * T.CreateChecked(1e19)); + + /// + /// Creates a new instance from the specified kilogram-force value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromKilogramForce(T value) => new(value * T.CreateChecked(9.80665e24)); + + /// + /// Creates a new instance from the specified gram-force value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromGramForce(T value) => new(value * T.CreateChecked(9.80665e21)); + + /// + /// Creates a new instance from the specified tonne-force value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromTonneForce(T value) => new(value * T.CreateChecked(9.80665e27)); + + /// + /// Creates a new instance from the specified pound-force value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromPoundForce(T value) => new(value * T.CreateChecked(4.4482216152605e24)); + + /// + /// Creates a new instance from the specified ounce-force value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromOunceForce(T value) => new(value * T.CreateChecked(2.780138850953781e23)); + + /// + /// Creates a new instance from the specified poundals value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromPoundals(T value) => new(value * T.CreateChecked(1.38255e23)); + + /// + /// Creates a new instance from the specified US short ton-force value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromShortTonForce(T value) => new(value * T.CreateChecked(8.896443230521e27)); + + /// + /// Creates a new instance from the specified imperial long ton-force value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Force FromLongTonForce(T value) => new(value * T.CreateChecked(9.964016418183519e27)); +} diff --git a/OnixLabs.Units/Force.To.cs b/OnixLabs.Units/Force.To.cs new file mode 100644 index 0000000..caddb6d --- /dev/null +++ b/OnixLabs.Units/Force.To.cs @@ -0,0 +1,89 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Force +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(YoctoNewtonsSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => + ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: YoctoNewtonsSpecifier); + + T value = specifier switch + { + YoctoNewtonsSpecifier => YoctoNewtons, + ZeptoNewtonsSpecifier => ZeptoNewtons, + AttoNewtonsSpecifier => AttoNewtons, + FemtoNewtonsSpecifier => FemtoNewtons, + PicoNewtonsSpecifier => PicoNewtons, + NanoNewtonsSpecifier => NanoNewtons, + MicroNewtonsSpecifier => MicroNewtons, + MilliNewtonsSpecifier => MilliNewtons, + NewtonsSpecifier => Newtons, + KiloNewtonsSpecifier => KiloNewtons, + MegaNewtonsSpecifier => MegaNewtons, + GigaNewtonsSpecifier => GigaNewtons, + TeraNewtonsSpecifier => TeraNewtons, + PetaNewtonsSpecifier => PetaNewtons, + ExaNewtonsSpecifier => ExaNewtons, + ZettaNewtonsSpecifier => ZettaNewtons, + YottaNewtonsSpecifier => YottaNewtons, + DynesSpecifier => Dynes, + KilogramForceSpecifier => KilogramForce, + GramForceSpecifier => GramForce, + TonneForceSpecifier => TonneForce, + PoundForceSpecifier => PoundForce, + OunceForceSpecifier => OunceForce, + PoundalsSpecifier => Poundals, + ShortTonForceSpecifier => ShortTonForce, + LongTonForceSpecifier => LongTonForce, + _ => throw new ArgumentException( + $"Format '{format.ToString()}' is invalid. " + + "Valid format specifiers are: " + + "yN, zN, aN, fN, pN, nN, uN, mN, N, kN, MN, GN, TN, PN, EN, ZN, YN, " + + "dyn, kgf, gf, tf, lbf, ozf, pdl, tonf, ltf. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Force.cs b/OnixLabs.Units/Force.cs new file mode 100644 index 0000000..7bbbfbf --- /dev/null +++ b/OnixLabs.Units/Force.cs @@ -0,0 +1,167 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of force. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Force : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The force unit in . + private Force(T value) => YoctoNewtons = value; + + /// + /// Gets the force in yoctonewtons (yN). + /// + public T YoctoNewtons { get; } + + /// + /// Gets the force in zeptonewtons (zN). + /// + public T ZeptoNewtons => YoctoNewtons / T.CreateChecked(1e3); + + /// + /// Gets the force in attonewtons (aN). + /// + public T AttoNewtons => YoctoNewtons / T.CreateChecked(1e6); + + /// + /// Gets the force in femtonewtons (fN). + /// + public T FemtoNewtons => YoctoNewtons / T.CreateChecked(1e9); + + /// + /// Gets the force in piconewtons (pN). + /// + public T PicoNewtons => YoctoNewtons / T.CreateChecked(1e12); + + /// + /// Gets the force in nanonewtons (nN). + /// + public T NanoNewtons => YoctoNewtons / T.CreateChecked(1e15); + + /// + /// Gets the force in micronewtons (µN). + /// + public T MicroNewtons => YoctoNewtons / T.CreateChecked(1e18); + + /// + /// Gets the force in millinewtons (mN). + /// + public T MilliNewtons => YoctoNewtons / T.CreateChecked(1e21); + + /// + /// Gets the force in newtons (N). + /// + public T Newtons => YoctoNewtons / T.CreateChecked(1e24); + + /// + /// Gets the force in kilonewtons (kN). + /// + public T KiloNewtons => YoctoNewtons / T.CreateChecked(1e27); + + /// + /// Gets the force in meganewtons (MN). + /// + public T MegaNewtons => YoctoNewtons / T.CreateChecked(1e30); + + /// + /// Gets the force in giganewtons (GN). + /// + public T GigaNewtons => YoctoNewtons / T.CreateChecked(1e33); + + /// + /// Gets the force in teranewtons (TN). + /// + public T TeraNewtons => YoctoNewtons / T.CreateChecked(1e36); + + /// + /// Gets the force in petanewtons (PN). + /// + public T PetaNewtons => YoctoNewtons / T.CreateChecked(1e39); + + /// + /// Gets the force in exanewtons (EN). + /// + public T ExaNewtons => YoctoNewtons / T.CreateChecked(1e42); + + /// + /// Gets the force in zettanewtons (ZN). + /// + public T ZettaNewtons => YoctoNewtons / T.CreateChecked(1e45); + + /// + /// Gets the force in yottanewtons (YN). + /// + public T YottaNewtons => YoctoNewtons / T.CreateChecked(1e48); + + /// + /// Gets the force in dynes (dyn). + /// + public T Dynes => YoctoNewtons / T.CreateChecked(1e19); + + /// + /// Gets the force in kilogram-force (kgf). + /// + public T KilogramForce => YoctoNewtons / T.CreateChecked(9.80665e24); + + /// + /// Gets the force in gram-force (gf). + /// + public T GramForce => YoctoNewtons / T.CreateChecked(9.80665e21); + + /// + /// Gets the force in tonne-force (tf). + /// + public T TonneForce => YoctoNewtons / T.CreateChecked(9.80665e27); + + /// + /// Gets the force in pound-force (lbf). + /// + public T PoundForce => YoctoNewtons / T.CreateChecked(4.4482216152605e24); + + /// + /// Gets the force in ounce-force (ozf). + /// + public T OunceForce => YoctoNewtons / T.CreateChecked(2.780138850953781e23); + + /// + /// Gets the force in poundals (pdl). + /// + public T Poundals => YoctoNewtons / T.CreateChecked(1.38255e23); + + /// + /// Gets the force in US short ton-force (tonf). + /// + public T ShortTonForce => YoctoNewtons / T.CreateChecked(8.896443230521e27); + + /// + /// Gets the force in imperial long ton-force (ltf). + /// + public T LongTonForce => YoctoNewtons / T.CreateChecked(9.964016418183519e27); +} diff --git a/onixlabs-dotnet.sln.DotSettings b/onixlabs-dotnet.sln.DotSettings index 60f2b83..dbd3e21 100644 --- a/onixlabs-dotnet.sln.DotSettings +++ b/onixlabs-dotnet.sln.DotSettings @@ -18,6 +18,7 @@ limitations under the License. True True True + True True True True @@ -56,6 +57,7 @@ limitations under the License. True True True + True True True True From c1cd5b9fe76194e2928a7ebeed2b293edf20277f Mon Sep 17 00:00:00 2001 From: matthew Date: Wed, 19 Nov 2025 00:06:08 +0000 Subject: [PATCH 15/23] Added Area unit and unit tests. --- OnixLabs.Playground/Program.cs | 5 + OnixLabs.Units.UnitTests/AreaTests.cs | 558 ++++++++++++++++++ OnixLabs.Units/Area.Arithmetic.Addition.cs | 42 ++ OnixLabs.Units/Area.Arithmetic.Division.cs | 42 ++ .../Area.Arithmetic.Multiplication.cs | 42 ++ OnixLabs.Units/Area.Arithmetic.Subtraction.cs | 42 ++ OnixLabs.Units/Area.Comparable.cs | 95 +++ OnixLabs.Units/Area.Constants.cs | 51 ++ OnixLabs.Units/Area.Equatable.cs | 75 +++ OnixLabs.Units/Area.Format.cs | 32 + OnixLabs.Units/Area.From.cs | 207 +++++++ OnixLabs.Units/Area.To.cs | 89 +++ OnixLabs.Units/Area.cs | 172 ++++++ OnixLabs.Units/Distance.cs | 1 + 14 files changed, 1453 insertions(+) create mode 100644 OnixLabs.Units.UnitTests/AreaTests.cs create mode 100644 OnixLabs.Units/Area.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Area.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Area.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Area.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Area.Comparable.cs create mode 100644 OnixLabs.Units/Area.Constants.cs create mode 100644 OnixLabs.Units/Area.Equatable.cs create mode 100644 OnixLabs.Units/Area.Format.cs create mode 100644 OnixLabs.Units/Area.From.cs create mode 100644 OnixLabs.Units/Area.To.cs create mode 100644 OnixLabs.Units/Area.cs diff --git a/OnixLabs.Playground/Program.cs b/OnixLabs.Playground/Program.cs index d629839..5f08a64 100644 --- a/OnixLabs.Playground/Program.cs +++ b/OnixLabs.Playground/Program.cs @@ -12,11 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; +using OnixLabs.Units; + namespace OnixLabs.Playground; internal static class Program { private static void Main() { + Area area = Area.FromSquareMeters(567); + Console.WriteLine($"{area:sqin0}"); } } diff --git a/OnixLabs.Units.UnitTests/AreaTests.cs b/OnixLabs.Units.UnitTests/AreaTests.cs new file mode 100644 index 0000000..45da648 --- /dev/null +++ b/OnixLabs.Units.UnitTests/AreaTests.cs @@ -0,0 +1,558 @@ +// 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; +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+85; + + [Fact(DisplayName = "Area.Empty should produce the expected result")] + public void AreaEmptyShouldProduceExpectedResult() + { + // Given / When + Area area = Area.Empty; + + // Then + Assert.Equal(0.0, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareYoctoMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void AreaFromSquareYoctoMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareYoctoMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareZeptoMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void AreaFromSquareZeptoMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareZeptoMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareAttoMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void AreaFromSquareAttoMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareAttoMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareFemtoMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void AreaFromSquareFemtoMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareFemtoMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquarePicoMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void AreaFromSquarePicoMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquarePicoMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareNanoMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void AreaFromSquareNanoMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareNanoMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMicroMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void AreaFromSquareMicroMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareMicroMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMilliMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void AreaFromSquareMilliMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareMilliMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareCentiMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e44)] + [InlineData(2.5, 2.5e44)] + public void AreaFromSquareCentiMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareCentiMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareDeciMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e46)] + [InlineData(2.5, 2.5e46)] + public void AreaFromSquareDeciMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareDeciMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void AreaFromSquareMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareDecaMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e50)] + [InlineData(2.5, 2.5e50)] + public void AreaFromSquareDecaMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareDecaMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareHectoMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e52)] + [InlineData(2.5, 2.5e52)] + public void AreaFromSquareHectoMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareHectoMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareKiloMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e54)] + [InlineData(2.5, 2.5e54)] + public void AreaFromSquareKiloMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareKiloMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMegaMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e60)] + [InlineData(2.5, 2.5e60)] + public void AreaFromSquareMegaMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareMegaMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareGigaMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e66)] + [InlineData(2.5, 2.5e66)] + public void AreaFromSquareGigaMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareGigaMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareTeraMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e72)] + [InlineData(2.5, 2.5e72)] + public void AreaFromSquareTeraMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareTeraMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquarePetaMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e78)] + [InlineData(2.5, 2.5e78)] + public void AreaFromSquarePetaMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquarePetaMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareExaMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e84)] + [InlineData(2.5, 2.5e84)] + public void AreaFromSquareExaMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareExaMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareZettaMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e90)] + [InlineData(2.5, 2.5e90)] + public void AreaFromSquareZettaMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareZettaMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareYottaMeters should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e96)] + [InlineData(2.5, 2.5e96)] + public void AreaFromSquareYottaMetersShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareYottaMeters(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareInches should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 6.4516e44)] + [InlineData(2.5, 1.6129e45)] + public void AreaFromSquareInchesShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareInches(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareFeet should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.290304e46)] + [InlineData(2.5, 2.322576e47)] + public void AreaFromSquareFeetShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareFeet(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareYards should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 8.3612736e47)] + [InlineData(2.5, 2.0903184e48)] + public void AreaFromSquareYardsShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareYards(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromSquareMiles should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.589988110336e54)] + [InlineData(2.5, 6.47497027584e54)] + public void AreaFromSquareMilesShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromSquareMiles(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromAcres should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.0468564224e51)] + [InlineData(2.5, 1.0117141056e52)] + public void AreaFromAcresShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromAcres(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Area.FromHectares should produce the expected SquareYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e52)] + [InlineData(2.5, 2.5e52)] + public void AreaFromHectaresShouldProduceExpectedSquareYoctoMeters(double value, double expectedSquareYoctoMeters) + { + // Given / When + Area area = Area.FromHectares(value); + + // Then + Assert.Equal(expectedSquareYoctoMeters, area.SquareYoctoMeters, 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); // 1e49 sqym + Area right = Area.FromSquareMeters(3.0); // 3e48 sqym + + // When + Area result = left.Multiply(right); // 1e49 * 3e48 = 3e97 sqym + + // Then + Assert.Equal(1e49, left.SquareYoctoMeters, Tolerance); + Assert.Equal(3e48, right.SquareYoctoMeters, Tolerance); + Assert.Equal(3e97, result.SquareYoctoMeters, Tolerance); + } + + [Fact(DisplayName = "Area.Divide should produce the expected result")] + public void AreaDivideShouldProduceExpectedValue() + { + // Given + Area left = Area.FromSquareMeters(100.0); // 1e50 sqym + Area right = Area.FromSquareMeters(20.0); // 2e49 sqym + + // When + Area result = left.Divide(right); // 1e50 / 2e49 = 5 sqym + + // Then + Assert.Equal(5.0, result.SquareYoctoMeters, Tolerance); + Assert.Equal(5e-48, 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); // 2 km² + Area right = Area.FromSquareMeters(2_000_000.0); // 2,000,000 m² + + // 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.FromSquareMeters(2.0); + Area right = Area.FromSquareMeters(2.5); + + // 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 area = Area.FromSquareMeters(1_000_000.0); // 1 km² + + // When / Then + Assert.Equal("1,000,000.000 sqm", $"{area:sqm3}"); + Assert.Equal("1.000 sqkm", $"{area:sqkm3}"); + Assert.Equal("10,000,000,000.000 sqcm", $"{area:sqcm3}"); + Assert.Equal("1,550,003,100.006 sqin", $"{area:sqin3}"); + Assert.Equal("10,763,910.417 sqft", $"{area:sqft3}"); + Assert.Equal("1,195,990.046 sqyd", $"{area:sqyd3}"); + Assert.Equal("0.386 sqmi", $"{area:sqmi3}"); + Assert.Equal("100.000 ha", $"{area:ha3}"); + Assert.Equal("247.105 ac", $"{area:ac3}"); + } + + [Fact(DisplayName = "Area.ToString should honor custom culture separators")] + public void AreaToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Area area = Area.FromSquareMeters(1234.56); + + // When + string formatted = area.ToString("sqm2", customCulture); + + // Then + // German uses '.' for thousands and ',' for decimals. + Assert.Equal("1.234,56 sqm", formatted); + } +} diff --git a/OnixLabs.Units/Area.Arithmetic.Addition.cs b/OnixLabs.Units/Area.Arithmetic.Addition.cs new file mode 100644 index 0000000..fec35a6 --- /dev/null +++ b/OnixLabs.Units/Area.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Area +{ + /// + /// 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 Area Add(Area left, Area right) => new(left.SquareYoctoMeters + right.SquareYoctoMeters); + + /// + /// 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 Area operator +(Area left, Area right) => Add(left, right); + + /// + /// 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 Area Add(Area other) => Add(this, other); +} diff --git a/OnixLabs.Units/Area.Arithmetic.Division.cs b/OnixLabs.Units/Area.Arithmetic.Division.cs new file mode 100644 index 0000000..e08ffdf --- /dev/null +++ b/OnixLabs.Units/Area.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Area +{ + /// + /// 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 Area Divide(Area left, Area right) => new(left.SquareYoctoMeters / right.SquareYoctoMeters); + + /// + /// 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 Area operator /(Area left, Area right) => Divide(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 Area Divide(Area other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Area.Arithmetic.Multiplication.cs b/OnixLabs.Units/Area.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..edb476e --- /dev/null +++ b/OnixLabs.Units/Area.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Area +{ + /// + /// Computes the product of the specified values. + /// + /// The left-hand value to multiply. + /// The right-hand value to multiply. + /// Returns the product of the specified values. + public static Area Multiply(Area left, Area right) => new(left.SquareYoctoMeters * right.SquareYoctoMeters); + + /// + /// Computes the product of the specified values. + /// + /// The left-hand value to multiply. + /// The right-hand value to multiply. + /// Returns the product of the specified values. + public static Area operator *(Area left, Area right) => Multiply(left, right); + + /// + /// Computes the product of the current value and the specified other value. + /// + /// The value to multiply with the current value. + /// Returns the product of the current value and the specified other value. + public Area Multiply(Area other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Area.Arithmetic.Subtraction.cs b/OnixLabs.Units/Area.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..bbc1607 --- /dev/null +++ b/OnixLabs.Units/Area.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Area +{ + /// + /// 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. + public static Area Subtract(Area left, Area right) => new(left.SquareYoctoMeters - right.SquareYoctoMeters); + + /// + /// 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. + public static Area operator -(Area left, Area right) => Subtract(left, right); + + /// + /// Computes the difference between the current value and the specified other value. + /// + /// The value to subtract from the current value. + /// Returns the difference between the current value and the specified other value. + public Area Subtract(Area other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Area.Comparable.cs b/OnixLabs.Units/Area.Comparable.cs new file mode 100644 index 0000000..f8fcffa --- /dev/null +++ b/OnixLabs.Units/Area.Comparable.cs @@ -0,0 +1,95 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Area +{ + /// + /// Compares two specified values and returns an integer that indicates their relative order. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns a value that indicates the relative order of the values being compared. + /// Less than zero if is less than , + /// zero if they are equal, and greater than zero if is greater than . + /// + public static int Compare(Area left, Area right) => left.SquareYoctoMeters.CompareTo(right.SquareYoctoMeters); + + /// + /// Compares the current instance with another instance and returns an integer that indicates their relative order. + /// + /// An instance to compare with the current instance. + /// + /// Returns a value that indicates the relative order of the instances being compared. + /// Less than zero if the current instance is less than , + /// zero if they are equal, and greater than zero if the current instance is greater than . + /// + public int CompareTo(Area other) => Compare(this, other); + + /// + /// Compares the current instance with another object and returns an integer that indicates their relative order. + /// + /// An object to compare with the current instance. + /// + /// Returns a value that indicates the relative order of the objects being compared. + /// Less than zero if the current instance is less than , + /// zero if they are equal, and greater than zero if the current instance is greater than . + /// + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if is greater than ; otherwise, . + /// + public static bool operator >(Area left, Area right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if is greater than or equal to ; otherwise, . + /// + public static bool operator >=(Area left, Area right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if is less than ; otherwise, . + /// + public static bool operator <(Area left, Area right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if is less than or equal to ; otherwise, . + /// + public static bool operator <=(Area left, Area right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Area.Constants.cs b/OnixLabs.Units/Area.Constants.cs new file mode 100644 index 0000000..0033e6f --- /dev/null +++ b/OnixLabs.Units/Area.Constants.cs @@ -0,0 +1,51 @@ +// 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 +{ + /// + /// Gets an empty value. + /// + public static Area Empty => new(T.Zero); + + private const string SquareYoctoMetersSpecifier = "sqym"; + private const string SquareZeptoMetersSpecifier = "sqzm"; + private const string SquareAttoMetersSpecifier = "sqam"; + private const string SquareFemtoMetersSpecifier = "sqfm"; + private const string SquarePicoMetersSpecifier = "sqpm"; + private const string SquareNanoMetersSpecifier = "sqnm"; + private const string SquareMicroMetersSpecifier = "squm"; + private const string SquareMilliMetersSpecifier = "sqmm"; + private const string SquareCentiMetersSpecifier = "sqcm"; + private const string SquareDeciMetersSpecifier = "sqdm"; + private const string SquareMetersSpecifier = "sqm"; + private const string SquareDecaMetersSpecifier = "sqdam"; + private const string SquareHectoMetersSpecifier = "sqhm"; + private const string SquareKiloMetersSpecifier = "sqkm"; + private const string SquareMegaMetersSpecifier = "sqMm"; + private const string SquareGigaMetersSpecifier = "sqGm"; + private const string SquareTeraMetersSpecifier = "sqTm"; + private const string SquarePetaMetersSpecifier = "sqPm"; + private const string SquareExaMetersSpecifier = "sqEm"; + private const string SquareZettaMetersSpecifier = "sqZm"; + private const string SquareYottaMetersSpecifier = "sqYm"; + private const string SquareInchesSpecifier = "sqin"; + private const string SquareFeetSpecifier = "sqft"; + private const string SquareYardsSpecifier = "sqyd"; + private const string SquareMilesSpecifier = "sqmi"; + private const string AcresSpecifier = "ac"; + private const string HectaresSpecifier = "ha"; +} diff --git a/OnixLabs.Units/Area.Equatable.cs b/OnixLabs.Units/Area.Equatable.cs new file mode 100644 index 0000000..ee505c4 --- /dev/null +++ b/OnixLabs.Units/Area.Equatable.cs @@ -0,0 +1,75 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Area +{ + /// + /// Determines whether two specified values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the two specified instances are equal; otherwise, . + /// + public static bool Equals(Area left, Area right) => Equals(left.SquareYoctoMeters, right.SquareYoctoMeters); + + /// + /// Determines whether the current instance is equal to another instance. + /// + /// The other instance to compare with the current instance. + /// + /// Returns if the current instance is equal to ; otherwise, . + /// + public bool Equals(Area other) => Equals(this, other); + + /// + /// Determines whether the current instance is equal to a specified object. + /// + /// The object to compare with the current instance. + /// + /// Returns if is an and is equal to the current instance; otherwise, . + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Area other && Equals(other); + + /// + /// Returns a hash code for the current instance. + /// + /// Returns a hash code for the current instance. + public override int GetHashCode() => SquareYoctoMeters.GetHashCode(); + + /// + /// Determines whether two specified instances are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the specified instances are equal; otherwise, . + /// + public static bool operator ==(Area left, Area right) => Equals(left, right); + + /// + /// Determines whether two specified instances are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the specified instances are not equal; otherwise, . + /// + public static bool operator !=(Area left, Area right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Area.Format.cs b/OnixLabs.Units/Area.Format.cs new file mode 100644 index 0000000..9163d54 --- /dev/null +++ b/OnixLabs.Units/Area.Format.cs @@ -0,0 +1,32 @@ +// 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 +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + 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..45e9a82 --- /dev/null +++ b/OnixLabs.Units/Area.From.cs @@ -0,0 +1,207 @@ +// 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 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); + + /// + /// 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(1e6)); + + /// + /// 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(1e12)); + + /// + /// 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(1e18)); + + /// + /// 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(1e24)); + + /// + /// 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(1e30)); + + /// + /// 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(1e36)); + + /// + /// 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(1e42)); + + /// + /// 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(1e44)); + + /// + /// 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(1e46)); + + /// + /// 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(1e48)); + + /// + /// 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(1e50)); + + /// + /// 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(1e52)); + + /// + /// 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(1e54)); + + /// + /// 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(1e60)); + + /// + /// 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(1e66)); + + /// + /// 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(1e72)); + + /// + /// 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(1e78)); + + /// + /// 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(1e84)); + + /// + /// 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(1e90)); + + /// + /// 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(1e96)); + + /// + /// 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.4516e44)); + + /// + /// 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.290304e46)); + + /// + /// 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.3612736e47)); + + /// + /// 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.589988110336e54)); + + /// + /// Creates a new instance from the specified acres value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromAcres(T value) => new(value * T.CreateChecked(4.0468564224e51)); + + /// + /// Creates a new instance from the specified hectares value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Area FromHectares(T value) => new(value * T.CreateChecked(1e52)); +} diff --git a/OnixLabs.Units/Area.To.cs b/OnixLabs.Units/Area.To.cs new file mode 100644 index 0000000..f3b7408 --- /dev/null +++ b/OnixLabs.Units/Area.To.cs @@ -0,0 +1,89 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Area +{ + /// + /// Returns a string representation of the current instance using the default format. + /// + /// Returns a string representation of the current instance. + public override string ToString() => ToString(SquareYoctoMetersSpecifier); + + /// + /// Returns a string representation of the current instance using the specified format and format provider. + /// + /// A standard or custom format string. + /// An object that provides culture-specific formatting information. + /// Returns a string representation of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => + ToString(format.AsSpan(), formatProvider); + + /// + /// Returns a string representation of the current instance using the specified format and format provider. + /// + /// A read-only span of characters representing a standard or custom format string. + /// An object that provides culture-specific formatting information. + /// Returns a string representation of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: SquareYoctoMetersSpecifier); + + T value = specifier switch + { + SquareYoctoMetersSpecifier => SquareYoctoMeters, + SquareZeptoMetersSpecifier => SquareZeptoMeters, + SquareAttoMetersSpecifier => SquareAttoMeters, + SquareFemtoMetersSpecifier => SquareFemtoMeters, + SquarePicoMetersSpecifier => SquarePicoMeters, + SquareNanoMetersSpecifier => SquareNanoMeters, + SquareMicroMetersSpecifier => SquareMicroMeters, + SquareMilliMetersSpecifier => SquareMilliMeters, + SquareCentiMetersSpecifier => SquareCentiMeters, + SquareDeciMetersSpecifier => SquareDeciMeters, + SquareMetersSpecifier => SquareMeters, + SquareDecaMetersSpecifier => SquareDecaMeters, + SquareHectoMetersSpecifier => SquareHectoMeters, + SquareKiloMetersSpecifier => SquareKiloMeters, + SquareMegaMetersSpecifier => SquareMegaMeters, + SquareGigaMetersSpecifier => SquareGigaMeters, + SquareTeraMetersSpecifier => SquareTeraMeters, + SquarePetaMetersSpecifier => SquarePetaMeters, + SquareExaMetersSpecifier => SquareExaMeters, + SquareZettaMetersSpecifier => SquareZettaMeters, + SquareYottaMetersSpecifier => SquareYottaMeters, + SquareInchesSpecifier => SquareInches, + SquareFeetSpecifier => SquareFeet, + SquareYardsSpecifier => SquareYards, + SquareMilesSpecifier => SquareMiles, + AcresSpecifier => Acres, + HectaresSpecifier => Hectares, + _ => throw new ArgumentException( + $"Format '{format.ToString()}' is invalid. Valid format specifiers are: " + + "sqym, sqzm, sqam, sqfm, sqpm, sqnm, squm, sqmm, sqcm, sqdm, sqm, sqdam, sqhm, sqkm, " + + "sqMm, sqGm, sqTm, sqPm, sqEm, sqZm, sqYm, sqin, sqft, sqyd, sqmi, ac, ha. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Area.cs b/OnixLabs.Units/Area.cs new file mode 100644 index 0000000..4daa74a --- /dev/null +++ b/OnixLabs.Units/Area.cs @@ -0,0 +1,172 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of area. +/// +/// The underlying type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Area : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The area unit in . + private Area(T value) => SquareYoctoMeters = value; + + /// + /// Gets the area in square yoctometers (sqym). + /// + public T SquareYoctoMeters { get; } + + /// + /// Gets the area in square zeptometers (sqzm). + /// + public T SquareZeptoMeters => SquareYoctoMeters / T.CreateChecked(1e6); + + /// + /// Gets the area in square attometers (sqam). + /// + public T SquareAttoMeters => SquareYoctoMeters / T.CreateChecked(1e12); + + /// + /// Gets the area in square femtometers (sqfm). + /// + public T SquareFemtoMeters => SquareYoctoMeters / T.CreateChecked(1e18); + + /// + /// Gets the area in square picometers (sqpm). + /// + public T SquarePicoMeters => SquareYoctoMeters / T.CreateChecked(1e24); + + /// + /// Gets the area in square nanometers (sqnm). + /// + public T SquareNanoMeters => SquareYoctoMeters / T.CreateChecked(1e30); + + /// + /// Gets the area in square micrometers (squm). + /// + public T SquareMicroMeters => SquareYoctoMeters / T.CreateChecked(1e36); + + /// + /// Gets the area in square millimeters (sqmm). + /// + public T SquareMilliMeters => SquareYoctoMeters / T.CreateChecked(1e42); + + /// + /// Gets the area in square centimeters (sqcm). + /// + public T SquareCentiMeters => SquareYoctoMeters / T.CreateChecked(1e44); + + /// + /// Gets the area in square decimeters (sqdm). + /// + public T SquareDeciMeters => SquareYoctoMeters / T.CreateChecked(1e46); + + /// + /// Gets the area in square meters (sqm). + /// + public T SquareMeters => SquareYoctoMeters / T.CreateChecked(1e48); + + /// + /// Gets the area in square decameters (sqdam). + /// + public T SquareDecaMeters => SquareYoctoMeters / T.CreateChecked(1e50); + + /// + /// Gets the area in square hectometers (sqhm). + /// + public T SquareHectoMeters => SquareYoctoMeters / T.CreateChecked(1e52); + + /// + /// Gets the area in square kilometers (sqkm). + /// + public T SquareKiloMeters => SquareYoctoMeters / T.CreateChecked(1e54); + + /// + /// Gets the area in square megameters (sqMm). + /// + public T SquareMegaMeters => SquareYoctoMeters / T.CreateChecked(1e60); + + /// + /// Gets the area in square gigameters (sqGm). + /// + public T SquareGigaMeters => SquareYoctoMeters / T.CreateChecked(1e66); + + /// + /// Gets the area in square terameters (sqTm). + /// + public T SquareTeraMeters => SquareYoctoMeters / T.CreateChecked(1e72); + + /// + /// Gets the area in square petameters (sqPm). + /// + public T SquarePetaMeters => SquareYoctoMeters / T.CreateChecked(1e78); + + /// + /// Gets the area in square exameters (sqEm). + /// + public T SquareExaMeters => SquareYoctoMeters / T.CreateChecked(1e84); + + /// + /// Gets the area in square zettameters (sqZm). + /// + public T SquareZettaMeters => SquareYoctoMeters / T.CreateChecked(1e90); + + /// + /// Gets the area in square yottameters (sqYm). + /// + public T SquareYottaMeters => SquareYoctoMeters / T.CreateChecked(1e96); + + /// + /// Gets the area in square inches (sqin). + /// + public T SquareInches => SquareYoctoMeters / T.CreateChecked(6.4516e44); + + /// + /// Gets the area in square feet (sqft). + /// + public T SquareFeet => SquareYoctoMeters / T.CreateChecked(9.290304e46); + + /// + /// Gets the area in square yards (sqyd). + /// + public T SquareYards => SquareYoctoMeters / T.CreateChecked(8.3612736e47); + + /// + /// Gets the area in square miles (sqmi). + /// + public T SquareMiles => SquareYoctoMeters / T.CreateChecked(2.589988110336e54); + + /// + /// Gets the area in acres (ac). + /// + public T Acres => SquareYoctoMeters / T.CreateChecked(4.0468564224e51); + + /// + /// Gets the area in hectares (ha). + /// + public T Hectares => SquareYoctoMeters / T.CreateChecked(1e52); +} diff --git a/OnixLabs.Units/Distance.cs b/OnixLabs.Units/Distance.cs index 794d0e9..d628493 100644 --- a/OnixLabs.Units/Distance.cs +++ b/OnixLabs.Units/Distance.cs @@ -175,6 +175,7 @@ namespace OnixLabs.Units; /// public T Yards => QuectoMeters / T.CreateChecked(0.9144e30); + // ReSharper disable once GrammarMistakeInComment /// /// Gets the distance in Miles (mi). /// From 3a3a79106fa453bd030ee61f156605bb4b6cf61a Mon Sep 17 00:00:00 2001 From: matthew Date: Wed, 19 Nov 2025 00:06:28 +0000 Subject: [PATCH 16/23] Removed playground test code. --- OnixLabs.Playground/Program.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/OnixLabs.Playground/Program.cs b/OnixLabs.Playground/Program.cs index 5f08a64..d629839 100644 --- a/OnixLabs.Playground/Program.cs +++ b/OnixLabs.Playground/Program.cs @@ -12,16 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using OnixLabs.Units; - namespace OnixLabs.Playground; internal static class Program { private static void Main() { - Area area = Area.FromSquareMeters(567); - Console.WriteLine($"{area:sqin0}"); } } From 84decc34d42f7f4bc000394add452abd2121ded8 Mon Sep 17 00:00:00 2001 From: matthew Date: Wed, 19 Nov 2025 14:00:09 +0000 Subject: [PATCH 17/23] Added Volume unit and unit tests. --- OnixLabs.Units.UnitTests/AreaTests.cs | 2 +- OnixLabs.Units.UnitTests/VolumeTests.cs | 575 ++++++++++++++++++ OnixLabs.Units/Area.Constants.cs | 4 +- OnixLabs.Units/Force.Constants.cs | 2 +- OnixLabs.Units/Volume.Arithmetic.Addition.cs | 42 ++ OnixLabs.Units/Volume.Arithmetic.Division.cs | 42 ++ .../Volume.Arithmetic.Multiplication.cs | 42 ++ .../Volume.Arithmetic.Subtraction.cs | 42 ++ OnixLabs.Units/Volume.Comparable.cs | 81 +++ OnixLabs.Units/Volume.Constants.cs | 62 ++ OnixLabs.Units/Volume.Equatable.cs | 65 ++ OnixLabs.Units/Volume.Format.cs | 32 + OnixLabs.Units/Volume.From.cs | 249 ++++++++ OnixLabs.Units/Volume.To.cs | 94 +++ OnixLabs.Units/Volume.cs | 202 ++++++ onixlabs-dotnet.sln.DotSettings | 11 + 16 files changed, 1543 insertions(+), 4 deletions(-) create mode 100644 OnixLabs.Units.UnitTests/VolumeTests.cs create mode 100644 OnixLabs.Units/Volume.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Volume.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Volume.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Volume.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Volume.Comparable.cs create mode 100644 OnixLabs.Units/Volume.Constants.cs create mode 100644 OnixLabs.Units/Volume.Equatable.cs create mode 100644 OnixLabs.Units/Volume.Format.cs create mode 100644 OnixLabs.Units/Volume.From.cs create mode 100644 OnixLabs.Units/Volume.To.cs create mode 100644 OnixLabs.Units/Volume.cs diff --git a/OnixLabs.Units.UnitTests/AreaTests.cs b/OnixLabs.Units.UnitTests/AreaTests.cs index 45da648..6686bc7 100644 --- a/OnixLabs.Units.UnitTests/AreaTests.cs +++ b/OnixLabs.Units.UnitTests/AreaTests.cs @@ -26,7 +26,7 @@ public sealed class AreaTests public void AreaEmptyShouldProduceExpectedResult() { // Given / When - Area area = Area.Empty; + Area area = Area.Zero; // Then Assert.Equal(0.0, area.SquareYoctoMeters, Tolerance); diff --git a/OnixLabs.Units.UnitTests/VolumeTests.cs b/OnixLabs.Units.UnitTests/VolumeTests.cs new file mode 100644 index 0000000..f6dced0 --- /dev/null +++ b/OnixLabs.Units.UnitTests/VolumeTests.cs @@ -0,0 +1,575 @@ +// 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; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class VolumeTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+120; + + [Fact(DisplayName = "Volume.Zero should produce the expected result")] + public void VolumeZeroShouldProduceExpectedResult() + { + // Given / When + Volume volume = Volume.Zero; + + // Then + Assert.Equal(0.0, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicYoctoMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void VolumeFromCubicYoctoMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicYoctoMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicZeptoMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e9)] + [InlineData(2.5, 2.5e9)] + public void VolumeFromCubicZeptoMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicZeptoMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicAttoMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void VolumeFromCubicAttoMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicAttoMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicFemtoMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void VolumeFromCubicFemtoMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicFemtoMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicPicoMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void VolumeFromCubicPicoMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicPicoMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicNanoMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e45)] + [InlineData(2.5, 2.5e45)] + public void VolumeFromCubicNanoMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicNanoMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicMicroMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e54)] + [InlineData(2.5, 2.5e54)] + public void VolumeFromCubicMicroMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicMicroMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicMilliMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e63)] + [InlineData(2.5, 2.5e63)] + public void VolumeFromCubicMilliMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicMilliMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicCentiMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e66)] + [InlineData(2.5, 2.5e66)] + public void VolumeFromCubicCentiMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicCentiMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicDeciMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e69)] + [InlineData(2.5, 2.5e69)] + public void VolumeFromCubicDeciMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicDeciMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e72)] + [InlineData(2.5, 2.5e72)] + public void VolumeFromCubicMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicDecaMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e75)] + [InlineData(2.5, 2.5e75)] + public void VolumeFromCubicDecaMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicDecaMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicHectoMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e78)] + [InlineData(2.5, 2.5e78)] + public void VolumeFromCubicHectoMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicHectoMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicKiloMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e81)] + [InlineData(2.5, 2.5e81)] + public void VolumeFromCubicKiloMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicKiloMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicMegaMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e90)] + [InlineData(2.5, 2.5e90)] + public void VolumeFromCubicMegaMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicMegaMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicGigaMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e99)] + [InlineData(2.5, 2.5e99)] + public void VolumeFromCubicGigaMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicGigaMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicTeraMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e108)] + [InlineData(2.5, 2.5e108)] + public void VolumeFromCubicTeraMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicTeraMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicPetaMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e117)] + [InlineData(2.5, 2.5e117)] + public void VolumeFromCubicPetaMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicPetaMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicExaMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e126)] + [InlineData(2.5, 2.5e126)] + public void VolumeFromCubicExaMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicExaMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicZettaMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e135)] + [InlineData(2.5, 2.5e135)] + public void VolumeFromCubicZettaMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicZettaMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicYottaMeters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e144)] + [InlineData(2.5, 2.5e144)] + public void VolumeFromCubicYottaMetersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicYottaMeters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromLiters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] // 0 L + [InlineData(1.0, 1e69)] // 1 L = 1e-3 m^3 + [InlineData(2.5, 2.5e69)] // 2.5 L + public void VolumeFromLitersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromLiters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromMilliLiters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] // 0 mL + [InlineData(1.0, 1e66)] // 1 mL = 1 cm^3 = 1e-6 m^3 + [InlineData(250.0, 2.5e68)] // 250 mL + public void VolumeFromMilliLitersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromMilliLiters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCentiLiters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] // 0 cL + [InlineData(1.0, 1e67)] // 1 cL = 10 mL + [InlineData(250.0, 2.5e69)] // 250 cL = 2.5 L + public void VolumeFromCentiLitersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCentiLiters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromDeciLiters should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] // 0 dL + [InlineData(1.0, 1e68)] // 1 dL = 0.1 L + [InlineData(25.0, 2.5e69)] // 25 dL = 2.5 L + public void VolumeFromDeciLitersShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromDeciLiters(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicInches should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.6387064e67)] // 1 in^3 = (0.0254 m)^3 + [InlineData(2.5, 2.5 * 1.6387064e67)] + public void VolumeFromCubicInchesShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicInches(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicFeet should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.8316846592e70)] // 1 ft^3 = (0.3048 m)^3 + [InlineData(2.5, 2.5 * 2.8316846592e70)] + public void VolumeFromCubicFeetShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicFeet(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCubicYards should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 7.64554857984e71)] // 1 yd^3 = (0.9144 m)^3 + [InlineData(2.5, 2.5 * 7.64554857984e71)] + public void VolumeFromCubicYardsShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCubicYards(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromFluidOunces should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.95735295625e67)] // 1 US fl oz ≈ 29.5735295625 mL + [InlineData(2.5, 2.5 * 2.95735295625e67)] + public void VolumeFromFluidOuncesShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromFluidOunces(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromCups should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.365882365e68)] // 1 cup = 8 fl oz + [InlineData(2.5, 2.5 * 2.365882365e68)] + public void VolumeFromCupsShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromCups(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromPints should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.73176473e68)] // 1 pint = 16 fl oz + [InlineData(2.5, 2.5 * 4.73176473e68)] + public void VolumeFromPintsShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromPints(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromQuarts should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.46352946e68)] // 1 quart = 32 fl oz + [InlineData(2.5, 2.5 * 9.46352946e68)] + public void VolumeFromQuartsShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromQuarts(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Theory(DisplayName = "Volume.FromGallons should produce the expected CubicYoctoMeters")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.785411784e69)] // 1 gallon = 128 fl oz + [InlineData(2.5, 2.5 * 3.785411784e69)] + public void VolumeFromGallonsShouldProduceExpectedCubicYoctoMeters(double value, double expectedCubicYoctoMeters) + { + // Given / When + Volume volume = Volume.FromGallons(value); + + // Then + Assert.Equal(expectedCubicYoctoMeters, volume.CubicYoctoMeters, Tolerance); + } + + [Fact(DisplayName = "Volume comparison should produce the expected result (left equal to right)")] + public void VolumeComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Volume left = Volume.FromCubicMeters(1234.0); + Volume right = Volume.FromCubicMeters(1234.0); + + // When / Then + Assert.Equal(0, Volume.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 = "Volume comparison should produce the expected result (left greater than right)")] + public void VolumeComparisonShouldProduceExpectedResultLeftGreaterThanRight() + { + // Given + Volume left = Volume.FromCubicMeters(2000.0); + Volume right = Volume.FromCubicMeters(1500.0); + + // When / Then + Assert.Equal(1, Volume.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 = "Volume comparison should produce the expected result (left less than right)")] + public void VolumeComparisonShouldProduceExpectedResultLeftLessThanRight() + { + // Given + Volume left = Volume.FromCubicMeters(1500.0); + Volume right = Volume.FromCubicMeters(2000.0); + + // When / Then + Assert.Equal(-1, Volume.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 = "Volume equality should produce the expected result (left equal to right)")] + public void VolumeEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Volume left = Volume.FromCubicMeters(2.0); + Volume right = Volume.FromLiters(2000.0); + + // When / Then + Assert.True(Volume.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Volume equality should produce the expected result (left not equal to right)")] + public void VolumeEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Volume left = Volume.FromCubicMeters(2.0); + Volume right = Volume.FromLiters(2500.0); + + // When / Then + Assert.False(Volume.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Volume.ToString should produce the expected result")] + public void VolumeToStringShouldProduceExpectedResult() + { + // Given + Volume volume = Volume.FromCubicMeters(1.0); + + // When / Then + // 1 m³ = 1,000 L = 1,000,000 mL + Assert.Equal("1.000 cum", $"{volume:cum3}"); + Assert.Equal("1,000.000 l", $"{volume:l3}"); + Assert.Equal("1,000,000.000 ml", $"{volume:ml3}"); + + // Using some imperial conversions for sanity-check formatting. + Assert.Equal("35.315 cuft", $"{volume:cuft3}"); + Assert.Equal("61,023.744 cuin", $"{volume:cuin3}"); + } + + [Fact(DisplayName = "Volume.ToString should honor custom culture separators")] + public void VolumeToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Volume volume = Volume.FromLiters(1234.56); // 1,234.56 L + + // When + string formatted = volume.ToString("l2", customCulture); + + // Then (German uses '.' for thousands and ',' for decimals) + Assert.Equal("1.234,56 l", formatted); + } +} diff --git a/OnixLabs.Units/Area.Constants.cs b/OnixLabs.Units/Area.Constants.cs index 0033e6f..458dd06 100644 --- a/OnixLabs.Units/Area.Constants.cs +++ b/OnixLabs.Units/Area.Constants.cs @@ -17,9 +17,9 @@ namespace OnixLabs.Units; public readonly partial struct Area { /// - /// Gets an empty value. + /// Gets a zero 0 value. /// - public static Area Empty => new(T.Zero); + public static Area Zero => new(T.Zero); private const string SquareYoctoMetersSpecifier = "sqym"; private const string SquareZeptoMetersSpecifier = "sqzm"; diff --git a/OnixLabs.Units/Force.Constants.cs b/OnixLabs.Units/Force.Constants.cs index 81048c6..319c904 100644 --- a/OnixLabs.Units/Force.Constants.cs +++ b/OnixLabs.Units/Force.Constants.cs @@ -17,7 +17,7 @@ namespace OnixLabs.Units; public readonly partial struct Force { /// - /// Gets a zero value, equal to zero yoctonewtons. + /// Gets a zero 0 value. /// public static readonly Force Zero = new(T.Zero); diff --git a/OnixLabs.Units/Volume.Arithmetic.Addition.cs b/OnixLabs.Units/Volume.Arithmetic.Addition.cs new file mode 100644 index 0000000..387448d --- /dev/null +++ b/OnixLabs.Units/Volume.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Volume +{ + /// + /// 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 Volume Add(Volume left, Volume right) => new(left.CubicYoctoMeters + right.CubicYoctoMeters); + + /// + /// 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 Volume operator +(Volume left, Volume right) => Add(left, right); + + /// + /// 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 Volume Add(Volume other) => Add(this, other); +} diff --git a/OnixLabs.Units/Volume.Arithmetic.Division.cs b/OnixLabs.Units/Volume.Arithmetic.Division.cs new file mode 100644 index 0000000..2653139 --- /dev/null +++ b/OnixLabs.Units/Volume.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Volume +{ + /// + /// 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 Volume Divide(Volume left, Volume right) => new(left.CubicYoctoMeters / right.CubicYoctoMeters); + + /// + /// 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 Volume operator /(Volume left, Volume right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value divided by the specified other value. + public Volume Divide(Volume other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Volume.Arithmetic.Multiplication.cs b/OnixLabs.Units/Volume.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..5524f0a --- /dev/null +++ b/OnixLabs.Units/Volume.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Volume +{ + /// + /// 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 Volume Multiply(Volume left, Volume right) => new(left.CubicYoctoMeters * right.CubicYoctoMeters); + + /// + /// 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 Volume operator *(Volume left, Volume right) => Multiply(left, right); + + /// + /// Computes the product of the current value, multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value multiplied by the specified other value. + public Volume Multiply(Volume other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Volume.Arithmetic.Subtraction.cs b/OnixLabs.Units/Volume.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..a6c2ea8 --- /dev/null +++ b/OnixLabs.Units/Volume.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Volume +{ + /// + /// 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 Volume Subtract(Volume left, Volume right) => new(left.CubicYoctoMeters - right.CubicYoctoMeters); + + /// + /// 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 Volume operator -(Volume left, Volume right) => Subtract(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 Volume Subtract(Volume other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Volume.Comparable.cs b/OnixLabs.Units/Volume.Comparable.cs new file mode 100644 index 0000000..7e5e9af --- /dev/null +++ b/OnixLabs.Units/Volume.Comparable.cs @@ -0,0 +1,81 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Volume +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(Volume left, Volume right) => left.CubicYoctoMeters.CompareTo(right.CubicYoctoMeters); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(Volume other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. +// ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than the right-hand operand; otherwise, . + public static bool operator >(Volume left, Volume right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(Volume left, Volume right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(Volume left, Volume right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(Volume left, Volume right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Volume.Constants.cs b/OnixLabs.Units/Volume.Constants.cs new file mode 100644 index 0000000..9ef4917 --- /dev/null +++ b/OnixLabs.Units/Volume.Constants.cs @@ -0,0 +1,62 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + + +public readonly partial struct Volume +{ + /// + /// Gets a zero 0 value. + /// + public static readonly Volume Zero = new(T.Zero); + + private const string CubicYoctoMetersSpecifier = "cuym"; + private const string CubicZeptoMetersSpecifier = "cuzm"; + private const string CubicAttoMetersSpecifier = "cuam"; + private const string CubicFemtoMetersSpecifier = "cufm"; + private const string CubicPicoMetersSpecifier = "cupm"; + private const string CubicNanoMetersSpecifier = "cunm"; + private const string CubicMicroMetersSpecifier = "cuum"; + private const string CubicMilliMetersSpecifier = "cumm"; + private const string CubicCentiMetersSpecifier = "cucm"; + private const string CubicDeciMetersSpecifier = "cudm"; + private const string CubicMetersSpecifier = "cum"; + private const string CubicDecaMetersSpecifier = "cudam"; + private const string CubicHectoMetersSpecifier = "cuhm"; + private const string CubicKiloMetersSpecifier = "cukm"; + private const string CubicMegaMetersSpecifier = "cuMm"; + private const string CubicGigaMetersSpecifier = "cuGm"; + private const string CubicTeraMetersSpecifier = "cuTm"; + private const string CubicPetaMetersSpecifier = "cuPm"; + private const string CubicExaMetersSpecifier = "cuEm"; + private const string CubicZettaMetersSpecifier = "cuZm"; + private const string CubicYottaMetersSpecifier = "cuYm"; + private const string LitersSpecifier = "l"; + private const string MilliLitersSpecifier = "ml"; + private const string CentiLitersSpecifier = "cl"; + private const string DeciLitersSpecifier = "dl"; + private const string CubicInchesSpecifier = "cuin"; + private const string CubicFeetSpecifier = "cuft"; + private const string CubicYardsSpecifier = "cuyd"; + private const string FluidOuncesSpecifier = "floz"; + private const string CupsSpecifier = "cup"; + private const string PintsSpecifier = "pt"; + private const string QuartsSpecifier = "qt"; + private const string GallonsSpecifier = "gal"; +} diff --git a/OnixLabs.Units/Volume.Equatable.cs b/OnixLabs.Units/Volume.Equatable.cs new file mode 100644 index 0000000..96c889b --- /dev/null +++ b/OnixLabs.Units/Volume.Equatable.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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Volume +{ + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(Volume left, Volume right) => Equals(left.CubicYoctoMeters, right.CubicYoctoMeters); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// Returns if the current instance equals the specified other instance; otherwise, . + public bool Equals(Volume other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Volume other && Equals(other); + + /// + /// Serves as a hash code function for this instance. + /// + /// Returns a hash code for this instance. + public override int GetHashCode() => CubicYoctoMeters.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(Volume left, Volume right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(Volume left, Volume right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Volume.Format.cs b/OnixLabs.Units/Volume.Format.cs new file mode 100644 index 0000000..3bf21d4 --- /dev/null +++ b/OnixLabs.Units/Volume.Format.cs @@ -0,0 +1,32 @@ +// 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 Volume +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Volume.From.cs b/OnixLabs.Units/Volume.From.cs new file mode 100644 index 0000000..93023be --- /dev/null +++ b/OnixLabs.Units/Volume.From.cs @@ -0,0 +1,249 @@ +// 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 Volume +{ + /// + /// Creates a new instance from the specified cubic yoctometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicYoctoMeters(T value) => new(value); + + /// + /// Creates a new instance from the specified cubic zeptometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicZeptoMeters(T value) => new(value * T.CreateChecked(1e9)); + + /// + /// Creates a new instance from the specified cubic attometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicAttoMeters(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from the specified cubic femtometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicFemtoMeters(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from the specified cubic picometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicPicoMeters(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from the specified cubic nanometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicNanoMeters(T value) => new(value * T.CreateChecked(1e45)); + + /// + /// Creates a new instance from the specified cubic micrometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicMicroMeters(T value) => new(value * T.CreateChecked(1e54)); + + /// + /// Creates a new instance from the specified cubic millimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicMilliMeters(T value) => new(value * T.CreateChecked(1e63)); + + /// + /// Creates a new instance from the specified cubic centimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicCentiMeters(T value) => new(value * T.CreateChecked(1e66)); + + /// + /// Creates a new instance from the specified cubic decimeters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicDeciMeters(T value) => new(value * T.CreateChecked(1e69)); + + /// + /// Creates a new instance from the specified cubic meters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicMeters(T value) => new(value * T.CreateChecked(1e72)); + + /// + /// Creates a new instance from the specified cubic decameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicDecaMeters(T value) => new(value * T.CreateChecked(1e75)); + + /// + /// Creates a new instance from the specified cubic hectometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicHectoMeters(T value) => new(value * T.CreateChecked(1e78)); + + /// + /// Creates a new instance from the specified cubic kilometers value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicKiloMeters(T value) => new(value * T.CreateChecked(1e81)); + + /// + /// Creates a new instance from the specified cubic megameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicMegaMeters(T value) => new(value * T.CreateChecked(1e90)); + + /// + /// Creates a new instance from the specified cubic gigameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicGigaMeters(T value) => new(value * T.CreateChecked(1e99)); + + /// + /// Creates a new instance from the specified cubic terameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicTeraMeters(T value) => new(value * T.CreateChecked(1e108)); + + /// + /// Creates a new instance from the specified cubic petameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicPetaMeters(T value) => new(value * T.CreateChecked(1e117)); + + /// + /// Creates a new instance from the specified cubic exameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicExaMeters(T value) => new(value * T.CreateChecked(1e126)); + + /// + /// Creates a new instance from the specified cubic zettameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicZettaMeters(T value) => new(value * T.CreateChecked(1e135)); + + /// + /// Creates a new instance from the specified cubic yottameters value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicYottaMeters(T value) => new(value * T.CreateChecked(1e144)); + + /// + /// Creates a new instance from the specified litres value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromLiters(T value) => new(value * T.CreateChecked(1e69)); + + /// + /// Creates a new instance from the specified millilitres value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromMilliLiters(T value) => new(value * T.CreateChecked(1e66)); + + /// + /// Creates a new instance from the specified centilitres value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCentiLiters(T value) => new(value * T.CreateChecked(1e67)); + + /// + /// Creates a new instance from the specified decilitres value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromDeciLiters(T value) => new(value * T.CreateChecked(1e68)); + + /// + /// Creates a new instance from the specified cubic inches value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicInches(T value) => new(value * T.CreateChecked(1.6387064e67)); + + /// + /// Creates a new instance from the specified cubic feet value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicFeet(T value) => new(value * T.CreateChecked(2.8316846592e70)); + + /// + /// Creates a new instance from the specified cubic yards value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCubicYards(T value) => new(value * T.CreateChecked(7.64554857984e71)); + + /// + /// Creates a new instance from the specified US fluid ounces value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromFluidOunces(T value) => new(value * T.CreateChecked(2.95735295625e67)); + + /// + /// Creates a new instance from the specified US cups value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromCups(T value) => new(value * T.CreateChecked(2.365882365e68)); + + /// + /// Creates a new instance from the specified US pints value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromPints(T value) => new(value * T.CreateChecked(4.73176473e68)); + + /// + /// Creates a new instance from the specified US quarts value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromQuarts(T value) => new(value * T.CreateChecked(9.46352946e68)); + + /// + /// Creates a new instance from the specified US gallons value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Volume FromGallons(T value) => new(value * T.CreateChecked(3.785411784e69)); +} diff --git a/OnixLabs.Units/Volume.To.cs b/OnixLabs.Units/Volume.To.cs new file mode 100644 index 0000000..1c047c2 --- /dev/null +++ b/OnixLabs.Units/Volume.To.cs @@ -0,0 +1,94 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Volume +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(CubicYoctoMetersSpecifier); + + /// + /// Formats the value of the current instance using the specified format and format provider. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format and format provider. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: CubicYoctoMetersSpecifier); + + T value = specifier switch + { + CubicYoctoMetersSpecifier => CubicYoctoMeters, + CubicZeptoMetersSpecifier => CubicZeptoMeters, + CubicAttoMetersSpecifier => CubicAttoMeters, + CubicFemtoMetersSpecifier => CubicFemtoMeters, + CubicPicoMetersSpecifier => CubicPicoMeters, + CubicNanoMetersSpecifier => CubicNanoMeters, + CubicMicroMetersSpecifier => CubicMicroMeters, + CubicMilliMetersSpecifier => CubicMilliMeters, + CubicCentiMetersSpecifier => CubicCentiMeters, + CubicDeciMetersSpecifier => CubicDeciMeters, + CubicMetersSpecifier => CubicMeters, + CubicDecaMetersSpecifier => CubicDecaMeters, + CubicHectoMetersSpecifier => CubicHectoMeters, + CubicKiloMetersSpecifier => CubicKiloMeters, + CubicMegaMetersSpecifier => CubicMegaMeters, + CubicGigaMetersSpecifier => CubicGigaMeters, + CubicTeraMetersSpecifier => CubicTeraMeters, + CubicPetaMetersSpecifier => CubicPetaMeters, + CubicExaMetersSpecifier => CubicExaMeters, + CubicZettaMetersSpecifier => CubicZettaMeters, + CubicYottaMetersSpecifier => CubicYottaMeters, + LitersSpecifier => Liters, + MilliLitersSpecifier => MilliLiters, + CentiLitersSpecifier => CentiLiters, + DeciLitersSpecifier => DeciLiters, + CubicInchesSpecifier => CubicInches, + CubicFeetSpecifier => CubicFeet, + CubicYardsSpecifier => CubicYards, + FluidOuncesSpecifier => FluidOunces, + CupsSpecifier => Cups, + PintsSpecifier => Pints, + QuartsSpecifier => Quarts, + GallonsSpecifier => Gallons, + _ => throw new ArgumentException( + $"Format '{format.ToString()}' is invalid. Valid format specifiers are: " + + "cuym, cuzm, cuam, cufm, cupm, cunm, cuum, cumm, cucm, cudm, cum, cudam, cuhm, cukm, cuMm, " + + "cuGm, cuTm, cuPm, cuEm, cuZm, cuYm, l, ml, cl, dl, cuin, cuft, cuyd, floz, cup, pt, qt, gal. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Volume.cs b/OnixLabs.Units/Volume.cs new file mode 100644 index 0000000..27d70cb --- /dev/null +++ b/OnixLabs.Units/Volume.cs @@ -0,0 +1,202 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of volume. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Volume : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The volume unit in . + private Volume(T value) => CubicYoctoMeters = value; + + /// + /// Gets the volume in cubic yoctometers (cuym). + /// + public T CubicYoctoMeters { get; } + + /// + /// Gets the volume in cubic zeptometers (cuzm). + /// + public T CubicZeptoMeters => CubicYoctoMeters / T.CreateChecked(1e9); + + /// + /// Gets the volume in cubic attometers (cuam). + /// + public T CubicAttoMeters => CubicYoctoMeters / T.CreateChecked(1e18); + + /// + /// Gets the volume in cubic femtometers (cufm). + /// + public T CubicFemtoMeters => CubicYoctoMeters / T.CreateChecked(1e27); + + /// + /// Gets the volume in cubic picometers (cupm). + /// + public T CubicPicoMeters => CubicYoctoMeters / T.CreateChecked(1e36); + + /// + /// Gets the volume in cubic nanometers (cunm). + /// + public T CubicNanoMeters => CubicYoctoMeters / T.CreateChecked(1e45); + + /// + /// Gets the volume in cubic micrometers (cuum). + /// + public T CubicMicroMeters => CubicYoctoMeters / T.CreateChecked(1e54); + + /// + /// Gets the volume in cubic millimeters (cumm). + /// + public T CubicMilliMeters => CubicYoctoMeters / T.CreateChecked(1e63); + + /// + /// Gets the volume in cubic centimeters (cucm). + /// + public T CubicCentiMeters => CubicYoctoMeters / T.CreateChecked(1e66); + + /// + /// Gets the volume in cubic decimeters (cudm). + /// + public T CubicDeciMeters => CubicYoctoMeters / T.CreateChecked(1e69); + + /// + /// Gets the volume in cubic meters (cum). + /// + public T CubicMeters => CubicYoctoMeters / T.CreateChecked(1e72); + + /// + /// Gets the volume in cubic decameters (cudam). + /// + public T CubicDecaMeters => CubicYoctoMeters / T.CreateChecked(1e75); + + /// + /// Gets the volume in cubic hectometers (cuhm). + /// + public T CubicHectoMeters => CubicYoctoMeters / T.CreateChecked(1e78); + + /// + /// Gets the volume in cubic kilometers (cukm). + /// + public T CubicKiloMeters => CubicYoctoMeters / T.CreateChecked(1e81); + + /// + /// Gets the volume in cubic megameters (cuMm). + /// + public T CubicMegaMeters => CubicYoctoMeters / T.CreateChecked(1e90); + + /// + /// Gets the volume in cubic gigameters (cuGm). + /// + public T CubicGigaMeters => CubicYoctoMeters / T.CreateChecked(1e99); + + /// + /// Gets the volume in cubic terameters (cuTm). + /// + public T CubicTeraMeters => CubicYoctoMeters / T.CreateChecked(1e108); + + /// + /// Gets the volume in cubic petameters (cuPm). + /// + public T CubicPetaMeters => CubicYoctoMeters / T.CreateChecked(1e117); + + /// + /// Gets the volume in cubic exameters (cuEm). + /// + public T CubicExaMeters => CubicYoctoMeters / T.CreateChecked(1e126); + + /// + /// Gets the volume in cubic zettameters (cuZm). + /// + public T CubicZettaMeters => CubicYoctoMeters / T.CreateChecked(1e135); + + /// + /// Gets the volume in cubic yottameters (cuYm). + /// + public T CubicYottaMeters => CubicYoctoMeters / T.CreateChecked(1e144); + + /// + /// Gets the volume in liters (L). + /// + public T Liters => CubicYoctoMeters / T.CreateChecked(1e69); + + /// + /// Gets the volume in millilitres (mL). + /// + public T MilliLiters => CubicYoctoMeters / T.CreateChecked(1e66); + + /// + /// Gets the volume in centilitres (cL). + /// + public T CentiLiters => CubicYoctoMeters / T.CreateChecked(1e67); + + /// + /// Gets the volume in decilitres (dL). + /// + public T DeciLiters => CubicYoctoMeters / T.CreateChecked(1e68); + + /// + /// Gets the volume in cubic inches (cuin). + /// + public T CubicInches => CubicYoctoMeters / T.CreateChecked(1.6387064e67); + + /// + /// Gets the volume in cubic feet (cuft). + /// + public T CubicFeet => CubicYoctoMeters / T.CreateChecked(2.8316846592e70); + + /// + /// Gets the volume in cubic yards (cuyd). + /// + public T CubicYards => CubicYoctoMeters / T.CreateChecked(7.64554857984e71); + + /// + /// Gets the volume in US fluid ounces (floz). + /// + public T FluidOunces => CubicYoctoMeters / T.CreateChecked(2.95735295625e67); + + /// + /// Gets the volume in US cups (cup). + /// + public T Cups => CubicYoctoMeters / T.CreateChecked(2.365882365e68); + + /// + /// Gets the volume in US pints (pt). + /// + public T Pints => CubicYoctoMeters / T.CreateChecked(4.73176473e68); + + /// + /// Gets the volume in US quarts (qt). + /// + public T Quarts => CubicYoctoMeters / T.CreateChecked(9.46352946e68); + + /// + /// Gets the volume in US gallons (gal). + /// + public T Gallons => CubicYoctoMeters / T.CreateChecked(3.785411784e69); +} diff --git a/onixlabs-dotnet.sln.DotSettings b/onixlabs-dotnet.sln.DotSettings index dbd3e21..b3e92ef 100644 --- a/onixlabs-dotnet.sln.DotSettings +++ b/onixlabs-dotnet.sln.DotSettings @@ -16,6 +16,17 @@ limitations under the License. True True True + True + True + True + True + True + True + True + True + True + True + True True True True From 3fa0ba331121e68e57295151e295a32c1e3bac11 Mon Sep 17 00:00:00 2001 From: matthew Date: Wed, 19 Nov 2025 21:15:03 +0000 Subject: [PATCH 18/23] Added Speed unit and tests. --- OnixLabs.Units.UnitTests/SpeedTests.cs | 616 ++++++++++++++++++ OnixLabs.Units/Speed.Arithmetic.Addition.cs | 42 ++ OnixLabs.Units/Speed.Arithmetic.Division.cs | 42 ++ .../Speed.Arithmetic.Multiplication.cs | 42 ++ .../Speed.Arithmetic.Subtraction.cs | 42 ++ OnixLabs.Units/Speed.Comparable.cs | 79 +++ OnixLabs.Units/Speed.Constants.cs | 54 ++ OnixLabs.Units/Speed.Equatable.cs | 65 ++ OnixLabs.Units/Speed.Format.cs | 32 + OnixLabs.Units/Speed.From.cs | 228 +++++++ OnixLabs.Units/Speed.To.cs | 92 +++ OnixLabs.Units/Speed.cs | 187 ++++++ onixlabs-dotnet.sln.DotSettings | 24 +- 13 files changed, 1544 insertions(+), 1 deletion(-) create mode 100644 OnixLabs.Units.UnitTests/SpeedTests.cs create mode 100644 OnixLabs.Units/Speed.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Speed.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Speed.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Speed.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Speed.Comparable.cs create mode 100644 OnixLabs.Units/Speed.Constants.cs create mode 100644 OnixLabs.Units/Speed.Equatable.cs create mode 100644 OnixLabs.Units/Speed.Format.cs create mode 100644 OnixLabs.Units/Speed.From.cs create mode 100644 OnixLabs.Units/Speed.To.cs create mode 100644 OnixLabs.Units/Speed.cs diff --git a/OnixLabs.Units.UnitTests/SpeedTests.cs b/OnixLabs.Units.UnitTests/SpeedTests.cs new file mode 100644 index 0000000..172c10c --- /dev/null +++ b/OnixLabs.Units.UnitTests/SpeedTests.cs @@ -0,0 +1,616 @@ +// 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 SpeedTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+42; + + [Fact(DisplayName = "Speed.Zero should produce the expected result")] + public void SpeedZeroShouldProduceExpectedResult() + { + // Given / When + Speed speed = Speed.Zero; + + // Then + Assert.Equal(0.0, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromQuectometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void SpeedFromQuectometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromQuectometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromRontometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e3)] + [InlineData(2.5, 2.5e3)] + public void SpeedFromRontometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromRontometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromYoctometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void SpeedFromYoctometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromYoctometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromZeptometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e9)] + [InlineData(2.5, 2.5e9)] + public void SpeedFromZeptometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromZeptometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromAttometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void SpeedFromAttometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromAttometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromFemtometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e15)] + [InlineData(2.5, 2.5e15)] + public void SpeedFromFemtometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromFemtometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromPicometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void SpeedFromPicometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromPicometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromNanometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e21)] + [InlineData(2.5, 2.5e21)] + public void SpeedFromNanometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromNanometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromMicrometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void SpeedFromMicrometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromMicrometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromMillimetersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void SpeedFromMillimetersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromMillimetersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromCentimetersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e28)] + [InlineData(2.5, 2.5e28)] + public void SpeedFromCentimetersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromCentimetersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromDecimetersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e29)] + [InlineData(2.5, 2.5e29)] + public void SpeedFromDecimetersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromDecimetersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromMetersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void SpeedFromMetersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromMetersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromDecametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e31)] + [InlineData(2.5, 2.5e31)] + public void SpeedFromDecametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromDecametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromHectometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e32)] + [InlineData(2.5, 2.5e32)] + public void SpeedFromHectometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromHectometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromKilometersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e33)] + [InlineData(2.5, 2.5e33)] + public void SpeedFromKilometersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromKilometersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromMegametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void SpeedFromMegametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromMegametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromGigametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e39)] + [InlineData(2.5, 2.5e39)] + public void SpeedFromGigametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromGigametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromTerametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void SpeedFromTerametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromTerametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromPetametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e45)] + [InlineData(2.5, 2.5e45)] + public void SpeedFromPetametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromPetametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromExametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void SpeedFromExametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromExametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromZettametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e51)] + [InlineData(2.5, 2.5e51)] + public void SpeedFromZettametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromZettametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromYottametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e54)] + [InlineData(2.5, 2.5e54)] + public void SpeedFromYottametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromYottametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromRonnametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e57)] + [InlineData(2.5, 2.5e57)] + public void SpeedFromRonnametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromRonnametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromQuettametersPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e60)] + [InlineData(2.5, 2.5e60)] + public void SpeedFromQuettametersPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromQuettametersPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromInchesPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.54e28)] + [InlineData(2.5, 6.35e28)] + [InlineData(100.0, 2.54e30)] + public void SpeedFromInchesPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromInchesPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromFeetPerSecond should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.048e29)] + [InlineData(2.5, 7.62e29)] + [InlineData(100.0, 3.048e31)] + public void SpeedFromFeetPerSecondShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromFeetPerSecond(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromKilometersPerHour should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.777777777777778e29)] + [InlineData(2.5, 6.944444444444445e29)] + [InlineData(60.0, 1.666666666666667e31)] + public void SpeedFromKilometersPerHourShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromKilometersPerHour(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromMilesPerHour should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.4704e29)] + [InlineData(2.5, 1.1176e30)] + [InlineData(60.0, 2.68224e31)] + public void SpeedFromMilesPerHourShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromMilesPerHour(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Theory(DisplayName = "Speed.FromKnots should produce the expected QuectoMetersPerSecond")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 5.144444444444445e29)] + [InlineData(2.5, 1.2861111111111113e30)] + [InlineData(10.0, 5.144444444444445e30)] + public void SpeedFromKnotsShouldProduceExpectedQuectoMetersPerSecond(double value, double expectedQuectoMetersPerSecond) + { + // Given / When + Speed speed = Speed.FromKnots(value); + + // Then + Assert.Equal(expectedQuectoMetersPerSecond, speed.QuectoMetersPerSecond, Tolerance); + } + + [Fact(DisplayName = "Speed.Add should produce the expected result")] + public void SpeedAddShouldProduceExpectedValue() + { + // Given + Speed left = Speed.FromMetersPerSecond(15.0); + Speed right = Speed.FromMetersPerSecond(5.0); + + // When + Speed result = left.Add(right); + + // Then + Assert.Equal(20.0, result.MetersPerSecond, Tolerance); + } + + [Fact(DisplayName = "Speed.Subtract should produce the expected result")] + public void SpeedSubtractShouldProduceExpectedValue() + { + // Given + Speed left = Speed.FromMetersPerSecond(15.0); + Speed right = Speed.FromMetersPerSecond(5.0); + + // When + Speed result = left.Subtract(right); + + // Then + Assert.Equal(10.0, result.MetersPerSecond, Tolerance); + } + + [Fact(DisplayName = "Speed.Multiply should produce the expected result")] + public void SpeedMultiplyShouldProduceExpectedValue() + { + // Given + Speed left = Speed.FromMetersPerSecond(10.0); // 1e31 qm/s + Speed right = Speed.FromMetersPerSecond(3.0); // 3e30 qm/s + + // When + Speed result = left.Multiply(right); // 1e31 * 3e30 = 3e61 qm/s + + // Then + Assert.Equal(1e31, left.QuectoMetersPerSecond, Tolerance); + Assert.Equal(3e30, right.QuectoMetersPerSecond, Tolerance); + Assert.Equal(3e61, result.QuectoMetersPerSecond, Tolerance); + Assert.Equal(3e31, result.MetersPerSecond, Tolerance); + } + + [Fact(DisplayName = "Speed.Divide should produce the expected result")] + public void SpeedDivideShouldProduceExpectedValue() + { + // Given + Speed left = Speed.FromMetersPerSecond(10.0); // 1e31 qm/s + Speed right = Speed.FromMetersPerSecond(2.0); // 2e30 qm/s + + // When + Speed result = left.Divide(right); // 1e31 / 2e30 = 5 qm/s + + // Then + Assert.Equal(1e31, left.QuectoMetersPerSecond, Tolerance); + Assert.Equal(2e30, right.QuectoMetersPerSecond, Tolerance); + Assert.Equal(5.0, result.QuectoMetersPerSecond, Tolerance); + Assert.Equal(5e-30, result.MetersPerSecond, Tolerance); + } + + [Fact(DisplayName = "Speed comparison should produce the expected result (left equal to right)")] + public void SpeedComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Speed left = Speed.FromMetersPerSecond(123.0); + Speed right = Speed.FromMetersPerSecond(123.0); + + // When / Then + Assert.Equal(0, Speed.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 = "Speed comparison should produce the expected result (left greater than right)")] + public void SpeedComparisonShouldProduceExpectedResultLeftGreaterThanRight() + { + // Given + Speed left = Speed.FromMetersPerSecond(456.0); + Speed right = Speed.FromMetersPerSecond(123.0); + + // When / Then + Assert.Equal(1, Speed.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 = "Speed comparison should produce the expected result (left less than right)")] + public void SpeedComparisonShouldProduceExpectedResultLeftLessThanRight() + { + // Given + Speed left = Speed.FromMetersPerSecond(123.0); + Speed right = Speed.FromMetersPerSecond(456.0); + + // When / Then + Assert.Equal(-1, Speed.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 = "Speed equality should produce the expected result (left equal to right)")] + public void SpeedEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Speed left = Speed.FromMetersPerSecond(123.0); + Speed right = Speed.FromMetersPerSecond(123.0); + + // When / Then + Assert.True(Speed.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Speed equality should produce the expected result (left not equal to right)")] + public void SpeedEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Speed left = Speed.FromMetersPerSecond(2.0); + Speed right = Speed.FromKilometersPerHour(7.2); + + // When / Then + Assert.False(Speed.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Speed equality should produce the expected result (different values)")] + public void SpeedEqualityShouldProduceExpectedResultDifferentValues() + { + // Given + Speed left = Speed.FromMetersPerSecond(2.0); + Speed right = Speed.FromMetersPerSecond(3.0); + + // When / Then + Assert.False(Speed.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Speed.ToString should produce the expected result")] + public void SpeedToStringShouldProduceExpectedResult() + { + // Given + Speed speed = Speed.FromMetersPerSecond(1.0); + + // When / Then + Assert.Equal("1.000 mps", $"{speed:mps3}"); + Assert.Equal("3.600 kmph", $"{speed:kmph3}"); + Assert.Equal("2.237 mph", $"{speed:mph3}"); + Assert.Equal("1.944 kt", $"{speed:kt3}"); + Assert.Equal("39.370 ips", $"{speed:ips3}"); + Assert.Equal("3.281 fps", $"{speed:fps3}"); + } + + [Fact(DisplayName = "Speed.ToString should honor custom culture separators")] + public void SpeedToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Speed speed = Speed.FromMetersPerSecond(1234.56); + + // When + string formatted = speed.ToString("mps2", customCulture); + + // Then + // German uses '.' for thousands and ',' for decimals. + Assert.Equal("1.234,56 mps", formatted); + } +} diff --git a/OnixLabs.Units/Speed.Arithmetic.Addition.cs b/OnixLabs.Units/Speed.Arithmetic.Addition.cs new file mode 100644 index 0000000..785bc08 --- /dev/null +++ b/OnixLabs.Units/Speed.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Speed +{ + /// + /// 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 Speed Add(Speed left, Speed right) => new(left.QuectoMetersPerSecond + right.QuectoMetersPerSecond); + + /// + /// 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 Speed operator +(Speed left, Speed right) => Add(left, right); + + /// + /// 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 Speed Add(Speed other) => Add(this, other); +} diff --git a/OnixLabs.Units/Speed.Arithmetic.Division.cs b/OnixLabs.Units/Speed.Arithmetic.Division.cs new file mode 100644 index 0000000..5de018c --- /dev/null +++ b/OnixLabs.Units/Speed.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Speed +{ + /// + /// 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 Speed Divide(Speed left, Speed right) => new(left.QuectoMetersPerSecond / right.QuectoMetersPerSecond); + + /// + /// 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 Speed operator /(Speed left, Speed right) => Divide(left, right); + + /// + /// Computes the quotient of the current value divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value divided by the specified other value. + public Speed Divide(Speed other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Speed.Arithmetic.Multiplication.cs b/OnixLabs.Units/Speed.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..c6d8816 --- /dev/null +++ b/OnixLabs.Units/Speed.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Speed +{ + /// + /// 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 Speed Multiply(Speed left, Speed right) => new(left.QuectoMetersPerSecond * right.QuectoMetersPerSecond); + + /// + /// 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 Speed operator *(Speed left, Speed right) => Multiply(left, right); + + /// + /// Computes the product of the current value multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value multiplied by the specified other value. + public Speed Multiply(Speed other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Speed.Arithmetic.Subtraction.cs b/OnixLabs.Units/Speed.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..3d1e99a --- /dev/null +++ b/OnixLabs.Units/Speed.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Speed +{ + /// + /// 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 Speed Subtract(Speed left, Speed right) => new(left.QuectoMetersPerSecond - right.QuectoMetersPerSecond); + + /// + /// 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 Speed operator -(Speed left, Speed right) => Subtract(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 Speed Subtract(Speed other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Speed.Comparable.cs b/OnixLabs.Units/Speed.Comparable.cs new file mode 100644 index 0000000..bc67ead --- /dev/null +++ b/OnixLabs.Units/Speed.Comparable.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 OnixLabs.Core; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Speed +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(Speed left, Speed right) => left.QuectoMetersPerSecond.CompareTo(right.QuectoMetersPerSecond); + + /// + /// Compares the current instance with another instance of and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// An instance of to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(Speed other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than the right-hand operand; otherwise, . + public static bool operator >(Speed left, Speed right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(Speed left, Speed right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(Speed left, Speed right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(Speed left, Speed right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Speed.Constants.cs b/OnixLabs.Units/Speed.Constants.cs new file mode 100644 index 0000000..4742fd1 --- /dev/null +++ b/OnixLabs.Units/Speed.Constants.cs @@ -0,0 +1,54 @@ +// 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 Speed +{ + /// + /// Gets a zero value, equal to zero quectometers per second. + /// + public static readonly Speed Zero = new(T.Zero); + + private const string QuectoMetersPerSecondSpecifier = "qmps"; + private const string RontoMetersPerSecondSpecifier = "rmps"; + private const string YoctoMetersPerSecondSpecifier = "ymps"; + private const string ZeptoMetersPerSecondSpecifier = "zmps"; + private const string AttoMetersPerSecondSpecifier = "amps"; + private const string FemtoMetersPerSecondSpecifier = "fmps"; + private const string PicoMetersPerSecondSpecifier = "pmps"; + private const string NanoMetersPerSecondSpecifier = "nmps"; + private const string MicroMetersPerSecondSpecifier = "umps"; + private const string MilliMetersPerSecondSpecifier = "mmps"; + private const string CentiMetersPerSecondSpecifier = "cmps"; + private const string DeciMetersPerSecondSpecifier = "dmps"; + private const string MetersPerSecondSpecifier = "mps"; + private const string DecaMetersPerSecondSpecifier = "damps"; + private const string HectoMetersPerSecondSpecifier = "hmps"; + private const string KiloMetersPerSecondSpecifier = "kmps"; + private const string MegaMetersPerSecondSpecifier = "Mmps"; + private const string GigaMetersPerSecondSpecifier = "Gmps"; + private const string TeraMetersPerSecondSpecifier = "Tmps"; + private const string PetaMetersPerSecondSpecifier = "Pmps"; + private const string ExaMetersPerSecondSpecifier = "Emps"; + private const string ZettaMetersPerSecondSpecifier = "Zmps"; + private const string YottaMetersPerSecondSpecifier = "Ymps"; + private const string RonnaMetersPerSecondSpecifier = "Rmps"; + private const string QuettaMetersPerSecondSpecifier = "Qmps"; + private const string InchesPerSecondSpecifier = "ips"; + private const string FeetPerSecondSpecifier = "fps"; + private const string KilometersPerHourSpecifier = "kmph"; + private const string MilesPerHourSpecifier = "mph"; + private const string KnotsSpecifier = "kt"; +} diff --git a/OnixLabs.Units/Speed.Equatable.cs b/OnixLabs.Units/Speed.Equatable.cs new file mode 100644 index 0000000..d3d2289 --- /dev/null +++ b/OnixLabs.Units/Speed.Equatable.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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Speed +{ + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(Speed left, Speed right) => Equals(left.QuectoMetersPerSecond, right.QuectoMetersPerSecond); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// Returns if the current instance has the same value as the specified other instance; otherwise, . + public bool Equals(Speed other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Speed other && Equals(other); + + /// + /// Serves as a hash code function for this instance. + /// + /// Returns a hash code for this instance. + public override int GetHashCode() => QuectoMetersPerSecond.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(Speed left, Speed right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(Speed left, Speed right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Speed.Format.cs b/OnixLabs.Units/Speed.Format.cs new file mode 100644 index 0000000..386e6cf --- /dev/null +++ b/OnixLabs.Units/Speed.Format.cs @@ -0,0 +1,32 @@ +// 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 Speed +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Speed.From.cs b/OnixLabs.Units/Speed.From.cs new file mode 100644 index 0000000..facc527 --- /dev/null +++ b/OnixLabs.Units/Speed.From.cs @@ -0,0 +1,228 @@ +// 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 Speed +{ + /// + /// Creates a new instance from the specified quectometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromQuectometersPerSecond(T value) => new(value); + + /// + /// Creates a new instance from the specified rontometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromRontometersPerSecond(T value) => new(value * T.CreateChecked(1e3)); + + /// + /// Creates a new instance from the specified yoctometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromYoctometersPerSecond(T value) => new(value * T.CreateChecked(1e6)); + + /// + /// Creates a new instance from the specified zeptometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromZeptometersPerSecond(T value) => new(value * T.CreateChecked(1e9)); + + /// + /// Creates a new instance from the specified attometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromAttometersPerSecond(T value) => new(value * T.CreateChecked(1e12)); + + /// + /// Creates a new instance from the specified femtometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromFemtometersPerSecond(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from the specified picometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromPicometersPerSecond(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from the specified nanometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromNanometersPerSecond(T value) => new(value * T.CreateChecked(1e21)); + + /// + /// Creates a new instance from the specified micrometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromMicrometersPerSecond(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from the specified millimeters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromMillimetersPerSecond(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from the specified centimeters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromCentimetersPerSecond(T value) => new(value * T.CreateChecked(1e28)); + + /// + /// Creates a new instance from the specified decimeters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromDecimetersPerSecond(T value) => new(value * T.CreateChecked(1e29)); + + /// + /// Creates a new instance from the specified meters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromMetersPerSecond(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified decameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromDecametersPerSecond(T value) => new(value * T.CreateChecked(1e31)); + + /// + /// Creates a new instance from the specified hectometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromHectometersPerSecond(T value) => new(value * T.CreateChecked(1e32)); + + /// + /// Creates a new instance from the specified kilometers per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromKilometersPerSecond(T value) => new(value * T.CreateChecked(1e33)); + + /// + /// Creates a new instance from the specified megameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromMegametersPerSecond(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from the specified gigameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromGigametersPerSecond(T value) => new(value * T.CreateChecked(1e39)); + + /// + /// Creates a new instance from the specified terameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromTerametersPerSecond(T value) => new(value * T.CreateChecked(1e42)); + + /// + /// Creates a new instance from the specified petameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromPetametersPerSecond(T value) => new(value * T.CreateChecked(1e45)); + + /// + /// Creates a new instance from the specified exameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromExametersPerSecond(T value) => new(value * T.CreateChecked(1e48)); + + /// + /// Creates a new instance from the specified zettameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromZettametersPerSecond(T value) => new(value * T.CreateChecked(1e51)); + + /// + /// Creates a new instance from the specified yottameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromYottametersPerSecond(T value) => new(value * T.CreateChecked(1e54)); + + /// + /// Creates a new instance from the specified ronnameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromRonnametersPerSecond(T value) => new(value * T.CreateChecked(1e57)); + + /// + /// Creates a new instance from the specified quettameters per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromQuettametersPerSecond(T value) => new(value * T.CreateChecked(1e60)); + + /// + /// Creates a new instance from the specified inches per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromInchesPerSecond(T value) => new(value * T.CreateChecked(2.54e28)); + + /// + /// Creates a new instance from the specified feet per second value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromFeetPerSecond(T value) => new(value * T.CreateChecked(3.048e29)); + + /// + /// Creates a new instance from the specified kilometers per hour value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromKilometersPerHour(T value) => new(value * T.CreateChecked(2.777777777777778e29)); + + /// + /// Creates a new instance from the specified miles per hour value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromMilesPerHour(T value) => new(value * T.CreateChecked(4.4704e29)); + + /// + /// Creates a new instance from the specified knots value. + /// + /// The value from which to construct the new instance. + /// Returns a new instance from the specified value. + public static Speed FromKnots(T value) => new(value * T.CreateChecked(5.144444444444445e29)); +} diff --git a/OnixLabs.Units/Speed.To.cs b/OnixLabs.Units/Speed.To.cs new file mode 100644 index 0000000..dbea682 --- /dev/null +++ b/OnixLabs.Units/Speed.To.cs @@ -0,0 +1,92 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Speed +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(QuectoMetersPerSecondSpecifier, CultureInfo.CurrentCulture); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => + ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or an empty span to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: QuectoMetersPerSecondSpecifier); + + T value = specifier switch + { + QuectoMetersPerSecondSpecifier => QuectoMetersPerSecond, + RontoMetersPerSecondSpecifier => RontoMetersPerSecond, + YoctoMetersPerSecondSpecifier => YoctoMetersPerSecond, + ZeptoMetersPerSecondSpecifier => ZeptoMetersPerSecond, + AttoMetersPerSecondSpecifier => AttoMetersPerSecond, + FemtoMetersPerSecondSpecifier => FemtoMetersPerSecond, + PicoMetersPerSecondSpecifier => PicoMetersPerSecond, + NanoMetersPerSecondSpecifier => NanoMetersPerSecond, + MicroMetersPerSecondSpecifier => MicroMetersPerSecond, + MilliMetersPerSecondSpecifier => MilliMetersPerSecond, + CentiMetersPerSecondSpecifier => CentiMetersPerSecond, + DeciMetersPerSecondSpecifier => DeciMetersPerSecond, + MetersPerSecondSpecifier => MetersPerSecond, + DecaMetersPerSecondSpecifier => DecaMetersPerSecond, + HectoMetersPerSecondSpecifier => HectoMetersPerSecond, + KiloMetersPerSecondSpecifier => KiloMetersPerSecond, + MegaMetersPerSecondSpecifier => MegaMetersPerSecond, + GigaMetersPerSecondSpecifier => GigaMetersPerSecond, + TeraMetersPerSecondSpecifier => TeraMetersPerSecond, + PetaMetersPerSecondSpecifier => PetaMetersPerSecond, + ExaMetersPerSecondSpecifier => ExaMetersPerSecond, + ZettaMetersPerSecondSpecifier => ZettaMetersPerSecond, + YottaMetersPerSecondSpecifier => YottaMetersPerSecond, + RonnaMetersPerSecondSpecifier => RonnaMetersPerSecond, + QuettaMetersPerSecondSpecifier => QuettaMetersPerSecond, + InchesPerSecondSpecifier => InchesPerSecond, + FeetPerSecondSpecifier => FeetPerSecond, + KilometersPerHourSpecifier => KilometersPerHour, + MilesPerHourSpecifier => MilesPerHour, + KnotsSpecifier => Knots, + _ => throw new ArgumentException( + $"Format '{format.ToString()}' is invalid. Valid format specifiers are: " + + "qmps, rmps, ymps, zmps, amps, fmps, pmps, nmps, umps, mmps, cmps, dmps, mps, damps, " + + "hmps, kmps, Mmps, Gmps, Tmps, Pmps, Emps, Zmps, Ymps, Rmps, Qmps, ips, fps, kmph, mph, kt. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Speed.cs b/OnixLabs.Units/Speed.cs new file mode 100644 index 0000000..92f6b48 --- /dev/null +++ b/OnixLabs.Units/Speed.cs @@ -0,0 +1,187 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of speed. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Speed : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The speed unit in . + private Speed(T value) => QuectoMetersPerSecond = value; + + /// + /// Gets the speed in quectometers per second (qmps). + /// + public T QuectoMetersPerSecond { get; } + + /// + /// Gets the speed in rontometers per second (rmps). + /// + public T RontoMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e3); + + /// + /// Gets the speed in yoctometers per second (ymps). + /// + public T YoctoMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e6); + + /// + /// Gets the speed in zeptometers per second (zmps). + /// + public T ZeptoMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e9); + + /// + /// Gets the speed in attometers per second (amps). + /// + public T AttoMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e12); + + /// + /// Gets the speed in femtometers per second (fmps). + /// + public T FemtoMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e15); + + /// + /// Gets the speed in picometers per second (pmps). + /// + public T PicoMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e18); + + /// + /// Gets the speed in nanometers per second (nmps). + /// + public T NanoMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e21); + + /// + /// Gets the speed in micrometers per second (µmps). + /// + public T MicroMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e24); + + /// + /// Gets the speed in millimeters per second (mmps). + /// + public T MilliMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e27); + + /// + /// Gets the speed in centimeters per second (cmps). + /// + public T CentiMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e28); + + /// + /// Gets the speed in decimeters per second (dmps). + /// + public T DeciMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e29); + + /// + /// Gets the speed in meters per second (mps). + /// + public T MetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e30); + + /// + /// Gets the speed in decameters per second (damps). + /// + public T DecaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e31); + + /// + /// Gets the speed in hectometers per second (hmps). + /// + public T HectoMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e32); + + /// + /// Gets the speed in kilometers per second (kmps). + /// + public T KiloMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e33); + + /// + /// Gets the speed in megameters per second (Mmps). + /// + public T MegaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e36); + + /// + /// Gets the speed in gigameters per second (Gmps). + /// + public T GigaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e39); + + /// + /// Gets the speed in terameters per second (Tmps). + /// + public T TeraMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e42); + + /// + /// Gets the speed in petameters per second (Pmps). + /// + public T PetaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e45); + + /// + /// Gets the speed in exameters per second (Emps). + /// + public T ExaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e48); + + /// + /// Gets the speed in zettameters per second (Zmps). + /// + public T ZettaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e51); + + /// + /// Gets the speed in yottameters per second (Ymps). + /// + public T YottaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e54); + + /// + /// Gets the speed in ronnameters per second (Rmps). + /// + public T RonnaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e57); + + /// + /// Gets the speed in quettameters per second (Qmps). + /// + public T QuettaMetersPerSecond => QuectoMetersPerSecond / T.CreateChecked(1e60); + + /// + /// Gets the speed in inches per second (inps). + /// + public T InchesPerSecond => QuectoMetersPerSecond / T.CreateChecked(0.0254e30); + + /// + /// Gets the speed in feet per second (ftps). + /// + public T FeetPerSecond => QuectoMetersPerSecond / T.CreateChecked(0.3048e30); + + /// + /// Gets the speed in kilometers per hour (kmph). + /// + public T KilometersPerHour => QuectoMetersPerSecond / T.CreateChecked(0.2777777777777778e30); + + /// + /// Gets the speed in miles per hour (mph). + /// + public T MilesPerHour => QuectoMetersPerSecond / T.CreateChecked(0.44704e30); + + /// + /// Gets the speed in knots (kt). + /// + public T Knots => QuectoMetersPerSecond / T.CreateChecked(0.5144444444444445e30); +} diff --git a/onixlabs-dotnet.sln.DotSettings b/onixlabs-dotnet.sln.DotSettings index b3e92ef..a4b79fd 100644 --- a/onixlabs-dotnet.sln.DotSettings +++ b/onixlabs-dotnet.sln.DotSettings @@ -15,6 +15,7 @@ limitations under the License. True True True + True True True True @@ -29,7 +30,9 @@ limitations under the License. True True True + True True + True True True True @@ -40,6 +43,7 @@ limitations under the License. True True True + True True True True @@ -48,17 +52,24 @@ 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 @@ -68,11 +79,17 @@ limitations under the License. True True True + True + True True + True + True True True True True + True + True True True True @@ -83,6 +100,9 @@ limitations under the License. True True True + True + True + True True True True @@ -104,4 +124,6 @@ limitations under the License. True True True - True \ No newline at end of file + True + True + True \ No newline at end of file From b094d17091028f52239dc00900fffe017f7ab9aa Mon Sep 17 00:00:00 2001 From: matthew Date: Wed, 19 Nov 2025 23:30:58 +0000 Subject: [PATCH 19/23] Added Pressure unit and unit tests. --- OnixLabs.Units.UnitTests/PressureTests.cs | 686 ++++++++++++++++++ .../Pressure.Arithmetic.Addition.cs | 42 ++ .../Pressure.Arithmetic.Division.cs | 42 ++ .../Pressure.Arithmetic.Multiplication.cs | 42 ++ .../Pressure.Arithmetic.Subtraction.cs | 42 ++ OnixLabs.Units/Pressure.Comparable.cs | 103 +++ OnixLabs.Units/Pressure.Constants.cs | 61 ++ OnixLabs.Units/Pressure.Equatable.cs | 80 ++ OnixLabs.Units/Pressure.Format.cs | 32 + OnixLabs.Units/Pressure.From.cs | 277 +++++++ OnixLabs.Units/Pressure.To.cs | 100 +++ OnixLabs.Units/Pressure.cs | 222 ++++++ 12 files changed, 1729 insertions(+) create mode 100644 OnixLabs.Units.UnitTests/PressureTests.cs create mode 100644 OnixLabs.Units/Pressure.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Pressure.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Pressure.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Pressure.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Pressure.Comparable.cs create mode 100644 OnixLabs.Units/Pressure.Constants.cs create mode 100644 OnixLabs.Units/Pressure.Equatable.cs create mode 100644 OnixLabs.Units/Pressure.Format.cs create mode 100644 OnixLabs.Units/Pressure.From.cs create mode 100644 OnixLabs.Units/Pressure.To.cs create mode 100644 OnixLabs.Units/Pressure.cs diff --git a/OnixLabs.Units.UnitTests/PressureTests.cs b/OnixLabs.Units.UnitTests/PressureTests.cs new file mode 100644 index 0000000..81ba374 --- /dev/null +++ b/OnixLabs.Units.UnitTests/PressureTests.cs @@ -0,0 +1,686 @@ +// 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; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class PressureTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+42; + + [Fact(DisplayName = "Pressure.Zero should produce the expected result")] + public void PressureZeroShouldProduceExpectedResult() + { + // Given / When + Pressure pressure = Pressure.Zero; + + // Then + Assert.Equal(0.0, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromQuectoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void PressureFromQuectoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromQuectoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromRontoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e3)] + [InlineData(2.5, 2.5e3)] + public void PressureFromRontoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromRontoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromYoctoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void PressureFromYoctoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromYoctoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromZeptoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e9)] + [InlineData(2.5, 2.5e9)] + public void PressureFromZeptoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromZeptoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromAttoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void PressureFromAttoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromAttoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromFemtoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e15)] + [InlineData(2.5, 2.5e15)] + public void PressureFromFemtoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromFemtoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromPicoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void PressureFromPicoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromPicoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromNanoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e21)] + [InlineData(2.5, 2.5e21)] + public void PressureFromNanoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromNanoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromMicroPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void PressureFromMicroPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromMicroPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromMilliPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void PressureFromMilliPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromMilliPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromCentiPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e28)] + [InlineData(2.5, 2.5e28)] + public void PressureFromCentiPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromCentiPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromDeciPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e29)] + [InlineData(2.5, 2.5e29)] + public void PressureFromDeciPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromDeciPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void PressureFromPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromDecaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e31)] + [InlineData(2.5, 2.5e31)] + public void PressureFromDecaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromDecaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromHectoPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e32)] + [InlineData(2.5, 2.5e32)] + public void PressureFromHectoPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromHectoPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromKiloPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e33)] + [InlineData(2.5, 2.5e33)] + public void PressureFromKiloPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromKiloPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromMegaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void PressureFromMegaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromMegaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromGigaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e39)] + [InlineData(2.5, 2.5e39)] + public void PressureFromGigaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromGigaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromTeraPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void PressureFromTeraPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromTeraPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromPetaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e45)] + [InlineData(2.5, 2.5e45)] + public void PressureFromPetaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromPetaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromExaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void PressureFromExaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromExaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromZettaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e51)] + [InlineData(2.5, 2.5e51)] + public void PressureFromZettaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromZettaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromYottaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e54)] + [InlineData(2.5, 2.5e54)] + public void PressureFromYottaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromYottaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromRonnaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e57)] + [InlineData(2.5, 2.5e57)] + public void PressureFromRonnaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromRonnaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromQuettaPascals should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e60)] + [InlineData(2.5, 2.5e60)] + public void PressureFromQuettaPascalsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromQuettaPascals(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromBars should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e+35)] + [InlineData(2.5, 2.5e+35)] + public void PressureFromBarsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromBars(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromMillibars should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e+32)] + [InlineData(2.5, 2.5e+32)] + public void PressureFromMillibarsShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromMillibars(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromAtmospheres should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.01325e+35)] + [InlineData(2.5, 2.533125e+35)] + public void PressureFromAtmospheresShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromAtmospheres(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromTechnicalAtmospheres should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.80665e+34)] + [InlineData(2.5, 2.4516625e+35)] + public void PressureFromTechnicalAtmospheresShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromTechnicalAtmospheres(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromTorr should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.33322368421053e+32)] + [InlineData(2.5, 3.33305921052632e+32)] + public void PressureFromTorrShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromTorr(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromMillimetersOfMercury should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.33322387415e+32)] + [InlineData(2.5, 3.333059685375e+32)] + public void PressureFromMillimetersOfMercuryShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromMillimetersOfMercury(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromInchesOfMercury should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.386389e+33)] + [InlineData(2.5, 8.4659725e+33)] + public void PressureFromInchesOfMercuryShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromInchesOfMercury(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromPoundsPerSquareInch should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 6.894757293168e+33)] + [InlineData(2.5, 1.723689323292e+34)] + public void PressureFromPoundsPerSquareInchShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromPoundsPerSquareInch(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromPoundsPerSquareFoot should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.788025898e+31)] + [InlineData(2.5, 1.1970064745e+32)] + public void PressureFromPoundsPerSquareFootShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromPoundsPerSquareFoot(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromBarye should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e+29)] + [InlineData(2.5, 2.5e+29)] + public void PressureFromBaryeShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromBarye(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromMillimetersOfWaterColumn should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 9.80665e+30)] + [InlineData(2.5, 2.4516625e+31)] + public void PressureFromMillimetersOfWaterColumnShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromMillimetersOfWaterColumn(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Theory(DisplayName = "Pressure.FromInchesOfWaterColumn should produce the expected QuectoPascals")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 2.4908891e+32)] + [InlineData(2.5, 6.22722275e+32)] + public void PressureFromInchesOfWaterColumnShouldProduceExpectedQuectoPascals(double value, double expectedQuectoPascals) + { + // Given / When + Pressure pressure = Pressure.FromInchesOfWaterColumn(value); + + // Then + Assert.Equal(expectedQuectoPascals, pressure.QuectoPascals, Tolerance); + } + + [Fact(DisplayName = "Pressure.Add should produce the expected result")] + public void PressureAddShouldProduceExpectedValue() + { + // Given + Pressure left = Pressure.FromPascals(1500.0); + Pressure right = Pressure.FromPascals(500.0); + + // When + Pressure result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.Pascals, Tolerance); + } + + [Fact(DisplayName = "Pressure.Subtract should produce the expected result")] + public void PressureSubtractShouldProduceExpectedValue() + { + // Given + Pressure left = Pressure.FromPascals(1500.0); + Pressure right = Pressure.FromPascals(500.0); + + // When + Pressure result = left.Subtract(right); + + // Then + Assert.Equal(1000.0, result.Pascals, Tolerance); + } + + [Fact(DisplayName = "Pressure.Multiply should produce the expected result")] + public void PressureMultiplyShouldProduceExpectedValue() + { + // Given + Pressure left = Pressure.FromPascals(10.0); // 1e31 qPa + Pressure right = Pressure.FromPascals(3.0); // 3e30 qPa + + // When + Pressure result = left.Multiply(right); // 1e31 * 3e30 = 3e61 qPa + + // Then + Assert.Equal(1e31, left.QuectoPascals, Tolerance); + Assert.Equal(3e30, right.QuectoPascals, Tolerance); + Assert.Equal(3e61, result.QuectoPascals, Tolerance); + Assert.Equal(3e31, result.Pascals, Tolerance); + } + + [Fact(DisplayName = "Pressure.Divide should produce the expected result")] + public void PressureDivideShouldProduceExpectedValue() + { + // Given + Pressure left = Pressure.FromPascals(100.0); // 1e32 qPa + Pressure right = Pressure.FromPascals(20.0); // 2e31 qPa + + // When + Pressure result = left.Divide(right); // 1e32 / 2e31 = 5 qPa + + // Then + Assert.Equal(5.0, result.QuectoPascals, Tolerance); + Assert.Equal(5e-30, result.Pascals, Tolerance); + } + + [Fact(DisplayName = "Pressure comparison should produce the expected result (left equal to right)")] + public void PressureComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Pressure left = Pressure.FromPascals(1234.0); + Pressure right = Pressure.FromPascals(1234.0); + + // When / Then + Assert.Equal(0, Pressure.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 = "Pressure comparison should produce the expected result (left greater than right)")] + public void PressureComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Pressure left = Pressure.FromPascals(4567.0); + Pressure right = Pressure.FromPascals(1234.0); + + // When / Then + Assert.Equal(1, Pressure.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 = "Pressure comparison should produce the expected result (left less than right)")] + public void PressureComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Pressure left = Pressure.FromPascals(1234.0); + Pressure right = Pressure.FromPascals(4567.0); + + // When / Then + Assert.Equal(-1, Pressure.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 = "Pressure equality should produce the expected result (left equal to right)")] + public void PressureEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Pressure left = Pressure.FromKiloPascals(2.0); + Pressure right = Pressure.FromPascals(2000.0); + + // When / Then + Assert.True(Pressure.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Pressure equality should produce the expected result (left not equal to right)")] + public void PressureEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Pressure left = Pressure.FromKiloPascals(2.0); + Pressure right = Pressure.FromPascals(2500.0); + + // When / Then + Assert.False(Pressure.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Pressure.ToString should produce the expected result")] + public void PressureToStringShouldProduceExpectedResult() + { + // Given + Pressure pressure = Pressure.FromPascals(101_325.0); // Approximately one atmosphere + + // When / Then + Assert.Equal("101,325.000 Pa", $"{pressure:Pa3}"); + Assert.Equal("101.325 kPa", $"{pressure:kPa3}"); + Assert.Equal("1.013 bar", $"{pressure:bar3}"); + Assert.Equal("1.000 atm", $"{pressure:atm3}"); + Assert.Equal("14.696 psi", $"{pressure:psi3}"); + Assert.Equal("760.000 mmHg", $"{pressure:mmHg3}"); + } + + [Fact(DisplayName = "Pressure.ToString should honor custom culture separators")] + public void PressureToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Pressure pressure = Pressure.FromPascals(1234.56); + + // When + string formatted = pressure.ToString("Pa2", customCulture); + + // Then + // German uses '.' for thousands and ',' for decimals. + Assert.Equal("1.234,56 Pa", formatted); + } +} diff --git a/OnixLabs.Units/Pressure.Arithmetic.Addition.cs b/OnixLabs.Units/Pressure.Arithmetic.Addition.cs new file mode 100644 index 0000000..1e26f26 --- /dev/null +++ b/OnixLabs.Units/Pressure.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Pressure +{ + /// + /// 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 Pressure Add(Pressure left, Pressure right) => new(left.QuectoPascals + right.QuectoPascals); + + /// + /// 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 Pressure operator +(Pressure left, Pressure right) => Add(left, right); + + /// + /// 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 Pressure Add(Pressure other) => Add(this, other); +} diff --git a/OnixLabs.Units/Pressure.Arithmetic.Division.cs b/OnixLabs.Units/Pressure.Arithmetic.Division.cs new file mode 100644 index 0000000..68850f6 --- /dev/null +++ b/OnixLabs.Units/Pressure.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Pressure +{ + /// + /// 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 Pressure Divide(Pressure left, Pressure right) => new(left.QuectoPascals / right.QuectoPascals); + + /// + /// 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 Pressure operator /(Pressure left, Pressure right) => Divide(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 Pressure Divide(Pressure other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Pressure.Arithmetic.Multiplication.cs b/OnixLabs.Units/Pressure.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..76eb126 --- /dev/null +++ b/OnixLabs.Units/Pressure.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Pressure +{ + /// + /// Computes the product of the specified values. + /// + /// The left-hand value to multiply. + /// The right-hand value to multiply. + /// Returns the product of the specified values. + public static Pressure Multiply(Pressure left, Pressure right) => new(left.QuectoPascals * right.QuectoPascals); + + /// + /// Computes the product of the specified values. + /// + /// The left-hand value to multiply. + /// The right-hand value to multiply. + /// Returns the product of the specified values. + public static Pressure operator *(Pressure left, Pressure right) => Multiply(left, right); + + /// + /// Computes the product of the current value and the specified other value. + /// + /// The value to multiply with the current value. + /// Returns the product of the current value and the specified other value. + public Pressure Multiply(Pressure other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Pressure.Arithmetic.Subtraction.cs b/OnixLabs.Units/Pressure.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..2ab54db --- /dev/null +++ b/OnixLabs.Units/Pressure.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Pressure +{ + /// + /// 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. + public static Pressure Subtract(Pressure left, Pressure right) => new(left.QuectoPascals - right.QuectoPascals); + + /// + /// 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. + public static Pressure operator -(Pressure left, Pressure right) => Subtract(left, right); + + /// + /// Computes the difference between the current value and the specified other value. + /// + /// The value to subtract from the current value. + /// Returns the difference between the current value and the specified other value. + public Pressure Subtract(Pressure other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Pressure.Comparable.cs b/OnixLabs.Units/Pressure.Comparable.cs new file mode 100644 index 0000000..47a90b3 --- /dev/null +++ b/OnixLabs.Units/Pressure.Comparable.cs @@ -0,0 +1,103 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Pressure +{ + /// + /// Compares two 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 a value that indicates the relative order of the values being compared. + /// The return value is less than zero if is less than , + /// zero if equals , + /// or greater than zero if is greater than . + /// + public static int Compare(Pressure left, Pressure right) => left.QuectoPascals.CompareTo(right.QuectoPascals); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// + /// Returns a value that indicates the relative order of the values being compared. + /// The return value is less than zero if the current instance is less than , + /// zero if the current instance equals , + /// or greater than zero if the current instance is greater than . + /// + public int CompareTo(Pressure other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the left-hand value is greater than the right-hand value; + /// otherwise, . + /// + public static bool operator >(Pressure left, Pressure right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the left-hand value is greater than or equal to the right-hand value; + /// otherwise, . + /// + public static bool operator >=(Pressure left, Pressure right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the left-hand value is less than the right-hand value; + /// otherwise, . + /// + public static bool operator <(Pressure left, Pressure right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the left-hand value is less than or equal to the right-hand value; + /// otherwise, . + /// + public static bool operator <=(Pressure left, Pressure right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Pressure.Constants.cs b/OnixLabs.Units/Pressure.Constants.cs new file mode 100644 index 0000000..9ee0b43 --- /dev/null +++ b/OnixLabs.Units/Pressure.Constants.cs @@ -0,0 +1,61 @@ +// 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 Pressure +{ + /// + /// Gets a zero value, equal to zero quectopascals. + /// + public static readonly Pressure Zero = new(T.Zero); + + private const string QuectoPascalsSpecifier = "qPa"; + private const string RontoPascalsSpecifier = "rPa"; + private const string YoctoPascalsSpecifier = "yPa"; + private const string ZeptoPascalsSpecifier = "zPa"; + private const string AttoPascalsSpecifier = "aPa"; + private const string FemtoPascalsSpecifier = "fPa"; + private const string PicoPascalsSpecifier = "pPa"; + private const string NanoPascalsSpecifier = "nPa"; + private const string MicroPascalsSpecifier = "uPa"; + private const string MilliPascalsSpecifier = "mPa"; + private const string CentiPascalsSpecifier = "cPa"; + private const string DeciPascalsSpecifier = "dPa"; + private const string PascalsSpecifier = "Pa"; + private const string DecaPascalsSpecifier = "daPa"; + private const string HectoPascalsSpecifier = "hPa"; + private const string KiloPascalsSpecifier = "kPa"; + private const string MegaPascalsSpecifier = "MPa"; + private const string GigaPascalsSpecifier = "GPa"; + private const string TeraPascalsSpecifier = "TPa"; + private const string PetaPascalsSpecifier = "PPa"; + private const string ExaPascalsSpecifier = "EPa"; + private const string ZettaPascalsSpecifier = "ZPa"; + private const string YottaPascalsSpecifier = "YPa"; + private const string RonnaPascalsSpecifier = "RPa"; + private const string QuettaPascalsSpecifier = "QPa"; + private const string BarsSpecifier = "bar"; + private const string MillibarsSpecifier = "mbar"; + private const string AtmospheresSpecifier = "atm"; + private const string TechnicalAtmospheresSpecifier = "at"; + private const string TorrSpecifier = "Torr"; + private const string MillimetersOfMercurySpecifier = "mmHg"; + private const string InchesOfMercurySpecifier = "inHg"; + private const string PoundsPerSquareInchSpecifier = "psi"; + private const string PoundsPerSquareFootSpecifier = "psf"; + private const string BaryeSpecifier = "Ba"; + private const string MillimetersOfWaterColumnSpecifier = "mmH2O"; + private const string InchesOfWaterColumnSpecifier = "inH2O"; +} diff --git a/OnixLabs.Units/Pressure.Equatable.cs b/OnixLabs.Units/Pressure.Equatable.cs new file mode 100644 index 0000000..4001e9f --- /dev/null +++ b/OnixLabs.Units/Pressure.Equatable.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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Pressure +{ + /// + /// Determines whether the specified values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the two specified instances are equal; + /// otherwise, . + /// + public static bool Equals(Pressure left, Pressure right) => Equals(left.QuectoPascals, right.QuectoPascals); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// + /// Returns if the current instance is equal to the specified other instance; + /// otherwise, . + /// + public bool Equals(Pressure other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// + /// Returns if the object is an instance of and + /// is equal to this instance; otherwise, . + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Pressure other && Equals(other); + + /// + /// Returns a hash code for this instance. + /// + /// Returns a hash code for this instance. + public override int GetHashCode() => QuectoPascals.GetHashCode(); + + /// + /// Determines whether two specified instances are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the two specified instances are equal; + /// otherwise, . + /// + public static bool operator ==(Pressure left, Pressure right) => Equals(left, right); + + /// + /// Determines whether two specified instances are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the two specified instances are not equal; + /// otherwise, . + /// + public static bool operator !=(Pressure left, Pressure right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Pressure.Format.cs b/OnixLabs.Units/Pressure.Format.cs new file mode 100644 index 0000000..e3bab31 --- /dev/null +++ b/OnixLabs.Units/Pressure.Format.cs @@ -0,0 +1,32 @@ +// 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 Pressure +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Pressure.From.cs b/OnixLabs.Units/Pressure.From.cs new file mode 100644 index 0000000..d516950 --- /dev/null +++ b/OnixLabs.Units/Pressure.From.cs @@ -0,0 +1,277 @@ +// 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 Pressure +{ + /// + /// Creates a new instance from the specified quectopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromQuectoPascals(T value) => new(value); + + /// + /// Creates a new instance from the specified rontopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromRontoPascals(T value) => new(value * T.CreateChecked(1e3)); + + /// + /// Creates a new instance from the specified yoctopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromYoctoPascals(T value) => new(value * T.CreateChecked(1e6)); + + /// + /// Creates a new instance from the specified zeptopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromZeptoPascals(T value) => new(value * T.CreateChecked(1e9)); + + /// + /// Creates a new instance from the specified attopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromAttoPascals(T value) => new(value * T.CreateChecked(1e12)); + + /// + /// Creates a new instance from the specified femtopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromFemtoPascals(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from the specified picopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromPicoPascals(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from the specified nanopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromNanoPascals(T value) => new(value * T.CreateChecked(1e21)); + + /// + /// Creates a new instance from the specified micropascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromMicroPascals(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from the specified millipascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromMilliPascals(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from the specified centipascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromCentiPascals(T value) => new(value * T.CreateChecked(1e28)); + + /// + /// Creates a new instance from the specified decipascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromDeciPascals(T value) => new(value * T.CreateChecked(1e29)); + + /// + /// Creates a new instance from the specified pascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromPascals(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified decapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromDecaPascals(T value) => new(value * T.CreateChecked(1e31)); + + /// + /// Creates a new instance from the specified hectopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromHectoPascals(T value) => new(value * T.CreateChecked(1e32)); + + /// + /// Creates a new instance from the specified kilopascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromKiloPascals(T value) => new(value * T.CreateChecked(1e33)); + + /// + /// Creates a new instance from the specified megapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromMegaPascals(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from the specified gigapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromGigaPascals(T value) => new(value * T.CreateChecked(1e39)); + + /// + /// Creates a new instance from the specified terapascals value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromTeraPascals(T value) => new(value * T.CreateChecked(1e42)); + + /// + /// Creates a new instance from the specified petapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromPetaPascals(T value) => new(value * T.CreateChecked(1e45)); + + /// + /// Creates a new instance from the specified exapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromExaPascals(T value) => new(value * T.CreateChecked(1e48)); + + /// + /// Creates a new instance from the specified zettapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromZettaPascals(T value) => new(value * T.CreateChecked(1e51)); + + /// + /// Creates a new instance from the specified yottapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromYottaPascals(T value) => new(value * T.CreateChecked(1e54)); + + /// + /// Creates a new instance from the specified ronnapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromRonnaPascals(T value) => new(value * T.CreateChecked(1e57)); + + /// + /// Creates a new instance from the specified quettapascal value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromQuettaPascals(T value) => new(value * T.CreateChecked(1e60)); + + /// + /// Creates a new instance from the specified bar value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromBars(T value) => new(value * T.CreateChecked(1e35)); + + /// + /// Creates a new instance from the specified millibar value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromMillibars(T value) => new(value * T.CreateChecked(1e32)); + + /// + /// Creates a new instance from the specified atmosphere value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromAtmospheres(T value) => new(value * T.CreateChecked(101325e30)); + + /// + /// Creates a new instance from the specified technical atmosphere value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromTechnicalAtmospheres(T value) => new(value * T.CreateChecked(98066.5e30)); + + /// + /// Creates a new instance from the specified torr value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromTorr(T value) => new(value * T.CreateChecked(133.32236842105263e30)); + + /// + /// Creates a new instance from the specified millimetres of mercury value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromMillimetersOfMercury(T value) => new(value * T.CreateChecked(133.322387415e30)); + + /// + /// Creates a new instance from the specified inches of mercury value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromInchesOfMercury(T value) => new(value * T.CreateChecked(3386.389e30)); + + /// + /// Creates a new instance from the specified pounds per square inch value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromPoundsPerSquareInch(T value) => new(value * T.CreateChecked(6894.757293168e30)); + + /// + /// Creates a new instance from the specified pounds per square foot value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromPoundsPerSquareFoot(T value) => new(value * T.CreateChecked(47.88025898e30)); + + /// + /// Creates a new instance from the specified barye value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromBarye(T value) => new(value * T.CreateChecked(0.1e30)); + + /// + /// Creates a new instance from the specified millimetres of water column value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromMillimetersOfWaterColumn(T value) => new(value * T.CreateChecked(9.80665e30)); + + /// + /// Creates a new instance from the specified inches of water column value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Pressure FromInchesOfWaterColumn(T value) => new(value * T.CreateChecked(249.08891e30)); +} diff --git a/OnixLabs.Units/Pressure.To.cs b/OnixLabs.Units/Pressure.To.cs new file mode 100644 index 0000000..26b288b --- /dev/null +++ b/OnixLabs.Units/Pressure.To.cs @@ -0,0 +1,100 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Pressure +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(QuectoPascalsSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => + ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or null to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: QuectoPascalsSpecifier); + + T value = specifier switch + { + QuectoPascalsSpecifier => QuectoPascals, + RontoPascalsSpecifier => RontoPascals, + YoctoPascalsSpecifier => YoctoPascals, + ZeptoPascalsSpecifier => ZeptoPascals, + AttoPascalsSpecifier => AttoPascals, + FemtoPascalsSpecifier => FemtoPascals, + PicoPascalsSpecifier => PicoPascals, + NanoPascalsSpecifier => NanoPascals, + MicroPascalsSpecifier => MicroPascals, + MilliPascalsSpecifier => MilliPascals, + CentiPascalsSpecifier => CentiPascals, + DeciPascalsSpecifier => DeciPascals, + PascalsSpecifier => Pascals, + DecaPascalsSpecifier => DecaPascals, + HectoPascalsSpecifier => HectoPascals, + KiloPascalsSpecifier => KiloPascals, + MegaPascalsSpecifier => MegaPascals, + GigaPascalsSpecifier => GigaPascals, + TeraPascalsSpecifier => TeraPascals, + PetaPascalsSpecifier => PetaPascals, + ExaPascalsSpecifier => ExaPascals, + ZettaPascalsSpecifier => ZettaPascals, + YottaPascalsSpecifier => YottaPascals, + RonnaPascalsSpecifier => RonnaPascals, + QuettaPascalsSpecifier => QuettaPascals, + BarsSpecifier => Bars, + MillibarsSpecifier => Millibars, + AtmospheresSpecifier => Atmospheres, + TechnicalAtmospheresSpecifier => TechnicalAtmospheres, + TorrSpecifier => Torr, + MillimetersOfMercurySpecifier => MillimetersOfMercury, + InchesOfMercurySpecifier => InchesOfMercury, + PoundsPerSquareInchSpecifier => PoundsPerSquareInch, + PoundsPerSquareFootSpecifier => PoundsPerSquareFoot, + BaryeSpecifier => Barye, + MillimetersOfWaterColumnSpecifier => MillimetersOfWaterColumn, + InchesOfWaterColumnSpecifier => InchesOfWaterColumn, + _ => throw new ArgumentException( + $"Format '{format.ToString()}' is invalid. " + + "Valid format specifiers are: " + + "qPa, rPa, yPa, zPa, aPa, fPa, pPa, nPa, uPa, mPa, cPa, dPa, Pa, daPa, hPa, kPa, MPa, GPa, TPa, " + + "PPa, EPa, ZPa, YPa, RPa, QPa, bar, mbar, atm, at, Torr, mmHg, inHg, psi, psf, Ba, mmH2O, inH2O. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Pressure.cs b/OnixLabs.Units/Pressure.cs new file mode 100644 index 0000000..4ce77c8 --- /dev/null +++ b/OnixLabs.Units/Pressure.cs @@ -0,0 +1,222 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of pressure. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Pressure : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The pressure unit in . + private Pressure(T value) => QuectoPascals = value; + + /// + /// Gets the pressure in quectopascals (qPa). + /// + public T QuectoPascals { get; } + + /// + /// Gets the pressure in rontopascals (rPa). + /// + public T RontoPascals => QuectoPascals / T.CreateChecked(1e3); + + /// + /// Gets the pressure in yoctopascals (yPa). + /// + public T YoctoPascals => QuectoPascals / T.CreateChecked(1e6); + + /// + /// Gets the pressure in zeptopascals (zPa). + /// + public T ZeptoPascals => QuectoPascals / T.CreateChecked(1e9); + + /// + /// Gets the pressure in attopascals (aPa). + /// + public T AttoPascals => QuectoPascals / T.CreateChecked(1e12); + + /// + /// Gets the pressure in femtopascals (fPa). + /// + public T FemtoPascals => QuectoPascals / T.CreateChecked(1e15); + + /// + /// Gets the pressure in picopascals (pPa). + /// + public T PicoPascals => QuectoPascals / T.CreateChecked(1e18); + + /// + /// Gets the pressure in nanopascals (nPa). + /// + public T NanoPascals => QuectoPascals / T.CreateChecked(1e21); + + /// + /// Gets the pressure in micropascals (µPa). + /// + public T MicroPascals => QuectoPascals / T.CreateChecked(1e24); + + /// + /// Gets the pressure in millipascals (mPa). + /// + public T MilliPascals => QuectoPascals / T.CreateChecked(1e27); + + /// + /// Gets the pressure in centipascals (cPa). + /// + public T CentiPascals => QuectoPascals / T.CreateChecked(1e28); + + /// + /// Gets the pressure in decipascals (dPa). + /// + public T DeciPascals => QuectoPascals / T.CreateChecked(1e29); + + /// + /// Gets the pressure in pascals (Pa). + /// + public T Pascals => QuectoPascals / T.CreateChecked(1e30); + + /// + /// Gets the pressure in decapascals (daPa). + /// + public T DecaPascals => QuectoPascals / T.CreateChecked(1e31); + + /// + /// Gets the pressure in hectopascals (hPa). + /// + public T HectoPascals => QuectoPascals / T.CreateChecked(1e32); + + /// + /// Gets the pressure in kilopascals (kPa). + /// + public T KiloPascals => QuectoPascals / T.CreateChecked(1e33); + + /// + /// Gets the pressure in megapascals (MPa). + /// + public T MegaPascals => QuectoPascals / T.CreateChecked(1e36); + + /// + /// Gets the pressure in gigapascals (GPa). + /// + public T GigaPascals => QuectoPascals / T.CreateChecked(1e39); + + /// + /// Gets the pressure in terapascals (TPa). + /// + public T TeraPascals => QuectoPascals / T.CreateChecked(1e42); + + /// + /// Gets the pressure in petapascals (PPa). + /// + public T PetaPascals => QuectoPascals / T.CreateChecked(1e45); + + /// + /// Gets the pressure in exapascals (EPa). + /// + public T ExaPascals => QuectoPascals / T.CreateChecked(1e48); + + /// + /// Gets the pressure in zettapascals (ZPa). + /// + public T ZettaPascals => QuectoPascals / T.CreateChecked(1e51); + + /// + /// Gets the pressure in yottapascals (YPa). + /// + public T YottaPascals => QuectoPascals / T.CreateChecked(1e54); + + /// + /// Gets the pressure in ronnapascals (RPa). + /// + public T RonnaPascals => QuectoPascals / T.CreateChecked(1e57); + + /// + /// Gets the pressure in quettapascals (QPa). + /// + public T QuettaPascals => QuectoPascals / T.CreateChecked(1e60); + + /// + /// Gets the pressure in bars (bar). + /// + public T Bars => QuectoPascals / T.CreateChecked(1e35); + + /// + /// Gets the pressure in millibars (mbar). + /// + public T Millibars => QuectoPascals / T.CreateChecked(1e32); + + /// + /// Gets the pressure in standard atmospheres (atm). + /// + public T Atmospheres => QuectoPascals / T.CreateChecked(101325e30); + + /// + /// Gets the pressure in technical atmospheres (at). + /// + public T TechnicalAtmospheres => QuectoPascals / T.CreateChecked(98066.5e30); + + /// + /// Gets the pressure in torr (Torr). + /// + public T Torr => QuectoPascals / T.CreateChecked(133.32236842105263e30); + + /// + /// Gets the pressure in millimetres of mercury (mmHg). + /// + public T MillimetersOfMercury => QuectoPascals / T.CreateChecked(133.322387415e30); + + /// + /// Gets the pressure in inches of mercury (inHg). + /// + public T InchesOfMercury => QuectoPascals / T.CreateChecked(3386.389e30); + + /// + /// Gets the pressure in pounds per square inch (psi). + /// + public T PoundsPerSquareInch => QuectoPascals / T.CreateChecked(6894.757293168e30); + + /// + /// Gets the pressure in pounds per square foot (psf). + /// + public T PoundsPerSquareFoot => QuectoPascals / T.CreateChecked(47.88025898e30); + + /// + /// Gets the pressure in barye (Ba). + /// + public T Barye => QuectoPascals / T.CreateChecked(0.1e30); + + /// + /// Gets the pressure in millimetres of water column (mmH₂O). + /// + public T MillimetersOfWaterColumn => QuectoPascals / T.CreateChecked(9.80665e30); + + /// + /// Gets the pressure in inches of water column (inH₂O). + /// + public T InchesOfWaterColumn => QuectoPascals / T.CreateChecked(249.08891e30); +} From 6fc819114c3847a02e28e181f6219d164bce1adb Mon Sep 17 00:00:00 2001 From: matthew Date: Thu, 20 Nov 2025 00:14:40 +0000 Subject: [PATCH 20/23] Simplified the exception messaging in Unit string formatting. --- OnixLabs.Units/Area.To.cs | 13 +++----- OnixLabs.Units/DataSize.To.cs | 11 +++---- OnixLabs.Units/Distance.To.cs | 11 +++---- .../Extensions.ArgumentException.cs | 32 +++++++++++++++++++ OnixLabs.Units/Force.To.cs | 14 +++----- OnixLabs.Units/Mass.To.cs | 11 +++---- OnixLabs.Units/Pressure.To.cs | 11 +++---- OnixLabs.Units/Pressure.cs | 4 +-- OnixLabs.Units/Speed.To.cs | 10 +++--- OnixLabs.Units/Temperature.To.cs | 2 +- OnixLabs.Units/Volume.To.cs | 10 +++--- onixlabs-dotnet.sln.DotSettings | 6 ++++ 12 files changed, 75 insertions(+), 60 deletions(-) create mode 100644 OnixLabs.Units/Extensions.ArgumentException.cs diff --git a/OnixLabs.Units/Area.To.cs b/OnixLabs.Units/Area.To.cs index f3b7408..fd14a6c 100644 --- a/OnixLabs.Units/Area.To.cs +++ b/OnixLabs.Units/Area.To.cs @@ -32,8 +32,7 @@ public readonly partial struct Area /// A standard or custom format string. /// An object that provides culture-specific formatting information. /// Returns a string representation of the current instance in the specified format. - public string ToString(string? format, IFormatProvider? formatProvider = null) => - ToString(format.AsSpan(), formatProvider); + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); /// /// Returns a string representation of the current instance using the specified format and format provider. @@ -74,12 +73,10 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide SquareMilesSpecifier => SquareMiles, AcresSpecifier => Acres, HectaresSpecifier => Hectares, - _ => throw new ArgumentException( - $"Format '{format.ToString()}' is invalid. Valid format specifiers are: " + - "sqym, sqzm, sqam, sqfm, sqpm, sqnm, squm, sqmm, sqcm, sqdm, sqm, sqdam, sqhm, sqkm, " + - "sqMm, sqGm, sqTm, sqPm, sqEm, sqZm, sqYm, sqin, sqft, sqyd, sqmi, ac, ha. " + - "Format specifiers may also be suffixed with a scale value.", - nameof(format)) + _ => throw ArgumentException.InvalidFormat(format, + "sqym, sqzm, sqam, sqfm, sqpm, sqnm, squm, sqmm, sqcm, " + + "sqdm, sqm, sqdam, sqhm, sqkm, sqMm, sqGm, sqTm, sqPm, " + + "sqEm, sqZm, sqYm, sqin, sqft, sqyd, sqmi, ac, and ha") }; T rounded = scale > 0 ? T.Round(value, scale) : value; diff --git a/OnixLabs.Units/DataSize.To.cs b/OnixLabs.Units/DataSize.To.cs index e06a355..ba55451 100755 --- a/OnixLabs.Units/DataSize.To.cs +++ b/OnixLabs.Units/DataSize.To.cs @@ -80,13 +80,10 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide YobiBytesSpecifier => YobiBytes, YottaBitsSpecifier => YottaBits, YottaBytesSpecifier => YottaBytes, - _ => throw new ArgumentException( - $"Format '{format}' is invalid. Valid format specifiers are: " + - "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, YB. " + - "Format specifiers may also be suffixed with a scale value.", - nameof(format)) + _ => 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; diff --git a/OnixLabs.Units/Distance.To.cs b/OnixLabs.Units/Distance.To.cs index f7aece5..0526fb8 100644 --- a/OnixLabs.Units/Distance.To.cs +++ b/OnixLabs.Units/Distance.To.cs @@ -81,13 +81,10 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide AstronomicalUnitsSpecifier => AstronomicalUnits, LightYearsSpecifier => LightYears, ParsecsSpecifier => Parsecs, - _ => throw new ArgumentException( - $"Format '{format.ToString()}' is invalid. " + - "Valid format specifiers are: " + - "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, pc. " + - "Format specifiers may also be suffixed with a scale value.", - nameof(format)) + _ => 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; diff --git a/OnixLabs.Units/Extensions.ArgumentException.cs b/OnixLabs.Units/Extensions.ArgumentException.cs new file mode 100644 index 0000000..acd6929 --- /dev/null +++ b/OnixLabs.Units/Extensions.ArgumentException.cs @@ -0,0 +1,32 @@ +// 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.Runtime.CompilerServices; + +namespace OnixLabs.Units; + +internal static class ArgumentExceptionExtensions +{ + 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/Force.To.cs b/OnixLabs.Units/Force.To.cs index caddb6d..3c41fcc 100644 --- a/OnixLabs.Units/Force.To.cs +++ b/OnixLabs.Units/Force.To.cs @@ -32,8 +32,7 @@ public readonly partial struct Force /// The format to use, or null to use the default format. /// The provider to use to format the value. /// Returns the value of the current instance in the specified format. - public string ToString(string? format, IFormatProvider? formatProvider = null) => - ToString(format.AsSpan(), formatProvider); + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); /// /// Formats the value of the current instance using the specified format. @@ -73,13 +72,10 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide PoundalsSpecifier => Poundals, ShortTonForceSpecifier => ShortTonForce, LongTonForceSpecifier => LongTonForce, - _ => throw new ArgumentException( - $"Format '{format.ToString()}' is invalid. " + - "Valid format specifiers are: " + - "yN, zN, aN, fN, pN, nN, uN, mN, N, kN, MN, GN, TN, PN, EN, ZN, YN, " + - "dyn, kgf, gf, tf, lbf, ozf, pdl, tonf, ltf. " + - "Format specifiers may also be suffixed with a scale value.", - nameof(format)) + _ => throw ArgumentException.InvalidFormat(format, + "yN, zN, aN, fN, pN, nN, uN, mN, N, " + + "kN, MN, GN, TN, PN, EN, ZN, YN, dyn, " + + "kgf, gf, tf, lbf, ozf, pdl, tonf, and ltf") }; T rounded = scale > 0 ? T.Round(value, scale) : value; diff --git a/OnixLabs.Units/Mass.To.cs b/OnixLabs.Units/Mass.To.cs index bb09f87..a5f2742 100644 --- a/OnixLabs.Units/Mass.To.cs +++ b/OnixLabs.Units/Mass.To.cs @@ -76,13 +76,10 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide TroyPoundsSpecifier => TroyPounds, TroyOuncesSpecifier => TroyOunces, PennyweightsSpecifier => Pennyweights, - _ => throw new ArgumentException( - $"Format '{format.ToString()}' is invalid. " + - "Valid format specifiers are: " + - "yg, zg, ag, fg, pg, ng, ug, mg, g, kg, Mg, t, Gg, Tg, Pg, Eg, Zg, Yg, " + - "lb, oz, st, gr, ton, lt, cwtUS, cwtUK, qr, lbt, ozt, dwt. " + - "Format specifiers may also be suffixed with a scale value.", - nameof(format)) + _ => throw ArgumentException.InvalidFormat(format, + "yg, zg, ag, fg, pg, ng, ug, mg, g, kg, Mg, " + + "t, Gg, Tg, Pg, Eg, Zg, Yg, lb, oz, st, gr, " + + "ton, lt, cwtUS, cwtUK, qr, lbt, ozt, and dwt") }; T rounded = scale > 0 ? T.Round(value, scale) : value; diff --git a/OnixLabs.Units/Pressure.To.cs b/OnixLabs.Units/Pressure.To.cs index 26b288b..0f49bed 100644 --- a/OnixLabs.Units/Pressure.To.cs +++ b/OnixLabs.Units/Pressure.To.cs @@ -84,13 +84,10 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide BaryeSpecifier => Barye, MillimetersOfWaterColumnSpecifier => MillimetersOfWaterColumn, InchesOfWaterColumnSpecifier => InchesOfWaterColumn, - _ => throw new ArgumentException( - $"Format '{format.ToString()}' is invalid. " + - "Valid format specifiers are: " + - "qPa, rPa, yPa, zPa, aPa, fPa, pPa, nPa, uPa, mPa, cPa, dPa, Pa, daPa, hPa, kPa, MPa, GPa, TPa, " + - "PPa, EPa, ZPa, YPa, RPa, QPa, bar, mbar, atm, at, Torr, mmHg, inHg, psi, psf, Ba, mmH2O, inH2O. " + - "Format specifiers may also be suffixed with a scale value.", - nameof(format)) + _ => throw ArgumentException.InvalidFormat(format, + "qPa, rPa, yPa, zPa, aPa, fPa, pPa, nPa, uPa, mPa, cPa, dPa, Pa, " + + "daPa, hPa, kPa, MPa, GPa, TPa, PPa, EPa, ZPa, YPa, RPa, QPa, bar, " + + "mbar, atm, at, Torr, mmHg, inHg, psi, psf, Ba, mmwc, and inwc") }; T rounded = scale > 0 ? T.Round(value, scale) : value; diff --git a/OnixLabs.Units/Pressure.cs b/OnixLabs.Units/Pressure.cs index 4ce77c8..4f100d9 100644 --- a/OnixLabs.Units/Pressure.cs +++ b/OnixLabs.Units/Pressure.cs @@ -211,12 +211,12 @@ namespace OnixLabs.Units; public T Barye => QuectoPascals / T.CreateChecked(0.1e30); /// - /// Gets the pressure in millimetres of water column (mmH₂O). + /// Gets the pressure in millimetres of water column (mmwc). /// public T MillimetersOfWaterColumn => QuectoPascals / T.CreateChecked(9.80665e30); /// - /// Gets the pressure in inches of water column (inH₂O). + /// Gets the pressure in inches of water column (inwc). /// public T InchesOfWaterColumn => QuectoPascals / T.CreateChecked(249.08891e30); } diff --git a/OnixLabs.Units/Speed.To.cs b/OnixLabs.Units/Speed.To.cs index dbea682..eec1817 100644 --- a/OnixLabs.Units/Speed.To.cs +++ b/OnixLabs.Units/Speed.To.cs @@ -77,12 +77,10 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide KilometersPerHourSpecifier => KilometersPerHour, MilesPerHourSpecifier => MilesPerHour, KnotsSpecifier => Knots, - _ => throw new ArgumentException( - $"Format '{format.ToString()}' is invalid. Valid format specifiers are: " + - "qmps, rmps, ymps, zmps, amps, fmps, pmps, nmps, umps, mmps, cmps, dmps, mps, damps, " + - "hmps, kmps, Mmps, Gmps, Tmps, Pmps, Emps, Zmps, Ymps, Rmps, Qmps, ips, fps, kmph, mph, kt. " + - "Format specifiers may also be suffixed with a scale value.", - nameof(format)) + _ => throw ArgumentException.InvalidFormat(format, + "qmps, rmps, ymps, zmps, amps, fmps, pmps, nmps, umps, mmps, " + + "cmps, dmps, mps, damps, hmps, kmps, Mmps, Gmps, Tmps, Pmps, " + + "Emps, Zmps, Ymps, Rmps, Qmps, ips, fps, kmph, mph, and kt") }; T rounded = scale > 0 ? T.Round(value, scale) : value; diff --git a/OnixLabs.Units/Temperature.To.cs b/OnixLabs.Units/Temperature.To.cs index 47aa77e..0fd091d 100644 --- a/OnixLabs.Units/Temperature.To.cs +++ b/OnixLabs.Units/Temperature.To.cs @@ -54,7 +54,7 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide RankineSpecifier => (Rankine, RankineSymbol), ReaumurSpecifier => (Reaumur, ReaumurSymbol), RomerSpecifier => (Romer, RomerSymbol), - _ => throw new ArgumentException($"Format '{format}' is invalid. Valid format specifiers are: C, De, F, K, N, R, Re, and Ro. Format specifiers may also be suffixed with a scale value.", nameof(format)) + _ => throw ArgumentException.InvalidFormat(format, "C, De, F, K, N, R, Re, and Ro") }; T rounded = scale > 0 ? T.Round(value, scale) : value; diff --git a/OnixLabs.Units/Volume.To.cs b/OnixLabs.Units/Volume.To.cs index 1c047c2..c9776ea 100644 --- a/OnixLabs.Units/Volume.To.cs +++ b/OnixLabs.Units/Volume.To.cs @@ -79,12 +79,10 @@ public string ToString(ReadOnlySpan format, IFormatProvider? formatProvide PintsSpecifier => Pints, QuartsSpecifier => Quarts, GallonsSpecifier => Gallons, - _ => throw new ArgumentException( - $"Format '{format.ToString()}' is invalid. Valid format specifiers are: " + - "cuym, cuzm, cuam, cufm, cupm, cunm, cuum, cumm, cucm, cudm, cum, cudam, cuhm, cukm, cuMm, " + - "cuGm, cuTm, cuPm, cuEm, cuZm, cuYm, l, ml, cl, dl, cuin, cuft, cuyd, floz, cup, pt, qt, gal. " + - "Format specifiers may also be suffixed with a scale value.", - nameof(format)) + _ => throw ArgumentException.InvalidFormat(format, + "cuym, cuzm, cuam, cufm, cupm, cunm, cuum, cumm, cucm, cudm, " + + "cum, cudam, cuhm, cukm, cuMm, cuGm, cuTm, cuPm, cuEm, cuZm, " + + "cuYm, l, ml, cl, dl, cuin, cuft, cuyd, floz, cup, pt, qt, and gal") }; T rounded = scale > 0 ? T.Round(value, scale) : value; diff --git a/onixlabs-dotnet.sln.DotSettings b/onixlabs-dotnet.sln.DotSettings index a4b79fd..d34cd46 100644 --- a/onixlabs-dotnet.sln.DotSettings +++ b/onixlabs-dotnet.sln.DotSettings @@ -29,6 +29,7 @@ limitations under the License. True True True + True True True True @@ -63,6 +64,7 @@ limitations under the License. True True True + True True True True @@ -86,13 +88,17 @@ limitations under the License. True True True + True True + True True True True True + True True True + True True True True From ffa62ae6f4e0469c6f4fb1fb8bca65d4254ab9d1 Mon Sep 17 00:00:00 2001 From: matthew Date: Thu, 20 Nov 2025 09:27:50 +0000 Subject: [PATCH 21/23] Added Frequency unit and unit tests --- OnixLabs.Numerics/GenericMath.cs | 6 +- OnixLabs.Units.UnitTests/FrequencyTests.cs | 437 ++++++++++++++++++ .../Frequency.Arithmetic.Addition.cs | 42 ++ .../Frequency.Arithmetic.Division.cs | 42 ++ .../Frequency.Arithmetic.Multiplication.cs | 42 ++ .../Frequency.Arithmetic.Subtraction.cs | 42 ++ OnixLabs.Units/Frequency.Comparable.cs | 79 ++++ OnixLabs.Units/Frequency.Constants.cs | 46 ++ OnixLabs.Units/Frequency.Equatable.cs | 65 +++ OnixLabs.Units/Frequency.Format.cs | 32 ++ OnixLabs.Units/Frequency.From.cs | 144 ++++++ OnixLabs.Units/Frequency.To.cs | 76 +++ OnixLabs.Units/Frequency.cs | 127 +++++ 13 files changed, 1177 insertions(+), 3 deletions(-) create mode 100644 OnixLabs.Units.UnitTests/FrequencyTests.cs create mode 100644 OnixLabs.Units/Frequency.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Frequency.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Frequency.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Frequency.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Frequency.Comparable.cs create mode 100644 OnixLabs.Units/Frequency.Constants.cs create mode 100644 OnixLabs.Units/Frequency.Equatable.cs create mode 100644 OnixLabs.Units/Frequency.Format.cs create mode 100644 OnixLabs.Units/Frequency.From.cs create mode 100644 OnixLabs.Units/Frequency.To.cs create mode 100644 OnixLabs.Units/Frequency.cs diff --git a/OnixLabs.Numerics/GenericMath.cs b/OnixLabs.Numerics/GenericMath.cs index 25971b4..0c52162 100644 --- a/OnixLabs.Numerics/GenericMath.cs +++ b/OnixLabs.Numerics/GenericMath.cs @@ -40,7 +40,7 @@ public static class GenericMath /// Returns the factorial of the specified value. public static BigInteger Factorial(T value) where T : IBinaryInteger { - Require(value >= T.Zero, "Value must be greater than or equal to zero."); + Require(value >= T.Zero, "Value must be greater than or equal to zero.", nameof(value)); if (value <= T.One) return BigInteger.One; @@ -84,7 +84,7 @@ public static T Pow10(int exponent) where T : INumber { Require(exponent >= 0, "Exponent must be greater than, or equal to zero.", nameof(exponent)); - if (exponent == 0) + if (exponent is 0) return T.One; T result = T.One; @@ -92,7 +92,7 @@ public static T Pow10(int exponent) where T : INumber while (exponent > 0) { - if ((exponent & 1) == 1) + if ((exponent & 1) is 1) result *= baseValue; baseValue *= baseValue; diff --git a/OnixLabs.Units.UnitTests/FrequencyTests.cs b/OnixLabs.Units.UnitTests/FrequencyTests.cs new file mode 100644 index 0000000..2910546 --- /dev/null +++ b/OnixLabs.Units.UnitTests/FrequencyTests.cs @@ -0,0 +1,437 @@ +// 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; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class FrequencyTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+42; + + [Fact(DisplayName = "Frequency.Zero should produce the expected result")] + public void FrequencyZeroShouldProduceExpectedResult() + { + // Given / When + Frequency frequency = Frequency.Zero; + + // Then + Assert.Equal(0.0, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromYoctoHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void FrequencyFromYoctoHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromYoctoHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromZeptoHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e3)] + [InlineData(2.5, 2.5e3)] + public void FrequencyFromZeptoHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromZeptoHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromAttoHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void FrequencyFromAttoHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromAttoHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromFemtoHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e9)] + [InlineData(2.5, 2.5e9)] + public void FrequencyFromFemtoHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromFemtoHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromPicoHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void FrequencyFromPicoHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromPicoHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromNanoHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e15)] + [InlineData(2.5, 2.5e15)] + public void FrequencyFromNanoHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromNanoHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromMicroHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void FrequencyFromMicroHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromMicroHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromMilliHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e21)] + [InlineData(2.5, 2.5e21)] + public void FrequencyFromMilliHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromMilliHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void FrequencyFromHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromKiloHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void FrequencyFromKiloHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromKiloHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromMegaHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void FrequencyFromMegaHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromMegaHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromGigaHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e33)] + [InlineData(2.5, 2.5e33)] + public void FrequencyFromGigaHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromGigaHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromTeraHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void FrequencyFromTeraHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromTeraHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromPetaHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e39)] + [InlineData(2.5, 2.5e39)] + public void FrequencyFromPetaHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromPetaHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromExaHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void FrequencyFromExaHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromExaHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromZettaHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e45)] + [InlineData(2.5, 2.5e45)] + public void FrequencyFromZettaHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromZettaHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromYottaHertz should produce the expected YoctoHertz")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void FrequencyFromYottaHertzShouldProduceExpectedYoctoHertz(double value, double expectedYoctoHertz) + { + // Given / When + Frequency frequency = Frequency.FromYottaHertz(value); + + // Then + Assert.Equal(expectedYoctoHertz, frequency.YoctoHertz, Tolerance); + } + + [Theory(DisplayName = "Frequency.FromRevolutionsPerMinute should produce the expected Hertz")] + [InlineData(0.0, 0.0)] + [InlineData(60.0, 1.0)] + [InlineData(3600.0, 60.0)] + public void FrequencyFromRevolutionsPerMinuteShouldProduceExpectedHertz(double rpm, double expectedHertz) + { + // Given / When + Frequency frequency = Frequency.FromRevolutionsPerMinute(rpm); + + // Then + Assert.Equal(expectedHertz, frequency.Hertz, 1e-12); + } + + [Fact(DisplayName = "Frequency.Add should produce the expected result")] + public void FrequencyAddShouldProduceExpectedValue() + { + // Given + Frequency left = Frequency.FromHertz(1500.0); + Frequency right = Frequency.FromHertz(500.0); + + // When + Frequency result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.Hertz, Tolerance); + } + + [Fact(DisplayName = "Frequency.Subtract should produce the expected result")] + public void FrequencySubtractShouldProduceExpectedValue() + { + // Given + Frequency left = Frequency.FromHertz(1500.0); + Frequency right = Frequency.FromHertz(500.0); + + // When + Frequency result = left.Subtract(right); + + // Then + Assert.Equal(1000.0, result.Hertz, Tolerance); + } + + [Fact(DisplayName = "Frequency.Multiply should produce the expected result")] + public void FrequencyMultiplyShouldProduceExpectedValue() + { + // Given + Frequency left = Frequency.FromHertz(10.0); // 1e25 yHz + Frequency right = Frequency.FromHertz(3.0); // 3e24 yHz + + // When + Frequency result = left.Multiply(right); // 1e25 * 3e24 = 3e49 yHz + + // Then + Assert.Equal(1e25, left.YoctoHertz, Tolerance); + Assert.Equal(3e24, right.YoctoHertz, Tolerance); + Assert.Equal(3e49, result.YoctoHertz, Tolerance); + Assert.Equal(3e25, result.Hertz, Tolerance); + } + + [Fact(DisplayName = "Frequency.Divide should produce the expected result")] + public void FrequencyDivideShouldProduceExpectedValue() + { + // Given + Frequency left = Frequency.FromHertz(100.0); // 1e26 yHz + Frequency right = Frequency.FromHertz(20.0); // 2e25 yHz + + // When + Frequency result = left.Divide(right); // 1e26 / 2e25 = 5 yHz + + // Then + Assert.Equal(5.0, result.YoctoHertz, Tolerance); + Assert.Equal(5e-24, result.Hertz, Tolerance); + } + + [Fact(DisplayName = "Frequency comparison should produce the expected result (left equal to right)")] + public void FrequencyComparisonShouldProduceExpectedLeftEqualToRight() + { + // Given + Frequency left = Frequency.FromHertz(1234.0); + Frequency right = Frequency.FromHertz(1234.0); + + // When / Then + Assert.Equal(0, Frequency.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 = "Frequency comparison should produce the expected result (left greater than right)")] + public void FrequencyComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Frequency left = Frequency.FromHertz(4567.0); + Frequency right = Frequency.FromHertz(1234.0); + + // When / Then + Assert.Equal(1, Frequency.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 = "Frequency comparison should produce the expected result (left less than right)")] + public void FrequencyComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Frequency left = Frequency.FromHertz(1234.0); + Frequency right = Frequency.FromHertz(4567.0); + + // When / Then + Assert.Equal(-1, Frequency.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 = "Frequency equality should produce the expected result (left equal to right)")] + public void FrequencyEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Frequency left = Frequency.FromKiloHertz(2.0); + Frequency right = Frequency.FromHertz(2000.0); + + // When / Then + Assert.True(Frequency.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Frequency equality should produce the expected result (left not equal to right)")] + public void FrequencyEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Frequency left = Frequency.FromKiloHertz(2.0); + Frequency right = Frequency.FromHertz(2500.0); + + // When / Then + Assert.False(Frequency.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Frequency.ToString should produce the expected result")] + public void FrequencyToStringShouldProduceExpectedResult() + { + // Given + Frequency frequency = Frequency.FromHertz(1000.0); + + // When / Then + Assert.Equal("1,000.000 Hz", $"{frequency:Hz3}"); + Assert.Equal("1.000 kHz", $"{frequency:kHz3}"); + Assert.Equal("0.001 MHz", $"{frequency:MHz3}"); + Assert.Equal("60,000.000 rpm", $"{frequency:rpm3}"); + } + + [Fact(DisplayName = "Frequency.ToString should honor custom culture separators")] + public void FrequencyToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Frequency frequency = Frequency.FromHertz(1234.56); + + // When + string formatted = frequency.ToString("Hz2", customCulture); + + // Then + // German uses '.' for thousands and ',' for decimals. + Assert.Equal("1.234,56 Hz", formatted); + } +} diff --git a/OnixLabs.Units/Frequency.Arithmetic.Addition.cs b/OnixLabs.Units/Frequency.Arithmetic.Addition.cs new file mode 100644 index 0000000..4846cf4 --- /dev/null +++ b/OnixLabs.Units/Frequency.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Frequency +{ + /// + /// 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 Frequency Add(Frequency left, Frequency right) => new(left.YoctoHertz + right.YoctoHertz); + + /// + /// 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 Frequency operator +(Frequency left, Frequency right) => Add(left, right); + + /// + /// 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 Frequency Add(Frequency other) => Add(this, other); +} diff --git a/OnixLabs.Units/Frequency.Arithmetic.Division.cs b/OnixLabs.Units/Frequency.Arithmetic.Division.cs new file mode 100644 index 0000000..91d731d --- /dev/null +++ b/OnixLabs.Units/Frequency.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Frequency +{ + /// + /// 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 Frequency Divide(Frequency left, Frequency right) => new(left.YoctoHertz / right.YoctoHertz); + + /// + /// 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 Frequency operator /(Frequency left, Frequency right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value, divided by the specified other value. + public Frequency Divide(Frequency other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Frequency.Arithmetic.Multiplication.cs b/OnixLabs.Units/Frequency.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..53fde35 --- /dev/null +++ b/OnixLabs.Units/Frequency.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Frequency +{ + /// + /// 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 Frequency Multiply(Frequency left, Frequency right) => new(left.YoctoHertz * right.YoctoHertz); + + /// + /// 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 Frequency operator *(Frequency left, Frequency right) => Multiply(left, right); + + /// + /// Computes the product of the current value multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value multiplied by the specified other value. + public Frequency Multiply(Frequency other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Frequency.Arithmetic.Subtraction.cs b/OnixLabs.Units/Frequency.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..aeadee1 --- /dev/null +++ b/OnixLabs.Units/Frequency.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Frequency +{ + /// + /// 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 Frequency Subtract(Frequency left, Frequency right) => new(left.YoctoHertz - right.YoctoHertz); + + /// + /// 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 Frequency operator -(Frequency left, Frequency right) => Subtract(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 Frequency Subtract(Frequency other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Frequency.Comparable.cs b/OnixLabs.Units/Frequency.Comparable.cs new file mode 100644 index 0000000..833c098 --- /dev/null +++ b/OnixLabs.Units/Frequency.Comparable.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 OnixLabs.Core; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Frequency +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(Frequency left, Frequency right) => left.YoctoHertz.CompareTo(right.YoctoHertz); + + /// + /// Compares the current instance with another value and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other value. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(Frequency other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than the right-hand operand; otherwise, . + public static bool operator >(Frequency left, Frequency right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(Frequency left, Frequency right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(Frequency left, Frequency right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(Frequency left, Frequency right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Frequency.Constants.cs b/OnixLabs.Units/Frequency.Constants.cs new file mode 100644 index 0000000..b3706b0 --- /dev/null +++ b/OnixLabs.Units/Frequency.Constants.cs @@ -0,0 +1,46 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Frequency +{ + /// + /// Gets a zero value, equal to zero yoctohertz. + /// + public static readonly Frequency Zero = new(T.Zero); + + private const string YoctoHertzSpecifier = "yHz"; + private const string ZeptoHertzSpecifier = "zHz"; + private const string AttoHertzSpecifier = "aHz"; + private const string FemtoHertzSpecifier = "fHz"; + private const string PicoHertzSpecifier = "pHz"; + private const string NanoHertzSpecifier = "nHz"; + private const string MicroHertzSpecifier = "µHz"; + private const string MilliHertzSpecifier = "mHz"; + private const string HertzSpecifier = "Hz"; + private const string KiloHertzSpecifier = "kHz"; + private const string MegaHertzSpecifier = "MHz"; + private const string GigaHertzSpecifier = "GHz"; + private const string TeraHertzSpecifier = "THz"; + private const string PetaHertzSpecifier = "PHz"; + private const string ExaHertzSpecifier = "EHz"; + private const string ZettaHertzSpecifier = "ZHz"; + private const string YottaHertzSpecifier = "YHz"; + private const string RevolutionsPerMinuteSpecifier = "rpm"; +} diff --git a/OnixLabs.Units/Frequency.Equatable.cs b/OnixLabs.Units/Frequency.Equatable.cs new file mode 100644 index 0000000..b3bbc85 --- /dev/null +++ b/OnixLabs.Units/Frequency.Equatable.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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Frequency +{ + /// + /// Determines whether two instances have the same value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(Frequency left, Frequency right) => Equals(left.YoctoHertz, right.YoctoHertz); + + /// + /// Determines whether the current instance is equal to the specified instance. + /// + /// The other instance to compare with this instance. + /// Returns if the current instance is equal to the specified instance; otherwise, . + public bool Equals(Frequency other) => Equals(this, other); + + /// + /// Determines whether the specified object is equal to the current instance. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Frequency other && Equals(other); + + /// + /// Serves as a hash code function for the type. + /// + /// Returns a hash code for the current instance. + public override int GetHashCode() => YoctoHertz.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(Frequency left, Frequency right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(Frequency left, Frequency right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Frequency.Format.cs b/OnixLabs.Units/Frequency.Format.cs new file mode 100644 index 0000000..6444216 --- /dev/null +++ b/OnixLabs.Units/Frequency.Format.cs @@ -0,0 +1,32 @@ +// 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 Frequency +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Frequency.From.cs b/OnixLabs.Units/Frequency.From.cs new file mode 100644 index 0000000..b2f7d5d --- /dev/null +++ b/OnixLabs.Units/Frequency.From.cs @@ -0,0 +1,144 @@ +// 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 Frequency +{ + /// + /// Creates a new instance from the specified yoctohertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromYoctoHertz(T value) => new(value); + + /// + /// Creates a new instance from the specified zeptohertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromZeptoHertz(T value) => new(value * T.CreateChecked(1e3)); + + /// + /// Creates a new instance from the specified attohertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromAttoHertz(T value) => new(value * T.CreateChecked(1e6)); + + /// + /// Creates a new instance from the specified femtohertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromFemtoHertz(T value) => new(value * T.CreateChecked(1e9)); + + /// + /// Creates a new instance from the specified picohertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromPicoHertz(T value) => new(value * T.CreateChecked(1e12)); + + /// + /// Creates a new instance from the specified nanohertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromNanoHertz(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from the specified microhertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromMicroHertz(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from the specified millihertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromMilliHertz(T value) => new(value * T.CreateChecked(1e21)); + + /// + /// Creates a new instance from the specified hertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromHertz(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from the specified kilohertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromKiloHertz(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from the specified megahertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromMegaHertz(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified gigahertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromGigaHertz(T value) => new(value * T.CreateChecked(1e33)); + + /// + /// Creates a new instance from the specified terahertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromTeraHertz(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from the specified petahertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromPetaHertz(T value) => new(value * T.CreateChecked(1e39)); + + /// + /// Creates a new instance from the specified exahertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromExaHertz(T value) => new(value * T.CreateChecked(1e42)); + + /// + /// Creates a new instance from the specified zettahertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromZettaHertz(T value) => new(value * T.CreateChecked(1e45)); + + /// + /// Creates a new instance from the specified yottahertz value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Frequency FromYottaHertz(T value) => new(value * T.CreateChecked(1e48)); + + /// + /// Creates a new instance from the specified revolutions per minute value. + /// + /// The value, in revolutions per minute, from which to construct a new instance. + /// Returns a newly created instance from the specified revolutions per minute value. + public static Frequency FromRevolutionsPerMinute(T value) => FromHertz(value / T.CreateChecked(60)); +} diff --git a/OnixLabs.Units/Frequency.To.cs b/OnixLabs.Units/Frequency.To.cs new file mode 100644 index 0000000..7ba7fec --- /dev/null +++ b/OnixLabs.Units/Frequency.To.cs @@ -0,0 +1,76 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Frequency +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(YoctoHertzSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or an empty span to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: YoctoHertzSpecifier); + + T value = specifier switch + { + YoctoHertzSpecifier => YoctoHertz, + ZeptoHertzSpecifier => ZeptoHertz, + AttoHertzSpecifier => AttoHertz, + FemtoHertzSpecifier => FemtoHertz, + PicoHertzSpecifier => PicoHertz, + NanoHertzSpecifier => NanoHertz, + MicroHertzSpecifier => MicroHertz, + MilliHertzSpecifier => MilliHertz, + HertzSpecifier => Hertz, + KiloHertzSpecifier => KiloHertz, + MegaHertzSpecifier => MegaHertz, + GigaHertzSpecifier => GigaHertz, + TeraHertzSpecifier => TeraHertz, + PetaHertzSpecifier => PetaHertz, + ExaHertzSpecifier => ExaHertz, + ZettaHertzSpecifier => ZettaHertz, + YottaHertzSpecifier => YottaHertz, + RevolutionsPerMinuteSpecifier => RevolutionsPerMinute, + _ => throw ArgumentException.InvalidFormat(format, + "yHz, zHz, aHz, fHz, pHz, nHz, µHz, mHz, Hz, kHz, " + + "MHz, GHz, THz, PHz, EHz, ZHz, YHz, and rpm") + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Frequency.cs b/OnixLabs.Units/Frequency.cs new file mode 100644 index 0000000..f8d74a2 --- /dev/null +++ b/OnixLabs.Units/Frequency.cs @@ -0,0 +1,127 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of frequency. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Frequency : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The frequency unit in . + private Frequency(T value) => YoctoHertz = value; + + /// + /// Gets the frequency in yoctohertz (yHz). + /// + public T YoctoHertz { get; } + + /// + /// Gets the frequency in zeptohertz (zHz). + /// + public T ZeptoHertz => YoctoHertz / T.CreateChecked(1e3); + + /// + /// Gets the frequency in attohertz (aHz). + /// + public T AttoHertz => YoctoHertz / T.CreateChecked(1e6); + + /// + /// Gets the frequency in femtohertz (fHz). + /// + public T FemtoHertz => YoctoHertz / T.CreateChecked(1e9); + + /// + /// Gets the frequency in picohertz (pHz). + /// + public T PicoHertz => YoctoHertz / T.CreateChecked(1e12); + + /// + /// Gets the frequency in nanohertz (nHz). + /// + public T NanoHertz => YoctoHertz / T.CreateChecked(1e15); + + /// + /// Gets the frequency in microhertz (µHz). + /// + public T MicroHertz => YoctoHertz / T.CreateChecked(1e18); + + /// + /// Gets the frequency in millihertz (mHz). + /// + public T MilliHertz => YoctoHertz / T.CreateChecked(1e21); + + /// + /// Gets the frequency in hertz (Hz). + /// + public T Hertz => YoctoHertz / T.CreateChecked(1e24); + + /// + /// Gets the frequency in kilohertz (kHz). + /// + public T KiloHertz => YoctoHertz / T.CreateChecked(1e27); + + /// + /// Gets the frequency in megahertz (MHz). + /// + public T MegaHertz => YoctoHertz / T.CreateChecked(1e30); + + /// + /// Gets the frequency in gigahertz (GHz). + /// + public T GigaHertz => YoctoHertz / T.CreateChecked(1e33); + + /// + /// Gets the frequency in terahertz (THz). + /// + public T TeraHertz => YoctoHertz / T.CreateChecked(1e36); + + /// + /// Gets the frequency in petahertz (PHz). + /// + public T PetaHertz => YoctoHertz / T.CreateChecked(1e39); + + /// + /// Gets the frequency in exahertz (EHz). + /// + public T ExaHertz => YoctoHertz / T.CreateChecked(1e42); + + /// + /// Gets the frequency in zettahertz (ZHz). + /// + public T ZettaHertz => YoctoHertz / T.CreateChecked(1e45); + + /// + /// Gets the frequency in yottahertz (YHz). + /// + public T YottaHertz => YoctoHertz / T.CreateChecked(1e48); + + /// + /// Gets the frequency in revolutions per minute (rpm). + /// + public T RevolutionsPerMinute => Hertz * T.CreateChecked(60); +} From 9eee0265d430e4a5208380208e13febf7cc13d61 Mon Sep 17 00:00:00 2001 From: matthew Date: Thu, 20 Nov 2025 16:20:53 +0000 Subject: [PATCH 22/23] Added Energy unit and unit tests. --- OnixLabs.Units.UnitTests/EnergyTests.cs | 381 ++++++++++++++++++ OnixLabs.Units/Energy.Arithmetic.Addition.cs | 47 +++ OnixLabs.Units/Energy.Arithmetic.Division.cs | 47 +++ .../Energy.Arithmetic.Multiplication.cs | 47 +++ .../Energy.Arithmetic.Subtraction.cs | 47 +++ OnixLabs.Units/Energy.Comparable.cs | 100 +++++ OnixLabs.Units/Energy.Constants.cs | 59 +++ OnixLabs.Units/Energy.Equatable.cs | 77 ++++ OnixLabs.Units/Energy.Format.cs | 32 ++ OnixLabs.Units/Energy.From.cs | 113 ++++++ OnixLabs.Units/Energy.To.cs | 74 ++++ OnixLabs.Units/Energy.cs | 102 +++++ 12 files changed, 1126 insertions(+) create mode 100644 OnixLabs.Units.UnitTests/EnergyTests.cs create mode 100644 OnixLabs.Units/Energy.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Energy.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Energy.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Energy.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Energy.Comparable.cs create mode 100644 OnixLabs.Units/Energy.Constants.cs create mode 100644 OnixLabs.Units/Energy.Equatable.cs create mode 100644 OnixLabs.Units/Energy.Format.cs create mode 100644 OnixLabs.Units/Energy.From.cs create mode 100644 OnixLabs.Units/Energy.To.cs create mode 100644 OnixLabs.Units/Energy.cs diff --git a/OnixLabs.Units.UnitTests/EnergyTests.cs b/OnixLabs.Units.UnitTests/EnergyTests.cs new file mode 100644 index 0000000..f232a6c --- /dev/null +++ b/OnixLabs.Units.UnitTests/EnergyTests.cs @@ -0,0 +1,381 @@ +// 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; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class EnergyTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+42; + + [Fact(DisplayName = "Energy.Zero should produce the expected result")] + public void EnergyZeroShouldProduceExpectedResult() + { + // Given / When + Energy energy = Energy.Zero; + + // Then + Assert.Equal(0.0, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromYoctoJoules should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void EnergyFromYoctoJoulesShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromYoctoJoules(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromJoules should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void EnergyFromJoulesShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromJoules(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromKiloJoules should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void EnergyFromKiloJoulesShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromKiloJoules(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromMegaJoules should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void EnergyFromMegaJoulesShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromMegaJoules(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromGigaJoules should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e33)] + [InlineData(2.5, 2.5e33)] + public void EnergyFromGigaJoulesShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromGigaJoules(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromCalories should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.184e24)] + [InlineData(2.5, 1.046e25)] + public void EnergyFromCaloriesShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromCalories(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromKiloCalories should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 4.184e27)] + [InlineData(2.5, 1.046e28)] + public void EnergyFromKiloCaloriesShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromKiloCalories(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromWattHours should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.6e27)] + [InlineData(2.5, 9.0e27)] + public void EnergyFromWattHoursShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromWattHours(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromKiloWattHours should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 3.6e30)] + [InlineData(2.5, 9.0e30)] + public void EnergyFromKiloWattHoursShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromKiloWattHours(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromErgs should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e17)] + [InlineData(2.5, 2.5e17)] + public void EnergyFromErgsShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromErgs(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromBritishThermalUnits should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.05505585262e27)] + [InlineData(2.5, 2.63763963155e27)] + public void EnergyFromBritishThermalUnitsShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromBritishThermalUnits(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromFootPounds should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.3558179483314e24)] + [InlineData(2.5, 3.3895448708285e24)] + public void EnergyFromFootPoundsShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromFootPounds(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Theory(DisplayName = "Energy.FromElectronVolts should produce the expected YoctoJoules")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.602176634e5)] + [InlineData(2.5, 4.005441584999999e5)] + public void EnergyFromElectronVoltsShouldProduceExpectedYoctoJoules(double value, double expectedYoctoJoules) + { + // Given / When + Energy energy = Energy.FromElectronVolts(value); + + // Then + Assert.Equal(expectedYoctoJoules, energy.YoctoJoules, Tolerance); + } + + [Fact(DisplayName = "Energy.Add should produce the expected result")] + public void EnergyAddShouldProduceExpectedValue() + { + // Given + Energy left = Energy.FromJoules(1500.0); + Energy right = Energy.FromJoules(500.0); + + // When + Energy result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.Joules, Tolerance); + } + + [Fact(DisplayName = "Energy.Subtract should produce the expected result")] + public void EnergySubtractShouldProduceExpectedValue() + { + // Given + Energy left = Energy.FromJoules(1500.0); + Energy right = Energy.FromJoules(400.0); + + // When + Energy result = left.Subtract(right); + + // Then + Assert.Equal(1100.0, result.Joules, Tolerance); + } + + [Fact(DisplayName = "Energy.Multiply should produce the expected result")] + public void EnergyMultiplyShouldProduceExpectedValue() + { + // Given + Energy left = Energy.FromJoules(10.0); // 1e25 yJ + Energy right = Energy.FromJoules(3.0); // 3e24 yJ + + // When + Energy result = left.Multiply(right); // 1e25 * 3e24 = 3e49 yJ + + // Then + Assert.Equal(1e25, left.YoctoJoules, Tolerance); + Assert.Equal(3e24, right.YoctoJoules, Tolerance); + Assert.Equal(3e49, result.YoctoJoules, Tolerance); + Assert.Equal(3e25, result.Joules, Tolerance); + } + + [Fact(DisplayName = "Energy.Divide should produce the expected result")] + public void EnergyDivideShouldProduceExpectedValue() + { + // Given + Energy left = Energy.FromJoules(100.0); // 1e26 yJ + Energy right = Energy.FromJoules(20.0); // 2e25 yJ + + // When + Energy result = left.Divide(right); // 1e26 / 2e25 = 5 yJ + + // Then + Assert.Equal(5.0, result.YoctoJoules, Tolerance); + Assert.Equal(5e-24, result.Joules, Tolerance); + } + + [Fact(DisplayName = "Energy equality should produce the expected result (left equal to right)")] + public void EnergyEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Energy left = Energy.FromKiloJoules(2.0); + Energy right = Energy.FromJoules(2000.0); + + // When / Then + Assert.True(Energy.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Energy equality should produce the expected result (left not equal to right)")] + public void EnergyEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Energy left = Energy.FromKiloJoules(2.0); + Energy right = Energy.FromJoules(2500.0); + + // When / Then + Assert.False(Energy.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Energy comparison should produce the expected result (left equal to right)")] + public void EnergyComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Energy left = Energy.FromJoules(1234.0); + Energy right = Energy.FromJoules(1234.0); + + // When / Then + Assert.Equal(0, Energy.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 = "Energy comparison should produce the expected result (left greater than right)")] + public void EnergyComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Energy left = Energy.FromJoules(4567.0); + Energy right = Energy.FromJoules(1234.0); + + // When / Then + Assert.Equal(1, Energy.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 = "Energy comparison should produce the expected result (left less than right)")] + public void EnergyComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Energy left = Energy.FromJoules(1234.0); + Energy right = Energy.FromJoules(4567.0); + + // When / Then + Assert.Equal(-1, Energy.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 = "Energy.ToString should produce the expected result")] + public void EnergyToStringShouldProduceExpectedResult() + { + // Given + // Use a value that renders nicely across several specifiers. + Energy energy = Energy.FromJoules(1000.0); + + // When / Then + Assert.Equal("1,000.000 J", $"{energy:J3}"); + Assert.Equal("1.000 kJ", $"{energy:kJ3}"); + Assert.Equal("0.001 MJ", $"{energy:MJ3}"); + Assert.Equal("0.000 GJ", $"{energy:GJ3}"); + Assert.Equal("239.006 cal", $"{energy:cal3}"); + Assert.Equal("0.239 kcal", $"{energy:kcal3}"); + Assert.Equal("0.278 Wh", $"{energy:Wh3}"); + Assert.Equal("0.000 kWh", $"{energy:kWh3}"); + Assert.Equal("10,000,000,000.000 erg", $"{energy:erg3}"); + Assert.Equal("0.948 BTU", $"{energy:BTU3}"); + Assert.Equal("737.562 ft·lbf", $"{energy:ftlb3}"); + Assert.Equal("6,241,509,074,460,762,701,824.000 eV", $"{energy:eV3}"); + } + + [Fact(DisplayName = "Energy.ToString should honor custom culture separators")] + public void EnergyToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Energy energy = Energy.FromJoules(1234.56); + + // When + string formatted = energy.ToString("J2", customCulture); + + // Then + // German uses '.' for thousands and ',' for decimals. + Assert.Equal("1.234,56 J", formatted); + } +} diff --git a/OnixLabs.Units/Energy.Arithmetic.Addition.cs b/OnixLabs.Units/Energy.Arithmetic.Addition.cs new file mode 100644 index 0000000..f600da9 --- /dev/null +++ b/OnixLabs.Units/Energy.Arithmetic.Addition.cs @@ -0,0 +1,47 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Energy +{ + /// + /// 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 Energy Add(Energy left, Energy right) => new(left.YoctoJoules + right.YoctoJoules); + + /// + /// 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 Energy operator +(Energy left, Energy right) => Add(left, right); + + /// + /// 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 Energy Add(Energy other) => Add(this, other); +} diff --git a/OnixLabs.Units/Energy.Arithmetic.Division.cs b/OnixLabs.Units/Energy.Arithmetic.Division.cs new file mode 100644 index 0000000..b3b174c --- /dev/null +++ b/OnixLabs.Units/Energy.Arithmetic.Division.cs @@ -0,0 +1,47 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Energy +{ + /// + /// 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 Energy Divide(Energy left, Energy right) => new(left.YoctoJoules / right.YoctoJoules); + + /// + /// 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 Energy operator /(Energy left, Energy right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// + /// Returns the quotient of the current value, divided by the specified other value. + /// + public Energy Divide(Energy other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Energy.Arithmetic.Multiplication.cs b/OnixLabs.Units/Energy.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..006cc5d --- /dev/null +++ b/OnixLabs.Units/Energy.Arithmetic.Multiplication.cs @@ -0,0 +1,47 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Energy +{ + /// + /// 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 Energy Multiply(Energy left, Energy right) => new(left.YoctoJoules * right.YoctoJoules); + + /// + /// 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 Energy operator *(Energy left, Energy right) => Multiply(left, right); + + /// + /// Computes the product of the current value multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// + /// Returns the product of the current value multiplied by the specified other value. + /// + public Energy Multiply(Energy other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Energy.Arithmetic.Subtraction.cs b/OnixLabs.Units/Energy.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..57e58ab --- /dev/null +++ b/OnixLabs.Units/Energy.Arithmetic.Subtraction.cs @@ -0,0 +1,47 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Energy +{ + /// + /// 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. + public static Energy Subtract(Energy left, Energy right) => new(left.YoctoJoules - right.YoctoJoules); + + /// + /// 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. + public static Energy operator -(Energy left, Energy right) => Subtract(left, right); + + /// + /// Computes the difference between the current value and the specified other value. + /// + /// The value to subtract from the current value. + /// + /// Returns the difference between the current value and the specified other value. + /// + public Energy Subtract(Energy other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Energy.Comparable.cs b/OnixLabs.Units/Energy.Comparable.cs new file mode 100644 index 0000000..9149dd1 --- /dev/null +++ b/OnixLabs.Units/Energy.Comparable.cs @@ -0,0 +1,100 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Energy +{ +/// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// An object to compare with this instance. + /// + /// A value that indicates the relative order of the objects being compared. + /// + /// + /// Thrown if the specified is not an instance of . + /// + public int CompareTo(object? obj) + { + if (obj is null) return 1; + if (obj is Energy other) return CompareTo(other); + throw new ArgumentException("Object must be of type Energy.", nameof(obj)); + } + + /// + /// Compares the current instance with another instance and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the other instance. + /// + /// An instance to compare with this instance. + /// + /// A value that indicates the relative order of the instances being compared. + /// + public int CompareTo(Energy other) => YoctoJoules.CompareTo(other.YoctoJoules); + + /// + /// Compares two instances and returns a value that indicates their relative order. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// A signed integer that indicates the relative values of and . + /// + public static int Compare(Energy left, Energy right) => left.CompareTo(right); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the left-hand operand is greater than the right-hand operand; otherwise, . + /// + public static bool operator >(Energy left, Energy right) => Compare(left, right) > 0; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + /// + public static bool operator >=(Energy left, Energy right) => Compare(left, right) >= 0; + + /// + /// Determines whether the left-hand value is less than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + /// + public static bool operator <(Energy left, Energy right) => Compare(left, right) < 0; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + /// + public static bool operator <=(Energy left, Energy right) => Compare(left, right) <= 0; +} diff --git a/OnixLabs.Units/Energy.Constants.cs b/OnixLabs.Units/Energy.Constants.cs new file mode 100644 index 0000000..07aa28f --- /dev/null +++ b/OnixLabs.Units/Energy.Constants.cs @@ -0,0 +1,59 @@ +// 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 Energy +{ + /// + /// Represents an empty value. + /// + public static readonly Energy Zero = new(T.Zero); + + private const string JoulesSpecifier = "J"; + private const string JoulesSymbol = "J"; + + private const string KiloJoulesSpecifier = "KJ"; + private const string KiloJoulesSymbol = "kJ"; + + private const string MegaJoulesSpecifier = "MJ"; + private const string MegaJoulesSymbol = "MJ"; + + private const string GigaJoulesSpecifier = "GJ"; + private const string GigaJoulesSymbol = "GJ"; + + private const string CaloriesSpecifier = "CAL"; + private const string CaloriesSymbol = "cal"; + + private const string KiloCaloriesSpecifier = "KCAL"; + private const string KiloCaloriesSymbol = "kcal"; + + private const string WattHoursSpecifier = "WH"; + private const string WattHoursSymbol = "Wh"; + + private const string KiloWattHoursSpecifier = "KWH"; + private const string KiloWattHoursSymbol = "kWh"; + + private const string ErgsSpecifier = "ERG"; + private const string ErgsSymbol = "erg"; + + private const string BritishThermalUnitsSpecifier = "BTU"; + private const string BritishThermalUnitsSymbol = "BTU"; + + private const string FootPoundsSpecifier = "FTLB"; + private const string FootPoundsSymbol = "ft·lbf"; + + private const string ElectronVoltsSpecifier = "EV"; + private const string ElectronVoltsSymbol = "eV"; +} diff --git a/OnixLabs.Units/Energy.Equatable.cs b/OnixLabs.Units/Energy.Equatable.cs new file mode 100644 index 0000000..e805cd7 --- /dev/null +++ b/OnixLabs.Units/Energy.Equatable.cs @@ -0,0 +1,77 @@ +// 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; +using System.Numerics; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Energy +{ +/// + /// Determines whether the current instance is equal to the specified instance. + /// + /// The instance to compare with the current instance. + /// + /// Returns if the current instance is equal to the specified instance; otherwise, . + /// + public bool Equals(Energy other) => YoctoJoules.Equals(other.YoctoJoules); + + /// + /// Determines whether the specified object is equal to the current instance. + /// + /// The object to compare with the current instance. + /// + /// Returns if the specified object is equal to the current instance; otherwise, . + /// + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Energy other && Equals(other); + + /// + /// Returns a hash code for the current instance. + /// + /// Returns a hash code for the current instance. + public override int GetHashCode() => YoctoJoules.GetHashCode(); + + /// + /// Determines whether two specified instances are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the specified instances are equal; otherwise, . + /// + public static bool Equals(Energy left, Energy right) => left.Equals(right); + + /// + /// Determines whether two specified instances are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the specified instances are equal; otherwise, . + /// + public static bool operator ==(Energy left, Energy right) => Equals(left, right); + + /// + /// Determines whether two specified instances are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// + /// Returns if the specified instances are not equal; otherwise, . + /// + public static bool operator !=(Energy left, Energy right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Energy.Format.cs b/OnixLabs.Units/Energy.Format.cs new file mode 100644 index 0000000..14404f8 --- /dev/null +++ b/OnixLabs.Units/Energy.Format.cs @@ -0,0 +1,32 @@ +// 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 Energy +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written. + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format. + /// An optional object that supplies culture-specific formatting information. + /// Returns if the formatting was successful; otherwise, . + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Energy.From.cs b/OnixLabs.Units/Energy.From.cs new file mode 100644 index 0000000..eba9690 --- /dev/null +++ b/OnixLabs.Units/Energy.From.cs @@ -0,0 +1,113 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Energy +{ +/// + /// Creates a new instance from the specified yoctojoule value. + /// + /// The value, in yoctojoules (yJ), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromYoctoJoules(T value) => new(value); + + /// + /// Creates a new instance from the specified joule value. + /// + /// The value, in joules (J), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromJoules(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from the specified kilojoule value. + /// + /// The value, in kilojoules (kJ), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromKiloJoules(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from the specified megajoule value. + /// + /// The value, in megajoules (MJ), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromMegaJoules(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified gigajoule value. + /// + /// The value, in gigajoules (GJ), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromGigaJoules(T value) => new(value * T.CreateChecked(1e33)); + + /// + /// Creates a new instance from the specified thermochemical calorie value. + /// + /// The value, in thermochemical calories (cal), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromCalories(T value) => new(value * T.CreateChecked(4.184e24)); + + /// + /// Creates a new instance from the specified thermochemical kilocalorie value. + /// + /// The value, in thermochemical kilocalories (kcal), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromKiloCalories(T value) => new(value * T.CreateChecked(4.184e27)); + + /// + /// Creates a new instance from the specified watt-hour value. + /// + /// The value, in watt-hours (Wh), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromWattHours(T value) => new(value * T.CreateChecked(3.6e27)); + + /// + /// Creates a new instance from the specified kilowatt-hour value. + /// + /// The value, in kilowatt-hours (kWh), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromKiloWattHours(T value) => new(value * T.CreateChecked(3.6e30)); + + /// + /// Creates a new instance from the specified erg value. + /// + /// The value, in ergs (erg), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromErgs(T value) => new(value * T.CreateChecked(1e17)); + + /// + /// Creates a new instance from the specified British thermal unit value. + /// + /// The value, in British thermal units (BTU, I.T.), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromBritishThermalUnits(T value) => new(value * T.CreateChecked(1.05505585262e27)); + + /// + /// Creates a new instance from the specified foot-pound value. + /// + /// The value, in foot-pounds force (ft⋅lbf), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromFootPounds(T value) => new(value * T.CreateChecked(1.3558179483314e24)); + + /// + /// Creates a new instance from the specified electronvolt value. + /// + /// The value, in electronvolts (eV), from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Energy FromElectronVolts(T value) => new(value * T.CreateChecked(1.602176634e5)); +} diff --git a/OnixLabs.Units/Energy.To.cs b/OnixLabs.Units/Energy.To.cs new file mode 100644 index 0000000..b1819a1 --- /dev/null +++ b/OnixLabs.Units/Energy.To.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. + +using System; +using System.Globalization; +using System.Numerics; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +public readonly partial struct Energy +{ +/// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(JoulesSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => + ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: JoulesSpecifier); + + (T value, string symbol) = specifier.ToUpperInvariant() switch + { + JoulesSpecifier => (Joules, JoulesSymbol), + KiloJoulesSpecifier => (KiloJoules, KiloJoulesSymbol), + MegaJoulesSpecifier => (MegaJoules, MegaJoulesSymbol), + GigaJoulesSpecifier => (GigaJoules, GigaJoulesSymbol), + CaloriesSpecifier => (Calories, CaloriesSymbol), + KiloCaloriesSpecifier => (KiloCalories, KiloCaloriesSymbol), + WattHoursSpecifier => (WattHours, WattHoursSymbol), + KiloWattHoursSpecifier => (KiloWattHours, KiloWattHoursSymbol), + ErgsSpecifier => (Ergs, ErgsSymbol), + BritishThermalUnitsSpecifier => (BritishThermalUnits, BritishThermalUnitsSymbol), + FootPoundsSpecifier => (FootPounds, FootPoundsSymbol), + ElectronVoltsSpecifier => (ElectronVolts, ElectronVoltsSymbol), + _ => throw new ArgumentException( + $"Format '{format.ToString()}' is invalid. Valid format specifiers are: " + + "J, KJ, MJ, GJ, CAL, KCAL, WH, KWH, ERG, BTU, FTLB, EV. " + + "Format specifiers may also be suffixed with a scale value.", + nameof(format)) + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {symbol}"; + } +} diff --git a/OnixLabs.Units/Energy.cs b/OnixLabs.Units/Energy.cs new file mode 100644 index 0000000..b805923 --- /dev/null +++ b/OnixLabs.Units/Energy.cs @@ -0,0 +1,102 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of energy. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Energy : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The energy value in . + private Energy(T value) => YoctoJoules = value; + + /// + /// Gets the energy in yoctojoules (yJ). + /// + public T YoctoJoules { get; } + + /// + /// Gets the energy in joules (J). + /// + public T Joules => YoctoJoules / T.CreateChecked(1e24); + + /// + /// Gets the energy in kilojoules (kJ). + /// + public T KiloJoules => YoctoJoules / T.CreateChecked(1e27); + + /// + /// Gets the energy in megajoules (MJ). + /// + public T MegaJoules => YoctoJoules / T.CreateChecked(1e30); + + /// + /// Gets the energy in gigajoules (GJ). + /// + public T GigaJoules => YoctoJoules / T.CreateChecked(1e33); + + /// + /// Gets the energy in thermochemical calories (cal). + /// + public T Calories => YoctoJoules / T.CreateChecked(4.184e24); + + /// + /// Gets the energy in thermochemical kilocalories (kcal). + /// + public T KiloCalories => YoctoJoules / T.CreateChecked(4.184e27); + + /// + /// Gets the energy in watt-hours (Wh). + /// + public T WattHours => YoctoJoules / T.CreateChecked(3.6e27); + + /// + /// Gets the energy in kilowatt-hours (kWh). + /// + public T KiloWattHours => YoctoJoules / T.CreateChecked(3.6e30); + + /// + /// Gets the energy in ergs (erg). + /// + public T Ergs => YoctoJoules / T.CreateChecked(1e17); + + /// + /// Gets the energy in British thermal units (BTU, I.T.). + /// + public T BritishThermalUnits => YoctoJoules / T.CreateChecked(1.05505585262e27); + + /// + /// Gets the energy in foot-pounds force (ft⋅lbf). + /// + public T FootPounds => YoctoJoules / T.CreateChecked(1.3558179483314e24); + + /// + /// Gets the energy in electronvolts (eV). + /// + public T ElectronVolts => YoctoJoules / T.CreateChecked(1.602176634e5); +} From 7fb90b52c0ad2b717a95b7954a95de27fd147dbb Mon Sep 17 00:00:00 2001 From: matthew Date: Fri, 21 Nov 2025 11:07:53 +0000 Subject: [PATCH 23/23] Added Power unit and unit tests. --- OnixLabs.Units.UnitTests/PowerTests.cs | 451 ++++++++++++++++++ OnixLabs.Units/Power.Arithmetic.Addition.cs | 42 ++ OnixLabs.Units/Power.Arithmetic.Division.cs | 42 ++ .../Power.Arithmetic.Multiplication.cs | 42 ++ .../Power.Arithmetic.Subtraction.cs | 42 ++ OnixLabs.Units/Power.Comparable.cs | 81 ++++ OnixLabs.Units/Power.Constants.cs | 43 ++ OnixLabs.Units/Power.Equatable.cs | 64 +++ OnixLabs.Units/Power.Format.cs | 32 ++ OnixLabs.Units/Power.From.cs | 151 ++++++ OnixLabs.Units/Power.To.cs | 78 +++ OnixLabs.Units/Power.cs | 132 +++++ 12 files changed, 1200 insertions(+) create mode 100644 OnixLabs.Units.UnitTests/PowerTests.cs create mode 100644 OnixLabs.Units/Power.Arithmetic.Addition.cs create mode 100644 OnixLabs.Units/Power.Arithmetic.Division.cs create mode 100644 OnixLabs.Units/Power.Arithmetic.Multiplication.cs create mode 100644 OnixLabs.Units/Power.Arithmetic.Subtraction.cs create mode 100644 OnixLabs.Units/Power.Comparable.cs create mode 100644 OnixLabs.Units/Power.Constants.cs create mode 100644 OnixLabs.Units/Power.Equatable.cs create mode 100644 OnixLabs.Units/Power.Format.cs create mode 100644 OnixLabs.Units/Power.From.cs create mode 100644 OnixLabs.Units/Power.To.cs create mode 100644 OnixLabs.Units/Power.cs diff --git a/OnixLabs.Units.UnitTests/PowerTests.cs b/OnixLabs.Units.UnitTests/PowerTests.cs new file mode 100644 index 0000000..bc25820 --- /dev/null +++ b/OnixLabs.Units.UnitTests/PowerTests.cs @@ -0,0 +1,451 @@ +// 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; +using OnixLabs.Numerics; + +namespace OnixLabs.Units.UnitTests; + +public sealed class PowerTests +{ + // IEEE-754 binary floating-point arithmetic causes small discrepancies in calculation, therefore we need a tolerance. + private const double Tolerance = 1e+42; + + [Fact(DisplayName = "Power.Zero should produce the expected result")] + public void PowerZeroShouldProduceExpectedResult() + { + // Given / When + Power power = Power.Zero; + + // Then + Assert.Equal(0.0, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromYoctoWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1.0)] + [InlineData(2.5, 2.5)] + public void PowerFromYoctoWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromYoctoWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromZeptoWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e3)] + [InlineData(2.5, 2.5e3)] + public void PowerFromZeptoWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromZeptoWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromAttoWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e6)] + [InlineData(2.5, 2.5e6)] + public void PowerFromAttoWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromAttoWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromFemtoWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e9)] + [InlineData(2.5, 2.5e9)] + public void PowerFromFemtoWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromFemtoWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromPicoWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e12)] + [InlineData(2.5, 2.5e12)] + public void PowerFromPicoWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromPicoWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromNanoWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e15)] + [InlineData(2.5, 2.5e15)] + public void PowerFromNanoWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromNanoWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromMicroWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e18)] + [InlineData(2.5, 2.5e18)] + public void PowerFromMicroWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromMicroWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromMilliWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e21)] + [InlineData(2.5, 2.5e21)] + public void PowerFromMilliWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromMilliWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e24)] + [InlineData(2.5, 2.5e24)] + public void PowerFromWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromKiloWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e27)] + [InlineData(2.5, 2.5e27)] + public void PowerFromKiloWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromKiloWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromMegaWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e30)] + [InlineData(2.5, 2.5e30)] + public void PowerFromMegaWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromMegaWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromGigaWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e33)] + [InlineData(2.5, 2.5e33)] + public void PowerFromGigaWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromGigaWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromTeraWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e36)] + [InlineData(2.5, 2.5e36)] + public void PowerFromTeraWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromTeraWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromPetaWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e39)] + [InlineData(2.5, 2.5e39)] + public void PowerFromPetaWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromPetaWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromExaWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e42)] + [InlineData(2.5, 2.5e42)] + public void PowerFromExaWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromExaWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromZettaWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e45)] + [InlineData(2.5, 2.5e45)] + public void PowerFromZettaWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromZettaWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromYottaWatts should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 1e48)] + [InlineData(2.5, 2.5e48)] + public void PowerFromYottaWattsShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromYottaWatts(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromHorsepower should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 7.456998715822702e26)] + [InlineData(2.5, 1.8642496789556757e27)] + public void PowerFromHorsepowerShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromHorsepower(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Theory(DisplayName = "Power.FromMetricHorsepower should produce the expected YoctoWatts")] + [InlineData(0.0, 0.0)] + [InlineData(1.0, 7.3549875e26)] + [InlineData(2.5, 1.838746875e27)] + public void PowerFromMetricHorsepowerShouldProduceExpectedYoctoWatts(double value, double expectedYoctoWatts) + { + // Given / When + Power power = Power.FromMetricHorsepower(value); + + // Then + Assert.Equal(expectedYoctoWatts, power.YoctoWatts, Tolerance); + } + + [Fact(DisplayName = "Power.Add should produce the expected result")] + public void PowerAddShouldProduceExpectedValue() + { + // Given + Power left = Power.FromWatts(1500.0); + Power right = Power.FromWatts(500.0); + + // When + Power result = left.Add(right); + + // Then + Assert.Equal(2000.0, result.Watts, Tolerance); + } + + [Fact(DisplayName = "Power.Subtract should produce the expected result")] + public void PowerSubtractShouldProduceExpectedValue() + { + // Given + Power left = Power.FromWatts(1500.0); + Power right = Power.FromWatts(500.0); + + // When + Power result = left.Subtract(right); + + // Then + Assert.Equal(1000.0, result.Watts, Tolerance); + } + + [Fact(DisplayName = "Power.Multiply should produce the expected result")] + public void PowerMultiplyShouldProduceExpectedValue() + { + // Given + Power left = Power.FromWatts(10.0); // 1e25 yW + Power right = Power.FromWatts(3.0); // 3e24 yW + + // When + Power result = left.Multiply(right); // 1e25 * 3e24 = 3e49 yW + + // Then + Assert.Equal(1e25, left.YoctoWatts, Tolerance); + Assert.Equal(3e24, right.YoctoWatts, Tolerance); + Assert.Equal(3e49, result.YoctoWatts, Tolerance); + Assert.Equal(3e25, result.Watts, Tolerance); + } + + [Fact(DisplayName = "Power.Divide should produce the expected result")] + public void PowerDivideShouldProduceExpectedValue() + { + // Given + Power left = Power.FromWatts(100.0); // 1e26 yW + Power right = Power.FromWatts(20.0); // 2e25 yW + + // When + Power result = left.Divide(right); // 1e26 / 2e25 = 5 yW + + // Then + Assert.Equal(5.0, result.YoctoWatts, Tolerance); + Assert.Equal(5e-24, result.Watts, Tolerance); + } + + [Fact(DisplayName = "Power comparison should produce the expected result (left equal to right)")] + public void PowerComparisonShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Power left = Power.FromWatts(1234.0); + Power right = Power.FromWatts(1234.0); + + // When / Then + Assert.Equal(0, Power.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 = "Power comparison should produce the expected result (left greater than right)")] + public void PowerComparisonShouldProduceExpectedLeftGreaterThanRight() + { + // Given + Power left = Power.FromWatts(4567.0); + Power right = Power.FromWatts(1234.0); + + // When / Then + Assert.Equal(1, Power.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 = "Power comparison should produce the expected result (left less than right)")] + public void PowerComparisonShouldProduceExpectedLeftLessThanRight() + { + // Given + Power left = Power.FromWatts(1234.0); + Power right = Power.FromWatts(4567.0); + + // When / Then + Assert.Equal(-1, Power.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 = "Power equality should produce the expected result (left equal to right)")] + public void PowerEqualityShouldProduceExpectedResultLeftEqualToRight() + { + // Given + Power left = Power.FromKiloWatts(2.0); + Power right = Power.FromWatts(2000.0); + + // When / Then + Assert.True(Power.Equals(left, right)); + Assert.True(left.Equals(right)); + Assert.True(left.Equals((object)right)); + Assert.True(left == right); + Assert.False(left != right); + } + + [Fact(DisplayName = "Power equality should produce the expected result (left not equal to right)")] + public void PowerEqualityShouldProduceExpectedResultLeftNotEqualToRight() + { + // Given + Power left = Power.FromKiloWatts(2.0); + Power right = Power.FromWatts(2500.0); + + // When / Then + Assert.False(Power.Equals(left, right)); + Assert.False(left.Equals(right)); + Assert.False(left.Equals((object)right)); + Assert.False(left == right); + Assert.True(left != right); + } + + [Fact(DisplayName = "Power.ToString should produce the expected result")] + public void PowerToStringShouldProduceExpectedResult() + { + // Given + Power power = Power.FromWatts(1000.0); + + // When / Then + Assert.Equal("1,000.000 W", $"{power:W3}"); + Assert.Equal("1.000 kW", $"{power:kW3}"); + Assert.Equal("0.001 MW", $"{power:MW3}"); + Assert.Equal("1.341 hp", $"{power:hp3}"); + Assert.Equal("1.360 hpM", $"{power:hpM3}"); + } + + [Fact(DisplayName = "Power.ToString should honor custom culture separators")] + public void PowerToStringShouldHonorCustomCulture() + { + // Given + CultureInfo customCulture = new("de-DE"); + Power power = Power.FromWatts(1234.56); + + // When + string formatted = power.ToString("W2", customCulture); + + // Then + // German uses '.' for thousands and ',' for decimals. + Assert.Equal("1.234,56 W", formatted); + } +} diff --git a/OnixLabs.Units/Power.Arithmetic.Addition.cs b/OnixLabs.Units/Power.Arithmetic.Addition.cs new file mode 100644 index 0000000..1069b33 --- /dev/null +++ b/OnixLabs.Units/Power.Arithmetic.Addition.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Power +{ + /// + /// 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 Power Add(Power left, Power right) => new(left.YoctoWatts + right.YoctoWatts); + + /// + /// 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 Power operator +(Power left, Power right) => Add(left, right); + + /// + /// 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 Power Add(Power other) => Add(this, other); +} diff --git a/OnixLabs.Units/Power.Arithmetic.Division.cs b/OnixLabs.Units/Power.Arithmetic.Division.cs new file mode 100644 index 0000000..9fa9cd0 --- /dev/null +++ b/OnixLabs.Units/Power.Arithmetic.Division.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Power +{ + /// + /// 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 Power Divide(Power left, Power right) => new(left.YoctoWatts / right.YoctoWatts); + + /// + /// 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 Power operator /(Power left, Power right) => Divide(left, right); + + /// + /// Computes the quotient of the current value, divided by the specified other value. + /// + /// The value to divide the current value by. + /// Returns the quotient of the current value, divided by the specified other value. + public Power Divide(Power other) => Divide(this, other); +} diff --git a/OnixLabs.Units/Power.Arithmetic.Multiplication.cs b/OnixLabs.Units/Power.Arithmetic.Multiplication.cs new file mode 100644 index 0000000..8da22ad --- /dev/null +++ b/OnixLabs.Units/Power.Arithmetic.Multiplication.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Power +{ + /// + /// 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 Power Multiply(Power left, Power right) => new(left.YoctoWatts * right.YoctoWatts); + + /// + /// 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 Power operator *(Power left, Power right) => Multiply(left, right); + + /// + /// Computes the product of the current value, multiplied by the specified other value. + /// + /// The value to multiply the current value by. + /// Returns the product of the current value, multiplied by the specified other value. + public Power Multiply(Power other) => Multiply(this, other); +} diff --git a/OnixLabs.Units/Power.Arithmetic.Subtraction.cs b/OnixLabs.Units/Power.Arithmetic.Subtraction.cs new file mode 100644 index 0000000..1a4d28e --- /dev/null +++ b/OnixLabs.Units/Power.Arithmetic.Subtraction.cs @@ -0,0 +1,42 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Power +{ + /// + /// 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 Power Subtract(Power left, Power right) => new(left.YoctoWatts - right.YoctoWatts); + + /// + /// 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 Power operator -(Power left, Power right) => Subtract(left, right); + + /// + /// Computes the difference of the specified other value, subtracted from the current value. + /// + /// The value to subtract from the current value. + /// Returns the difference of the specified other value, subtracted from the current value. + public Power Subtract(Power other) => Subtract(this, other); +} diff --git a/OnixLabs.Units/Power.Comparable.cs b/OnixLabs.Units/Power.Comparable.cs new file mode 100644 index 0000000..80d2e70 --- /dev/null +++ b/OnixLabs.Units/Power.Comparable.cs @@ -0,0 +1,81 @@ +// 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; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Power +{ + /// + /// Compares two 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 a value that indicates the relative order of the objects being compared. + public static int Compare(Power left, Power right) => left.YoctoWatts.CompareTo(right.YoctoWatts); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + public int CompareTo(Power other) => Compare(this, other); + + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates + /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the + /// other object. + /// + /// An object to compare with this instance. + /// Returns a value that indicates the relative order of the objects being compared. + // ReSharper disable once HeapView.BoxingAllocation + public int CompareTo(object? obj) => this.CompareToObject(obj); + + /// + /// Determines whether the left-hand value is greater than the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than right-hand operand; otherwise, . + public static bool operator >(Power left, Power right) => Compare(left, right) is 1; + + /// + /// Determines whether the left-hand value is greater than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is greater than or equal to the right-hand operand; otherwise, . + public static bool operator >=(Power left, Power right) => Compare(left, right) is 1 or 0; + + /// + /// Determines whether the left-hand value is less than right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than the right-hand operand; otherwise, . + public static bool operator <(Power left, Power right) => Compare(left, right) is -1; + + /// + /// Determines whether the left-hand value is less than or equal to the right-hand value. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the left-hand operand is less than or equal to the right-hand operand; otherwise, . + public static bool operator <=(Power left, Power right) => Compare(left, right) is -1 or 0; +} diff --git a/OnixLabs.Units/Power.Constants.cs b/OnixLabs.Units/Power.Constants.cs new file mode 100644 index 0000000..12329b6 --- /dev/null +++ b/OnixLabs.Units/Power.Constants.cs @@ -0,0 +1,43 @@ +// 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 Power +{ + /// + /// Gets a zero value, equal to zero yoctowatts. + /// + public static readonly Power Zero = new(T.Zero); + + private const string YoctoWattsSpecifier = "yW"; + private const string ZeptoWattsSpecifier = "zW"; + private const string AttoWattsSpecifier = "aW"; + private const string FemtoWattsSpecifier = "fW"; + private const string PicoWattsSpecifier = "pW"; + private const string NanoWattsSpecifier = "nW"; + private const string MicroWattsSpecifier = "uW"; + private const string MilliWattsSpecifier = "mW"; + private const string WattsSpecifier = "W"; + private const string KiloWattsSpecifier = "kW"; + private const string MegaWattsSpecifier = "MW"; + private const string GigaWattsSpecifier = "GW"; + private const string TeraWattsSpecifier = "TW"; + private const string PetaWattsSpecifier = "PW"; + private const string ExaWattsSpecifier = "EW"; + private const string ZettaWattsSpecifier = "ZW"; + private const string YottaWattsSpecifier = "YW"; + private const string HorsepowerSpecifier = "hp"; + private const string MetricHorsepowerSpecifier = "hpM"; +} diff --git a/OnixLabs.Units/Power.Equatable.cs b/OnixLabs.Units/Power.Equatable.cs new file mode 100644 index 0000000..bc63027 --- /dev/null +++ b/OnixLabs.Units/Power.Equatable.cs @@ -0,0 +1,64 @@ +// 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.Diagnostics.CodeAnalysis; + +namespace OnixLabs.Units; + +public readonly partial struct Power +{ + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool Equals(Power left, Power right) => Equals(left.YoctoWatts, right.YoctoWatts); + + /// + /// Compares the current instance of with the specified other instance of . + /// + /// The other instance of to compare with the current instance. + /// Returns if the current instance is equal to the specified other instance; otherwise, . + public bool Equals(Power other) => Equals(this, other); + + /// + /// Checks for equality between this instance and another object. + /// + /// The object to check for equality. + /// Returns if the object is equal to this instance; otherwise, . + public override bool Equals([NotNullWhen(true)] object? obj) => obj is Power other && Equals(other); + + /// + /// Serves as a hash code function for this instance. + /// + /// Returns a hash code for this instance. + public override int GetHashCode() => YoctoWatts.GetHashCode(); + + /// + /// Compares two instances of to determine whether their values are equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are equal; otherwise, . + public static bool operator ==(Power left, Power right) => Equals(left, right); + + /// + /// Compares two instances of to determine whether their values are not equal. + /// + /// The left-hand value to compare. + /// The right-hand value to compare. + /// Returns if the two specified instances are not equal; otherwise, . + public static bool operator !=(Power left, Power right) => !Equals(left, right); +} diff --git a/OnixLabs.Units/Power.Format.cs b/OnixLabs.Units/Power.Format.cs new file mode 100644 index 0000000..7228720 --- /dev/null +++ b/OnixLabs.Units/Power.Format.cs @@ -0,0 +1,32 @@ +// 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 Power +{ + /// + /// Tries to format the value of the current instance into the provided span of characters. + /// + /// The span in which to write this instance's value formatted as a span of characters. + /// When this method returns, contains the number of characters that were written in . + /// A span containing the characters that represent a standard or custom format string that defines the acceptable format for . + /// An optional object that supplies culture-specific formatting information for . + /// Returns if the formatting was successful; otherwise, . + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => + ToString(format, provider).TryCopyTo(destination, out charsWritten); +} diff --git a/OnixLabs.Units/Power.From.cs b/OnixLabs.Units/Power.From.cs new file mode 100644 index 0000000..481d08e --- /dev/null +++ b/OnixLabs.Units/Power.From.cs @@ -0,0 +1,151 @@ +// 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 Power +{ + /// + /// Creates a new instance from the specified yoctowatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromYoctoWatts(T value) => new(value); + + /// + /// Creates a new instance from the specified zeptowatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromZeptoWatts(T value) => new(value * T.CreateChecked(1e3)); + + /// + /// Creates a new instance from the specified attowatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromAttoWatts(T value) => new(value * T.CreateChecked(1e6)); + + /// + /// Creates a new instance from the specified femtowatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromFemtoWatts(T value) => new(value * T.CreateChecked(1e9)); + + /// + /// Creates a new instance from the specified picowatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromPicoWatts(T value) => new(value * T.CreateChecked(1e12)); + + /// + /// Creates a new instance from the specified nanowatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromNanoWatts(T value) => new(value * T.CreateChecked(1e15)); + + /// + /// Creates a new instance from the specified microwatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromMicroWatts(T value) => new(value * T.CreateChecked(1e18)); + + /// + /// Creates a new instance from the specified milliwatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromMilliWatts(T value) => new(value * T.CreateChecked(1e21)); + + /// + /// Creates a new instance from the specified watts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromWatts(T value) => new(value * T.CreateChecked(1e24)); + + /// + /// Creates a new instance from the specified kilowatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromKiloWatts(T value) => new(value * T.CreateChecked(1e27)); + + /// + /// Creates a new instance from the specified megawatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromMegaWatts(T value) => new(value * T.CreateChecked(1e30)); + + /// + /// Creates a new instance from the specified gigawatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromGigaWatts(T value) => new(value * T.CreateChecked(1e33)); + + /// + /// Creates a new instance from the specified terawatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromTeraWatts(T value) => new(value * T.CreateChecked(1e36)); + + /// + /// Creates a new instance from the specified petawatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromPetaWatts(T value) => new(value * T.CreateChecked(1e39)); + + /// + /// Creates a new instance from the specified exawatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromExaWatts(T value) => new(value * T.CreateChecked(1e42)); + + /// + /// Creates a new instance from the specified zettawatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromZettaWatts(T value) => new(value * T.CreateChecked(1e45)); + + /// + /// Creates a new instance from the specified yottawatts value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromYottaWatts(T value) => new(value * T.CreateChecked(1e48)); + + /// + /// Creates a new instance from the specified mechanical horsepower value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromHorsepower(T value) => new(value * T.CreateChecked(7.456998715822702e26)); + + /// + /// Creates a new instance from the specified metric horsepower value. + /// + /// The value from which to construct a new instance. + /// Returns a newly created instance from the specified value. + public static Power FromMetricHorsepower(T value) => new(value * T.CreateChecked(7.3549875e26)); +} diff --git a/OnixLabs.Units/Power.To.cs b/OnixLabs.Units/Power.To.cs new file mode 100644 index 0000000..3847cd9 --- /dev/null +++ b/OnixLabs.Units/Power.To.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; +using System.Globalization; + +namespace OnixLabs.Units; + +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Power +{ + /// + /// Formats the value of the current instance using the default format. + /// + /// Returns the value of the current instance in the default format. + public override string ToString() => ToString(YoctoWattsSpecifier); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(string? format, IFormatProvider? formatProvider = null) => + ToString(format.AsSpan(), formatProvider); + + /// + /// Formats the value of the current instance using the specified format. + /// + /// The format to use, or to use the default format. + /// The provider to use to format the value. + /// Returns the value of the current instance in the specified format. + public string ToString(ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + (string specifier, int scale) = format.GetSpecifierAndScale(defaultSpecifier: YoctoWattsSpecifier); + + T value = specifier switch + { + YoctoWattsSpecifier => YoctoWatts, + ZeptoWattsSpecifier => ZeptoWatts, + AttoWattsSpecifier => AttoWatts, + FemtoWattsSpecifier => FemtoWatts, + PicoWattsSpecifier => PicoWatts, + NanoWattsSpecifier => NanoWatts, + MicroWattsSpecifier => MicroWatts, + MilliWattsSpecifier => MilliWatts, + WattsSpecifier => Watts, + KiloWattsSpecifier => KiloWatts, + MegaWattsSpecifier => MegaWatts, + GigaWattsSpecifier => GigaWatts, + TeraWattsSpecifier => TeraWatts, + PetaWattsSpecifier => PetaWatts, + ExaWattsSpecifier => ExaWatts, + ZettaWattsSpecifier => ZettaWatts, + YottaWattsSpecifier => YottaWatts, + HorsepowerSpecifier => Horsepower, + MetricHorsepowerSpecifier => MetricHorsepower, + _ => throw ArgumentException.InvalidFormat(format, + "yW, zW, aW, fW, pW, nW, uW, mW, W, kW, " + + "MW, GW, TW, PW, EW, ZW, YW, hp, and hpM") + }; + + T rounded = scale > 0 ? T.Round(value, scale) : value; + + return $"{rounded.ToString($"N{scale}", formatProvider ?? CultureInfo.CurrentCulture)} {specifier}"; + } +} diff --git a/OnixLabs.Units/Power.cs b/OnixLabs.Units/Power.cs new file mode 100644 index 0000000..f21da00 --- /dev/null +++ b/OnixLabs.Units/Power.cs @@ -0,0 +1,132 @@ +// 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; +using OnixLabs.Core; + +namespace OnixLabs.Units; + +/// +/// Represents a unit of power. +/// +/// The underlying value type. +// ReSharper disable MemberCanBePrivate.Global +public readonly partial struct Power : + IValueEquatable>, + IValueComparable>, + ISpanFormattable + where T : IFloatingPoint +{ + /// + /// Initializes a new instance of the struct. + /// + /// The power unit in . + private Power(T value) => YoctoWatts = value; + + /// + /// Gets the power in yoctowatts (yW). + /// + public T YoctoWatts { get; } + + /// + /// Gets the power in zeptowatts (zW). + /// + public T ZeptoWatts => YoctoWatts / T.CreateChecked(1e3); + + /// + /// Gets the power in attowatts (aW). + /// + public T AttoWatts => YoctoWatts / T.CreateChecked(1e6); + + /// + /// Gets the power in femtowatts (fW). + /// + public T FemtoWatts => YoctoWatts / T.CreateChecked(1e9); + + /// + /// Gets the power in picowatts (pW). + /// + public T PicoWatts => YoctoWatts / T.CreateChecked(1e12); + + /// + /// Gets the power in nanowatts (nW). + /// + public T NanoWatts => YoctoWatts / T.CreateChecked(1e15); + + /// + /// Gets the power in microwatts (µW). + /// + public T MicroWatts => YoctoWatts / T.CreateChecked(1e18); + + /// + /// Gets the power in milliwatts (mW). + /// + public T MilliWatts => YoctoWatts / T.CreateChecked(1e21); + + /// + /// Gets the power in watts (W). + /// + public T Watts => YoctoWatts / T.CreateChecked(1e24); + + /// + /// Gets the power in kilowatts (kW). + /// + public T KiloWatts => YoctoWatts / T.CreateChecked(1e27); + + /// + /// Gets the power in megawatts (MW). + /// + public T MegaWatts => YoctoWatts / T.CreateChecked(1e30); + + /// + /// Gets the power in gigawatts (GW). + /// + public T GigaWatts => YoctoWatts / T.CreateChecked(1e33); + + /// + /// Gets the power in terawatts (TW). + /// + public T TeraWatts => YoctoWatts / T.CreateChecked(1e36); + + /// + /// Gets the power in petawatts (PW). + /// + public T PetaWatts => YoctoWatts / T.CreateChecked(1e39); + + /// + /// Gets the power in exawatts (EW). + /// + public T ExaWatts => YoctoWatts / T.CreateChecked(1e42); + + /// + /// Gets the power in zettawatts (ZW). + /// + public T ZettaWatts => YoctoWatts / T.CreateChecked(1e45); + + /// + /// Gets the power in yottawatts (YW). + /// + public T YottaWatts => YoctoWatts / T.CreateChecked(1e48); + + /// + /// Gets the power in mechanical horsepower (hp). + /// + public T Horsepower => YoctoWatts / T.CreateChecked(7.456998715822702e26); + + /// + /// Gets the power in metric horsepower (hpM). + /// + public T MetricHorsepower => YoctoWatts / T.CreateChecked(7.3549875e26); +}