diff --git a/Directory.Packages.props b/Directory.Packages.props
index a29c695..ded2c6d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,7 +3,7 @@
true
-
+
diff --git a/README.md b/README.md
index 6fc3191..8ce0b4d 100644
--- a/README.md
+++ b/README.md
@@ -13,43 +13,46 @@
[](https://github.com/ktsu-dev/SignificantNumber/graphs/contributors)
[](https://github.com/ktsu-dev/SignificantNumber/actions)
-The `SignificantNumber` class represents a number with a significand and an exponent, enabling high-precision arithmetic operations that comply with calculation rules for significant figures. It provides a robust set of functionalities for mathematical computations and formatting.
+`SignificantNumber` is a numeric value type whose arithmetic follows the rules for significant figures. It holds a [`ktsu.PreciseNumber`](https://github.com/ktsu-dev/PreciseNumber) and rounds every result to the precision its operands justify.
+
## Features
-- High-precision arithmetic operations (addition, subtraction, multiplication, division)
-- Support for significant figures and exponents
-- Integration with .NET numerical interfaces
-- Comprehensive error handling and validation
+- Addition and subtraction round to the fewest decimal places among the operands, and multiplication, division, and modulus round to the fewest significant digits
+- Operands of exactly -1, 0, or 1 have unlimited precision, so they never limit a result
+- A `readonly record struct` whose `default` is zero, holding a `PreciseNumber` that it converts to implicitly
+- Implements `INumber`, including `CreateChecked`, `CreateSaturating`, and `CreateTruncating` for every built-in numeric type, `BigInteger`, and `PreciseNumber`
+
+Upgrading from 1.x? See the [2.0 migration guide](docs/migration-guide-2.0.md).
-## Table of Contents
+## Table of contents
- [Installation](#installation)
- [Usage](#usage)
- [Creating a SignificantNumber](#creating-a-significantnumber)
- - [Supported Numeric Types](#supported-numeric-types)
+ - [Supported numeric types](#supported-numeric-types)
- [Examples](#examples)
- - [Arithmetic Operations](#arithmetic-operations)
- - [Comparison Operations](#comparison-operations)
- - [Formatting and Parsing](#formatting-and-parsing)
- - [Extension Methods](#extension-methods)
+ - [Arithmetic operations](#arithmetic-operations)
+ - [Comparison operations](#comparison-operations)
+ - [Formatting and parsing](#formatting-and-parsing)
+ - [Extension methods](#extension-methods)
- [Conversion](#conversion)
- [Precision](#precision)
- - [Significand and Exponent](#significand-and-exponent)
- - [Precision Handling](#precision-handling)
- - [Example of Precision](#example-of-precision)
-- [API Reference](#api-reference)
+ - [Significand and exponent](#significand-and-exponent)
+ - [Precision handling](#precision-handling)
+ - [Example of precision](#example-of-precision)
+- [API reference](#api-reference)
- [Contributing](#contributing)
- [License](#license)
## Installation
-To install the `SignificantNumber` library, you can use the .NET CLI:
+Install the package with the .NET CLI:
```sh
dotnet add package ktsu.SignificantNumber
```
-Or, add the package reference directly in your project file:
+Or add the package reference directly in your project file:
```xml
@@ -59,13 +62,13 @@ Or, add the package reference directly in your project file:
### Creating a SignificantNumber
-You can create a `SignificantNumber` from various numeric types using the `ToSignificantNumber` extension method:
+Create a `SignificantNumber` from any supported numeric type with the `ToSignificantNumber` extension method, from text with `Parse`, or from a `PreciseNumber` with an explicit cast.
-#### Supported Numeric Types
+#### Supported numeric types
-The `SignificantNumber` class supports a wide range of numeric types through the `ToSignificantNumber` extension method, leveraging the `INumber` interface for conversions. The following types are supported:
+`ToSignificantNumber` converts through `INumber`. These types are supported:
-- **Integer Types**:
+- **Integer types**:
- `int`
- `long`
- `short`
@@ -76,17 +79,21 @@ The `SignificantNumber` class supports a wide range of numeric types through the
- `byte`
- `BigInteger`
-- **Floating-Point Types**:
+- **Floating point types**:
- `double`
- `float`
- `Half`
- `decimal`
-### Examples
+- **ktsu types**:
+ - `PreciseNumber`
+ - `SignificantNumber`
-You can convert various numeric types to `SignificantNumber` using the `ToSignificantNumber` extension method:
+### Examples
```csharp
+using System.Numerics;
+using ktsu.PreciseNumber;
using ktsu.SignificantNumber;
// Integer types
@@ -96,7 +103,7 @@ SignificantNumber significantNumberFromInt = intValue.ToSignificantNumber();
BigInteger bigIntValue = new BigInteger(9876543210);
SignificantNumber significantNumberFromBigInt = bigIntValue.ToSignificantNumber();
-// Floating-point types
+// Floating point types
double doubleValue = 123.45;
SignificantNumber significantNumberFromDouble = doubleValue.ToSignificantNumber();
@@ -108,29 +115,31 @@ SignificantNumber significantNumberFromFloat = floatValue.ToSignificantNumber();
decimal decimalValue = 123.45m;
SignificantNumber significantNumberFromDecimal = decimalValue.ToSignificantNumber();
-```
-### Arithmetic Operations
+// A PreciseNumber, with an explicit cast because it opts into the significant figure rules
+PreciseNumber precise = 12.5.ToPreciseNumber();
+SignificantNumber significantNumberFromPrecise = (SignificantNumber)precise;
+```
-You can perform various arithmetic operations on `SignificantNumber` instances:
+### Arithmetic operations
```csharp
-var result1 = number1 + number2;
-var result2 = number1 - number2;
-var result3 = number1 * number2;
-var result4 = number1 / number2;
+SignificantNumber result1 = number1 + number2;
+SignificantNumber result2 = number1 - number2;
+SignificantNumber result3 = number1 * number2;
+SignificantNumber result4 = number1 / number2;
-// Square and cube operations
-var squared = number1.Squared();
-var cubed = number1.Cubed();
+// Square and cube operations, which return the unrounded PreciseNumber
+PreciseNumber squared = number1.Squared();
+PreciseNumber cubed = number1.Cubed();
// Power operation
-var powerResult = number1.Pow(3); // number1 raised to the power of 3
+SignificantNumber powerResult = number1.Pow(3.ToPreciseNumber());
```
-### Comparison Operations
+The operators also accept a `PreciseNumber` on either side, and the result is a `SignificantNumber`.
-You can compare `SignificantNumber` instances using comparison operators:
+### Comparison operations
```csharp
bool isEqual = number1 == number2;
@@ -138,55 +147,54 @@ bool isGreater = number1 > number2;
bool isLessOrEqual = number1 <= number2;
```
-### Formatting and Parsing
+Ordering operators compare values exactly. `SignificantNumber.CompareTo(left, right)` and `CompareTo(SignificantNumber)` compare both numbers at the lower of their significant digit counts.
+
+### Formatting and parsing
-You can format a `SignificantNumber` as a string:
+Format a `SignificantNumber` as a string:
```csharp
string formatted = number1.ToString("G", CultureInfo.InvariantCulture);
Console.WriteLine(formatted); // Outputs the formatted number
```
-Parsing is not supported and will throw `NotSupportedException`:
+Parse one from text, including scientific notation:
```csharp
-try
-{
- var parsedNumber = SignificantNumber.Parse("123.45", CultureInfo.InvariantCulture);
-}
-catch (NotSupportedException ex)
+SignificantNumber parsed = SignificantNumber.Parse("1.23E4", NumberStyles.Float, CultureInfo.InvariantCulture);
+
+if (SignificantNumber.TryParse("123.45", CultureInfo.InvariantCulture, out SignificantNumber result))
{
- Console.WriteLine(ex.Message);
+ Console.WriteLine(result);
}
```
-Instead, you should parse the number as another numeric type and convert it to a `SignificantNumber`:
-
-```csharp
-double parsedDouble = double.Parse("123.45", CultureInfo.InvariantCulture);
-SignificantNumber significantNumberFromDouble = parsedDouble.ToSignificantNumber();
-```
+`TryParse` yields zero when parsing fails.
-### Extension Methods
+### Extension methods
#### `ToSignificantNumber`
-Converts various numeric types to a `SignificantNumber`.
+Converts a supported numeric type to a `SignificantNumber`. An overload takes the number of significant digits to keep.
#### Usage
```csharp
-public static SignificantNumber ToSignificantNumber(this INumber input)
+public static SignificantNumber ToSignificantNumber(this TInput input)
+ where TInput : INumber
+
+public static SignificantNumber ToSignificantNumber(this TInput input, int significantDigits)
where TInput : INumber
```
#### Parameters
-- `input`: The input number to convert.
+- `input`: The number to convert.
+- `significantDigits`: The number of significant digits to keep. It must be greater than zero.
#### Returns
-- `SignificantNumber`: The converted `SignificantNumber`.
+- `SignificantNumber`: The converted number.
#### Example
@@ -214,10 +222,6 @@ public TOutput To()
where TOutput : INumber
```
-#### Parameters
-
-- None
-
#### Returns
- `TOutput`: The converted value of the `SignificantNumber`.
@@ -225,65 +229,81 @@ public TOutput To()
#### Example
```csharp
-SignificantNumber significantNumber = new SignificantNumber(3, 12345); // 12345e3
+SignificantNumber significantNumber = SignificantNumber.Parse("12345E3", NumberStyles.Float, CultureInfo.InvariantCulture);
double result = significantNumber.To();
Console.WriteLine(result); // Outputs 12345000
```
-## Precision
+Generic code converts the same way through `CreateChecked`, `CreateSaturating`, and `CreateTruncating`:
-The `SignificantNumber` class is designed to handle high-precision arithmetic operations with significant figures and exponents.
+```csharp
+static T ToMeters(T feet) where T : INumber => feet * T.CreateChecked(0.3048);
-Here's how it ensures precision:
+SignificantNumber meters = ToMeters(10.ToSignificantNumber());
+double asDouble = double.CreateChecked(meters);
+```
-### Significand and Exponent
+A `SignificantNumber` converts to a `PreciseNumber` implicitly, and `Value` returns the `PreciseNumber` it holds.
-A `SignificantNumber` consists of two main components:
+## Precision
-- **Significand**: This is the significant part of the number, stored as a `BigInteger` to accommodate a wide range of values with high precision.
-- **Exponent**: This is the exponent part of the number, which scales the significand by a power of ten.
+### Significand and exponent
-### Precision Handling
+A `SignificantNumber` holds a `PreciseNumber`, which stores two components:
-- **Maximum Significant Digits**: When converting from a floating-point number to a `SignificantNumber` maximum number of significant digits is limited to 7 for `float` values, and 16 for `double` values.
-- **Trailing Zero Removal**: The class automatically sanitizes the significand by removing trailing zeros, ensuring that the number is stored in its most compact and precise form.
-- **Rounding**: You can round a `SignificantNumber` to a specified number of decimal digits, ensuring that you can control the precision of your calculations.
+- **Significand**: The significant digits of the number, stored as a `BigInteger`.
+- **Exponent**: The power of ten that scales the significand.
-### Example of Precision
+`Significand`, `Exponent`, and `SignificantDigits` are available directly on `SignificantNumber`.
-Consider the number `123.456000`:
+### Precision handling
-- When stored as a `SignificantNumber`, it will be represented as `123456e-3` after removing the trailing zeros and adjusting the exponent accordingly.
-- This ensures that the number is represented with the exact precision required for your calculations, without unnecessary trailing zeros.
+- **Floating point input**: A `float` keeps up to 8 significant digits, and a `double` up to 16.
+- **Trailing zero removal**: Trailing zeros move from the significand into the exponent, so every value is stored in its most compact form.
+- **Rounding**: `Round` rounds to a number of decimal digits, and `ReduceSignificance` to a number of significant digits.
+
+### Example of precision
+
+Consider the number `123.456000`:
-By using the `SignificantNumber` class, you can perform high-precision arithmetic operations and maintain control over the significant figures and exponent, ensuring accurate and efficient mathematical computations.
+- As a `SignificantNumber`, it's stored as `123456e-3` after removing the trailing zeros and adjusting the exponent.
+- Adding `1.2` to it rounds the sum to one decimal place, giving `124.7`, because `1.2` has the fewest decimal places.
-## API Reference
+## API reference
### Properties
-- `static SignificantNumber NegativeOne` - Gets the value -1 for the type.
-- `static SignificantNumber One` - Gets the value 1 for the type.
-- `static SignificantNumber Zero` - Gets the value 0 for the type.
+- `PreciseNumber Value` - Gets the `PreciseNumber` the number holds.
+- `int Exponent`, `BigInteger Significand`, and `int SignificantDigits` - Get the components of the held value.
+- `static SignificantNumber NegativeOne`, `One`, and `Zero` - Get -1, 1, and 0. `Zero` is also `default`.
+- `static SignificantNumber E`, `Pi`, and `Tau` - Get the mathematical constants.
- `static int Radix` - Gets the radix, or base, for the type.
-- `static SignificantNumber AdditiveIdentity` - Gets the additive identity of the current type.
-- `static SignificantNumber MultiplicativeIdentity` - Gets the multiplicative identity of the current type.
+- `static SignificantNumber AdditiveIdentity` - Gets the additive identity of the type.
+- `static SignificantNumber MultiplicativeIdentity` - Gets the multiplicative identity of the type.
### Methods
-- `bool Equals(SignificantNumber other)` - Determines whether the specified object is equal to the current object.
+- `bool Equals(SignificantNumber other)` - Determines whether two numbers have the same significand and exponent.
- `int CompareTo(object? obj)` - Compares the current instance with another object.
-- `int CompareTo(SignificantNumber other)` - Compares the current instance with another significant number.
-- `int CompareTo(TInput other) where TInput : INumber` - Compares the current instance with another number.
-- `SignificantNumber Abs()` - Returns the absolute value of the current instance.
-- `SignificantNumber Round(int decimalDigits)` - Rounds the current instance to the specified number of decimal digits.
-- `SignificantNumber Clamp(TNumber min, TNumber max) where TNumber : INumber` - Clamps the specified value between the minimum and maximum values.
-- `string ToString(string? format, IFormatProvider? formatProvider)` - Converts the current instance to its equivalent string representation using the specified format and format provider.
+- `int CompareTo(SignificantNumber other)` - Compares the current instance with another significant number at the lower of their significant digit counts.
+- `int CompareTo(TInput other) where TInput : INumber` - Compares the value of the current instance with another number.
+- `PreciseNumber Abs()` - Returns the absolute value of the current instance.
+- `PreciseNumber Round(int decimalDigits)` - Rounds the current instance to the specified number of decimal digits.
+- `PreciseNumber ReduceSignificance(int significantDigits)` - Reduces the current instance to the specified number of significant digits.
+- `PreciseNumber Clamp(TNumber min, TNumber max) where TNumber : INumber` - Clamps the current instance between the minimum and maximum values.
+- `SignificantNumber Pow(PreciseNumber power)` - Raises the current instance to a power.
+- `PreciseNumber ToPreciseNumber()` - Returns the `PreciseNumber` the number holds.
+- `string ToString(string? format, IFormatProvider? formatProvider)` - Converts the current instance to a string using the specified format and format provider.
- `bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider)` - Attempts to format the current instance into the provided span.
- `TOutput To() where TOutput : INumber` - Converts the current significant number to the specified numeric type.
-### Static Methods
+### Static methods
+- `static SignificantNumber FromPreciseNumber(PreciseNumber value)` - Creates a significant number that holds a `PreciseNumber`.
+- `static SignificantNumber Add`, `Subtract`, `Multiply`, `Divide`, and `Mod(PreciseNumber left, PreciseNumber right)` - Apply the significant figure rules to two numbers.
+- `static SignificantNumber Exp(PreciseNumber power)` - Raises e to a power.
+- `static SignificantNumber Max`, `Min(SignificantNumber x, SignificantNumber y)`, and `Clamp(SignificantNumber value, SignificantNumber min, SignificantNumber max)` - Compare by value.
+- `static SignificantNumber Round(SignificantNumber value, int decimalDigits)` - Rounds a number to the specified number of decimal digits.
- `static SignificantNumber Abs(SignificantNumber value)` - Returns the absolute value of a `SignificantNumber`.
- `static bool IsCanonical(SignificantNumber value)` - Determines whether the specified value is canonical.
- `static bool IsComplexNumber(SignificantNumber value)` - Determines whether the specified value is a complex number.
@@ -309,23 +329,19 @@ By using the `SignificantNumber` class, you can perform high-precision arithmeti
### Operators
+- `static implicit operator PreciseNumber(SignificantNumber value)` - Converts to the held `PreciseNumber`.
+- `static explicit operator SignificantNumber(PreciseNumber value)` - Creates a significant number that holds a `PreciseNumber`.
- `static SignificantNumber operator -(SignificantNumber value)` - Negates a significant number.
-- `static SignificantNumber operator -(SignificantNumber left, SignificantNumber right)` - Subtracts one significant number from another.
-- `static bool operator !=(SignificantNumber left, SignificantNumber right)` - Determines whether two significant numbers are not equal.
-- `static SignificantNumber operator *(SignificantNumber left, SignificantNumber right)` - Multiplies two significant numbers.
-- `static SignificantNumber operator /(SignificantNumber left, SignificantNumber right)` - Divides one significant number by another.
+- `static SignificantNumber operator +`, `-`, `*`, `/`, and `%` - Apply the significant figure rules. Each also accepts a `PreciseNumber` on either side.
- `static SignificantNumber operator +(SignificantNumber value)` - Returns the unary plus of a significant number.
-- `static SignificantNumber operator +(SignificantNumber left, SignificantNumber right)` - Adds two significant numbers.
-- `static bool operator ==(SignificantNumber left, SignificantNumber right)` - Determines whether two significant numbers are equal.
-- `static bool operator >(SignificantNumber left, SignificantNumber right)` - Determines whether one significant number is greater than another.
-- `static bool operator <(SignificantNumber left, SignificantNumber right)` - Determines whether one significant number is less than another.
-- `static bool operator >=(SignificantNumber left, SignificantNumber right)` - Determines whether one significant number is greater than or equal to another.
-- `static bool operator <=(SignificantNumber left, SignificantNumber right)` - Determines whether one significant number is less than or equal to another.
+- `static SignificantNumber operator ++` and `--` - Increment and decrement by one.
+- `static bool operator ==` and `!=` - Determine whether two numbers are equal, including against a `PreciseNumber`.
+- `static bool operator >`, `<`, `>=`, and `<=` - Compare two numbers, including against a `PreciseNumber`.
## Contributing
-Contributions are welcome! Please feel free to submit a pull request or open an issue.
+Contributions are welcome. Submit a pull request or open an issue.
## License
-This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
+This project is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details.
diff --git a/SignificantNumber.Test/SignificantNumberTests.cs b/SignificantNumber.Test/SignificantNumberTests.cs
index 1c1c150..640e0b9 100644
--- a/SignificantNumber.Test/SignificantNumberTests.cs
+++ b/SignificantNumber.Test/SignificantNumberTests.cs
@@ -420,14 +420,11 @@ public class DummyTestClass : ITest { }
public class DummyNonTestClass { }
[TestMethod]
- public void CreateFromComponents_WithExplicitNormalize_ReturnsNormalizedValue()
+ public void CreateFromComponents_ReturnsNormalizedValue()
{
- // Tests the CreateFromComponents method with the normalize parameter
- SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(123), true);
+ // Tests that CreateFromComponents keeps a significand with no trailing zeros as it is
+ SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(123));
- // Verify it returns a normalized value (the implementation details might vary)
- Assert.IsNotNull(number);
- // This assumes normalization doesn't change the value for this simple case
Assert.AreEqual(new BigInteger(123), number.Significand);
Assert.AreEqual(2, number.Exponent);
}
@@ -439,10 +436,9 @@ public void Exp_NegativeValue_ReturnsCorrectResult()
SignificantNumber power = SignificantNumber.CreateFromComponents(0, new BigInteger(-2));
SignificantNumber result = SignificantNumber.Exp(power);
- // The expected result should be approximately 1/e^2
- // This is a very simple test; actual implementation may have more precision considerations
- Assert.IsNotNull(result);
- // Add specific value assertion based on your implementation
+ // The expected result is approximately 1/e^2, which is between zero and one
+ Assert.IsTrue(SignificantNumber.IsPositive(result), "Exp of a negative power should be positive");
+ Assert.IsLessThan(1.0, result.To());
}
[TestMethod]
@@ -538,11 +534,19 @@ public void Operator_Comparison_WithDifferentTypes_ReturnsCorrectResults()
}
[TestMethod]
- public void ToSignificantNumber_ReturnsSameInstance()
+ public void ToSignificantNumber_FromPreciseNumber_KeepsValue()
{
PreciseNumber number = SignificantNumber.CreateFromComponents(0, new BigInteger(5));
SignificantNumber result = number.ToSignificantNumber();
- Assert.AreSame(number, result);
+ Assert.AreEqual(number, result);
+ }
+
+ [TestMethod]
+ public void ToSignificantNumber_FromSignificantNumber_ReturnsSameValue()
+ {
+ SignificantNumber number = SignificantNumber.CreateFromComponents(0, new BigInteger(5));
+ SignificantNumber result = number.ToSignificantNumber();
+ Assert.AreEqual(number, result);
}
[TestMethod]
@@ -647,7 +651,7 @@ public void Parse_ValidString_ReturnsCorrectNumber()
public void TryParse_ValidString_ReturnsTrueAndCorrectNumber()
{
string input = "5";
- bool success = SignificantNumber.TryParse(input, CultureInfo.InvariantCulture, out SignificantNumber? result);
+ bool success = SignificantNumber.TryParse(input, CultureInfo.InvariantCulture, out SignificantNumber result);
Assert.IsTrue(success, "TryParse should return true for a valid numeric string");
Assert.AreEqual(SignificantNumber.CreateFromComponents(0, new BigInteger(5)), result);
}
@@ -656,31 +660,21 @@ public void TryParse_ValidString_ReturnsTrueAndCorrectNumber()
public void TryParse_InvalidString_ReturnsFalse()
{
string input = "invalid";
- bool success = SignificantNumber.TryParse(input, CultureInfo.InvariantCulture, out SignificantNumber? result);
+ bool success = SignificantNumber.TryParse(input, CultureInfo.InvariantCulture, out SignificantNumber result);
Assert.IsFalse(success, "TryParse should return false for an invalid string");
- Assert.IsNull(result);
+ Assert.AreEqual(SignificantNumber.Zero, result);
}
[TestMethod]
- public void CreateFromComponents_WithSanitizeTrue_RemovesTrailingZeros()
+ public void CreateFromComponents_TrailingZeros_AreRemoved()
{
- SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(12300), true);
+ SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(12300));
- // Assuming sanitization removes trailing zeros
+ // Sanitization removes trailing zeros and moves them into the exponent
Assert.AreEqual(new BigInteger(123), number.Significand);
Assert.AreEqual(4, number.Exponent); // Adjusted exponent
}
- [TestMethod]
- public void CreateFromComponents_WithSanitizeFalse_KeepsTrailingZeros()
- {
- SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(12300), false);
-
- // Trailing zeros should remain
- Assert.AreEqual(new BigInteger(12300), number.Significand);
- Assert.AreEqual(2, number.Exponent);
- }
-
[TestMethod]
public void DoesImplementGenericInterface_InvalidGenericInterface_ThrowsArgumentException()
{
diff --git a/SignificantNumber.Test/SignificantNumberValueTypeTests.cs b/SignificantNumber.Test/SignificantNumberValueTypeTests.cs
new file mode 100644
index 0000000..65ea469
--- /dev/null
+++ b/SignificantNumber.Test/SignificantNumberValueTypeTests.cs
@@ -0,0 +1,234 @@
+// Copyright (c) 2023-2026 ktsu-dev contributors
+
+namespace SignificantNumber.Test;
+
+using System.Globalization;
+using System.Numerics;
+using System.Text;
+using ktsu.PreciseNumber;
+using ktsu.SignificantNumber;
+
+///
+/// Covers what changed when became a value type that holds a .
+///
+[TestClass]
+public class SignificantNumberValueTypeTests
+{
+ private static SignificantNumber Parse(string text) =>
+ SignificantNumber.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);
+
+ // The generic helpers call conversions the way generic numeric code does, through the static interface members.
+ private static TTo ConvertChecked(TFrom value)
+ where TFrom : INumberBase
+ where TTo : INumberBase =>
+ TTo.CreateChecked(value);
+
+ private static TTo ConvertSaturating(TFrom value)
+ where TFrom : INumberBase
+ where TTo : INumberBase =>
+ TTo.CreateSaturating(value);
+
+ private static TTo ConvertTruncating(TFrom value)
+ where TFrom : INumberBase
+ where TTo : INumberBase =>
+ TTo.CreateTruncating(value);
+
+ private static T Sum(params T[] values)
+ where T : INumber
+ {
+ T total = T.Zero;
+ foreach (T value in values)
+ {
+ total += value;
+ }
+
+ return total;
+ }
+
+ [TestMethod]
+ public void Default_EqualsZero()
+ {
+ SignificantNumber value = default;
+
+ Assert.AreEqual(SignificantNumber.Zero, value);
+ Assert.IsTrue(SignificantNumber.IsZero(value), "default should be zero");
+ Assert.AreEqual(0, value.Exponent);
+ Assert.AreEqual(BigInteger.Zero, value.Significand);
+ Assert.AreEqual(0, value.SignificantDigits);
+ Assert.AreEqual(Parse("0").GetHashCode(), value.GetHashCode());
+ }
+
+ [TestMethod]
+ public void Default_ArrayElementIsUsableZero()
+ {
+ SignificantNumber[] values = new SignificantNumber[3];
+
+ Assert.AreEqual(SignificantNumber.Zero, values[1]);
+ Assert.AreEqual(Parse("2.5"), values[1] + Parse("2.5"));
+ }
+
+ [TestMethod]
+ public void ImplicitConversion_ToPreciseNumber_KeepsValue()
+ {
+ SignificantNumber significant = Parse("123.45");
+ PreciseNumber precise = significant;
+
+ Assert.AreEqual(significant.Value, precise);
+ Assert.AreEqual(PreciseNumber.Parse("123.45", CultureInfo.InvariantCulture), precise);
+ }
+
+ [TestMethod]
+ public void ExplicitConversion_FromPreciseNumber_HoldsValue()
+ {
+ PreciseNumber precise = PreciseNumber.Parse("123.45", CultureInfo.InvariantCulture);
+ SignificantNumber significant = (SignificantNumber)precise;
+
+ Assert.AreEqual(precise, significant.Value);
+ Assert.AreEqual(significant, SignificantNumber.FromPreciseNumber(precise));
+ }
+
+ [TestMethod]
+ public void ToPreciseNumber_ReturnsHeldValue()
+ {
+ SignificantNumber significant = Parse("0.001");
+
+ Assert.AreEqual(significant.Value, significant.ToPreciseNumber());
+ }
+
+ [TestMethod]
+ public void CreateChecked_FromDouble_IsExact()
+ {
+ SignificantNumber result = ConvertChecked(0.3048);
+
+ Assert.AreEqual(Parse("0.3048"), result);
+ }
+
+ [TestMethod]
+ public void CreateChecked_FromInt_IsExact()
+ {
+ SignificantNumber result = ConvertChecked(42);
+
+ Assert.AreEqual(Parse("42"), result);
+ }
+
+ [TestMethod]
+ public void CreateChecked_ToDouble_IsExact()
+ {
+ double result = ConvertChecked(Parse("0.3048"));
+
+ Assert.AreEqual(0.3048, result);
+ }
+
+ [TestMethod]
+ public void CreateChecked_ToAndFromPreciseNumber_KeepsValue()
+ {
+ SignificantNumber significant = Parse("98.6");
+
+ PreciseNumber precise = ConvertChecked(significant);
+ SignificantNumber roundTripped = ConvertChecked(precise);
+
+ Assert.AreEqual(significant.Value, precise);
+ Assert.AreEqual(significant, roundTripped);
+ }
+
+ [TestMethod]
+ public void CreateChecked_ToInt_OutOfRange_Throws()
+ {
+ SignificantNumber tooLarge = Parse("1e20");
+
+ Assert.ThrowsExactly(() => ConvertChecked(tooLarge));
+ }
+
+ [TestMethod]
+ public void CreateSaturating_ToByte_Clamps()
+ {
+ byte result = ConvertSaturating(Parse("300"));
+
+ Assert.AreEqual(byte.MaxValue, result);
+ }
+
+ [TestMethod]
+ public void CreateTruncating_ToInt_TruncatesTowardZero()
+ {
+ int result = ConvertTruncating(Parse("12.9"));
+
+ Assert.AreEqual(12, result);
+ }
+
+ [TestMethod]
+ public void GenericSum_AppliesDecimalPlaceRule()
+ {
+ // 1.25 + 2.1 is 3.35, which the decimal place rule rounds to one decimal place. Zero, the starting total,
+ // has unlimited precision, so it doesn't limit the first addition.
+ SignificantNumber result = Sum(Parse("1.25"), Parse("2.1"));
+
+ Assert.AreEqual(Parse("3.4"), result);
+ }
+
+ [TestMethod]
+ public void StaticMaxMinClamp_CompareByValue()
+ {
+ SignificantNumber low = Parse("1.5");
+ SignificantNumber high = Parse("2.5");
+
+ Assert.AreEqual(high, SignificantNumber.Max(low, high));
+ Assert.AreEqual(low, SignificantNumber.Min(low, high));
+ Assert.AreEqual(high, SignificantNumber.Clamp(Parse("9"), low, high));
+ Assert.AreEqual(Parse("1.2"), SignificantNumber.Round(Parse("1.23"), 1));
+ }
+
+ [TestMethod]
+ public void TryParse_Invalid_YieldsZero()
+ {
+ bool parsed = SignificantNumber.TryParse("not a number", NumberStyles.Float, CultureInfo.InvariantCulture, out SignificantNumber result);
+
+ Assert.IsFalse(parsed, "TryParse should fail for text that isn't a number");
+ Assert.AreEqual(SignificantNumber.Zero, result);
+ }
+
+ [TestMethod]
+ public void CompareToObject_Null_SortsFirst()
+ {
+ object? nothing = null;
+ int result = Parse("5").CompareTo(nothing);
+
+ Assert.AreEqual(1, result);
+ }
+
+ [TestMethod]
+ public void CompareToObject_BoxedPreciseNumber_ComparesValue()
+ {
+ object precise = PreciseNumber.Parse("5", CultureInfo.InvariantCulture);
+
+ Assert.AreEqual(0, Parse("5").CompareTo(precise));
+ }
+
+ [TestMethod]
+ public void ToString_MatchesHeldValue()
+ {
+ SignificantNumber significant = Parse("123.45");
+
+ Assert.AreEqual("123.45", significant.ToString(CultureInfo.InvariantCulture));
+ Assert.AreEqual(significant.Value.ToString(CultureInfo.InvariantCulture), significant.ToString(CultureInfo.InvariantCulture));
+ }
+
+ [TestMethod]
+ public void Utf8TryFormat_WritesSameTextAsToString()
+ {
+ SignificantNumber significant = Parse("123.45");
+ Span buffer = stackalloc byte[64];
+
+ bool formatted = ((IUtf8SpanFormattable)significant).TryFormat(buffer, out int bytesWritten, default, CultureInfo.InvariantCulture);
+
+ Assert.IsTrue(formatted, "Formatting a short number into a 64 byte buffer should succeed");
+ Assert.AreEqual(significant.ToString(CultureInfo.InvariantCulture), Encoding.UTF8.GetString(buffer[..bytesWritten]));
+ }
+
+ [TestMethod]
+ public void ToSignificantNumberWithDigits_FromSignificantNumber_ReducesSignificance()
+ {
+ SignificantNumber result = Parse("123.456").ToSignificantNumber(3);
+
+ Assert.AreEqual(Parse("123"), result);
+ }
+}
diff --git a/SignificantNumber/SignificantNumber.cs b/SignificantNumber/SignificantNumber.cs
index 86fff7e..cc5d5d0 100644
--- a/SignificantNumber/SignificantNumber.cs
+++ b/SignificantNumber/SignificantNumber.cs
@@ -11,62 +11,186 @@ namespace ktsu.SignificantNumber;
using ktsu.PreciseNumber;
///
-/// Represents a significant number.
+/// Represents a number whose arithmetic follows the rules for significant figures.
///
+///
+///
+/// A is a value type that holds a . Its default value is
+/// zero, so an uninitialized field or array element is a valid number.
+///
+///
+/// Addition and subtraction round the result to the fewest decimal places among the operands. Multiplication,
+/// division, and modulus round it to the fewest significant digits. An operand of exactly -1, 0, or 1 is treated as
+/// having unlimited precision, so it never limits the result.
+///
+///
[DebuggerDisplay("{Significand}e{Exponent}")]
-public record SignificantNumber
- : PreciseNumber, INumber
+public readonly record struct SignificantNumber
+ : INumber
{
///
- /// Gets the significant number representing one.
+ /// Initializes a new instance of the struct that holds the specified value.
///
- public static new SignificantNumber One => PreciseNumber.One.ToSignificantNumber();
+ /// The value to hold.
+ public SignificantNumber(PreciseNumber value) => Value = value;
///
- /// Gets the significant number representing zero.
+ /// Gets the value this number holds.
///
- public static new SignificantNumber Zero => PreciseNumber.Zero.ToSignificantNumber();
+ public PreciseNumber Value { get; }
+
+ ///
+ /// Gets the value -1.
+ ///
+ public static SignificantNumber NegativeOne { get; } = new(PreciseNumber.NegativeOne);
+
+ ///
+ /// Gets the value 1.
+ ///
+ public static SignificantNumber One { get; } = new(PreciseNumber.One);
+
+ ///
+ /// Gets the value 0, which is also the default value of the type.
+ ///
+ public static SignificantNumber Zero => default;
+
+ ///
+ /// Gets the value of e.
+ ///
+ public static SignificantNumber E { get; } = new(PreciseNumber.E);
+
+ ///
+ /// Gets the value of pi.
+ ///
+ public static SignificantNumber Pi { get; } = new(PreciseNumber.Pi);
+
+ ///
+ /// Gets the value of tau.
+ ///
+ public static SignificantNumber Tau { get; } = new(PreciseNumber.Tau);
+
+ ///
+ public static int Radix => PreciseNumber.Radix;
///
/// Gets the additive identity for significant numbers, which is zero.
///
- public static new SignificantNumber AdditiveIdentity => Zero;
+ public static SignificantNumber AdditiveIdentity => Zero;
///
/// Gets the multiplicative identity for significant numbers, which is one.
///
- public static new SignificantNumber MultiplicativeIdentity => One;
+ public static SignificantNumber MultiplicativeIdentity => One;
///
- /// Initializes a new instance of the record using a value.
+ /// Gets the exponent of the number.
///
- /// The value to initialize the with.
- public SignificantNumber(PreciseNumber value) : base(value) { }
+ public int Exponent => Value.Exponent;
///
- /// Initializes a new instance of the record.
+ /// Gets the significand of the number.
///
- /// The exponent of the number.
- /// The significand of the number.
- /// If true, trailing zeros in the significand will be removed.
- protected SignificantNumber(int exponent, BigInteger significand, bool sanitize)
- : base(exponent, significand, sanitize)
- { }
+ public BigInteger Significand => Value.Significand;
+
+ ///
+ /// Gets the number of significant digits in the number.
+ ///
+ public int SignificantDigits => Value.SignificantDigits;
///
- /// Initializes a new instance of the record.
+ /// Converts a significant number to the it holds.
+ ///
+ /// The significant number to convert.
+ public static implicit operator PreciseNumber(SignificantNumber value) => value.Value;
+
+ ///
+ /// Converts a to a significant number that holds it.
+ ///
+ /// The value to convert.
+ public static explicit operator SignificantNumber(PreciseNumber value) => new(value);
+
+ ///
+ /// Gets the this number holds.
+ ///
+ /// The value this number holds.
+ public PreciseNumber ToPreciseNumber() => Value;
+
+ ///
+ /// Creates a significant number that holds the specified value.
+ ///
+ /// The value to hold.
+ /// A significant number that holds .
+ public static SignificantNumber FromPreciseNumber(PreciseNumber value) => new(value);
+
+ ///
+ /// Creates a significant number from a significand and an exponent, removing trailing zeros from the significand.
///
/// The exponent of the number.
/// The significand of the number.
- protected SignificantNumber(int exponent, BigInteger significand)
- : this(exponent, significand, sanitize: true)
- { }
-
+ /// The number × 10^.
internal static SignificantNumber CreateFromComponents(int exponent, BigInteger significand) =>
- new(exponent, significand);
+ // PreciseNumber has no public constructor that takes components, and parsing scientific notation is exact.
+ new(PreciseNumber.Parse(
+ string.Create(CultureInfo.InvariantCulture, $"{significand}E{exponent}"),
+ NumberStyles.Float,
+ CultureInfo.InvariantCulture));
+
+ ///
+ /// Determines whether a number is exactly -1, 0, or 1, which the significant figure rules treat as having
+ /// unlimited precision.
+ ///
+ /// The number to check.
+ /// when is -1, 0, or 1.
+ private static bool HasInfinitePrecision(PreciseNumber value) =>
+ value.Exponent == 0 && BigInteger.Abs(value.Significand) <= BigInteger.One;
+
+ ///
+ /// Counts the digits after the decimal point in a number.
+ ///
+ /// The number to count the decimal digits of.
+ /// The number of digits after the decimal point.
+ private static int CountDecimalDigits(PreciseNumber value) =>
+ value.Exponent > 0
+ ? 0
+ : int.Abs(value.Exponent);
+
+ ///
+ /// Gets the lower of the decimal digit counts of two numbers, ignoring an operand with unlimited precision.
+ ///
+ /// The first number.
+ /// The second number.
+ /// The lower of the decimal digit counts of the two numbers.
+ private static int LowestDecimalDigits(PreciseNumber left, PreciseNumber right)
+ {
+ int leftDecimalDigits = CountDecimalDigits(left);
+ int rightDecimalDigits = CountDecimalDigits(right);
+
+ leftDecimalDigits = HasInfinitePrecision(left) ? rightDecimalDigits : leftDecimalDigits;
+ rightDecimalDigits = HasInfinitePrecision(right) ? leftDecimalDigits : rightDecimalDigits;
+
+ return leftDecimalDigits < rightDecimalDigits
+ ? leftDecimalDigits
+ : rightDecimalDigits;
+ }
+
+ ///
+ /// Gets the lower of the significant digit counts of two numbers, ignoring an operand with unlimited precision.
+ ///
+ /// The first number.
+ /// The second number.
+ /// The lower of the significant digit counts of the two numbers.
+ private static int LowestSignificantDigits(PreciseNumber left, PreciseNumber right)
+ {
+ int leftSignificantDigits = left.SignificantDigits;
+ int rightSignificantDigits = right.SignificantDigits;
+
+ leftSignificantDigits = HasInfinitePrecision(left) ? rightSignificantDigits : leftSignificantDigits;
+ rightSignificantDigits = HasInfinitePrecision(right) ? leftSignificantDigits : rightSignificantDigits;
- internal static SignificantNumber CreateFromComponents(int exponent, BigInteger significand, bool sanitize) =>
- new(exponent, significand, sanitize);
+ return leftSignificantDigits < rightSignificantDigits
+ ? leftSignificantDigits
+ : rightSignificantDigits;
+ }
///
/// Subtracts one number from another.
@@ -74,12 +198,10 @@ internal static SignificantNumber CreateFromComponents(int exponent, BigInteger
/// The number to subtract from.
/// The number to subtract.
/// The result of the subtraction.
- public static new SignificantNumber Subtract(PreciseNumber left, PreciseNumber right)
+ public static SignificantNumber Subtract(PreciseNumber left, PreciseNumber right)
{
int lowestDecimalDigits = LowestDecimalDigits(left, right);
- return PreciseNumber.Subtract(left, right)
- .Round(lowestDecimalDigits)
- .ToSignificantNumber();
+ return new(PreciseNumber.Subtract(left, right).Round(lowestDecimalDigits));
}
///
@@ -88,12 +210,10 @@ internal static SignificantNumber CreateFromComponents(int exponent, BigInteger
/// The first number to add.
/// The second number to add.
/// The result of the addition.
- public static new SignificantNumber Add(PreciseNumber left, PreciseNumber right)
+ public static SignificantNumber Add(PreciseNumber left, PreciseNumber right)
{
int lowestDecimalDigits = LowestDecimalDigits(left, right);
- return PreciseNumber.Add(left, right)
- .Round(lowestDecimalDigits)
- .ToSignificantNumber();
+ return new(PreciseNumber.Add(left, right).Round(lowestDecimalDigits));
}
///
@@ -102,7 +222,7 @@ internal static SignificantNumber CreateFromComponents(int exponent, BigInteger
/// The first number to multiply.
/// The second number to multiply.
/// The result of the multiplication.
- public static new SignificantNumber Multiply(PreciseNumber left, PreciseNumber right)
+ public static SignificantNumber Multiply(PreciseNumber left, PreciseNumber right)
{
int lowestSignificantDigits = LowestSignificantDigits(left, right);
return PreciseNumber.Multiply(left, right)
@@ -112,11 +232,10 @@ internal static SignificantNumber CreateFromComponents(int exponent, BigInteger
///
/// Divides one number by another.
///
-
/// The number to divide.
/// The number to divide by.
/// The result of the division.
- public static new SignificantNumber Divide(PreciseNumber left, PreciseNumber right)
+ public static SignificantNumber Divide(PreciseNumber left, PreciseNumber right)
{
int lowestSignificantDigits = LowestSignificantDigits(left, right);
return PreciseNumber.Divide(left, right)
@@ -129,7 +248,7 @@ internal static SignificantNumber CreateFromComponents(int exponent, BigInteger
/// The number to divide.
/// The number to divide by.
/// The modulus of the two numbers.
- public static new SignificantNumber Mod(PreciseNumber left, PreciseNumber right)
+ public static SignificantNumber Mod(PreciseNumber left, PreciseNumber right)
{
int lowestSignificantDigits = LowestSignificantDigits(left, right);
return PreciseNumber.Mod(left, right)
@@ -140,19 +259,17 @@ internal static SignificantNumber CreateFromComponents(int exponent, BigInteger
/// Increments the specified significant number by one.
///
/// The significant number to increment.
- /// A new instance of representing the incremented value.
+ /// The incremented value.
public static SignificantNumber Increment(SignificantNumber value) =>
- PreciseNumber.Increment(value)
- .ToSignificantNumber();
+ new(PreciseNumber.Increment(value.Value));
///
/// Decrements the specified significant number by one.
///
/// The significant number to decrement.
- /// A new instance of representing the decremented value.
+ /// The decremented value.
public static SignificantNumber Decrement(SignificantNumber value) =>
- PreciseNumber.Decrement(value)
- .ToSignificantNumber();
+ new(PreciseNumber.Decrement(value.Value));
///
/// Returns the unary plus of a number.
@@ -160,98 +277,72 @@ public static SignificantNumber Decrement(SignificantNumber value) =>
/// The number.
/// The unary plus of the number.
public static SignificantNumber Plus(SignificantNumber value) =>
- PreciseNumber.Plus(value)
- .ToSignificantNumber();
+ new(PreciseNumber.Plus(value.Value));
///
/// Negates the specified significant number.
///
/// The significant number to negate.
- /// A new instance of representing the negated value.
+ /// The negated value.
public static SignificantNumber Negate(SignificantNumber value) =>
- PreciseNumber.Negate(value)
- .ToSignificantNumber();
+ new(PreciseNumber.Negate(value.Value));
///
/// Determines whether one number is greater than another.
///
/// The first number.
/// The second number.
- /// true if the first number is greater than the second; otherwise, false.
- public static new bool GreaterThan(PreciseNumber left, PreciseNumber right)
- {
- Ensure.NotNull(left);
- Ensure.NotNull(right);
- return left.CompareTo(right) > 0;
- }
+ /// if the first number is greater than the second; otherwise, .
+ public static bool GreaterThan(PreciseNumber left, PreciseNumber right) =>
+ left.CompareTo(right) > 0;
///
/// Determines whether one number is greater than or equal to another.
///
/// The first number.
/// The second number.
- /// true if the first number is greater than or equal to the second; otherwise, false.
- public static new bool GreaterThanOrEqual(PreciseNumber left, PreciseNumber right)
- {
- Ensure.NotNull(left);
- Ensure.NotNull(right);
- return left.CompareTo(right) >= 0;
- }
+ /// if the first number is greater than or equal to the second; otherwise, .
+ public static bool GreaterThanOrEqual(PreciseNumber left, PreciseNumber right) =>
+ left.CompareTo(right) >= 0;
///
/// Determines whether one number is less than another.
///
/// The first number.
/// The second number.
- /// true if the first number is less than the second; otherwise, false.
- public static new bool LessThan(PreciseNumber left, PreciseNumber right)
- {
- Ensure.NotNull(left);
- Ensure.NotNull(right);
- return left.CompareTo(right) < 0;
- }
+ /// if the first number is less than the second; otherwise, .
+ public static bool LessThan(PreciseNumber left, PreciseNumber right) =>
+ left.CompareTo(right) < 0;
///
/// Determines whether one number is less than or equal to another.
///
/// The first number.
/// The second number.
- /// true if the first number is less than or equal to the second; otherwise, false.
- public static new bool LessThanOrEqual(PreciseNumber left, PreciseNumber right)
- {
- Ensure.NotNull(left);
- Ensure.NotNull(right);
- return left.CompareTo(right) <= 0;
- }
+ /// if the first number is less than or equal to the second; otherwise, .
+ public static bool LessThanOrEqual(PreciseNumber left, PreciseNumber right) =>
+ left.CompareTo(right) <= 0;
///
/// Determines whether two numbers are equal.
///
/// The first number.
/// The second number.
- /// true if the two numbers are equal; otherwise, false.
- public static new bool Equal(PreciseNumber left, PreciseNumber right)
- {
- Ensure.NotNull(left);
- Ensure.NotNull(right);
- return left.CompareTo(right) == 0;
- }
+ /// if the two numbers are equal; otherwise, .
+ public static bool Equal(PreciseNumber left, PreciseNumber right) =>
+ left.CompareTo(right) == 0;
///
/// Determines whether two numbers are not equal.
///
/// The first number.
/// The second number.
- /// true if the two numbers are not equal; otherwise, false.
- public static new bool NotEqual(PreciseNumber left, PreciseNumber right)
- {
- Ensure.NotNull(left);
- Ensure.NotNull(right);
- return left.CompareTo(right) != 0;
- }
+ /// if the two numbers are not equal; otherwise, .
+ public static bool NotEqual(PreciseNumber left, PreciseNumber right) =>
+ left.CompareTo(right) != 0;
///
- /// Compares two numbers and returns an integer that indicates their relative position in the sort order.
+ /// Compares two numbers at the lower of their significant digit counts.
///
/// The first number to compare.
/// The second number to compare.
@@ -269,13 +360,8 @@ public static SignificantNumber Negate(SignificantNumber value) =>
///
///
///
- ///
- /// Thrown when or is null.
- ///
public static int CompareTo(PreciseNumber left, PreciseNumber right)
{
- Ensure.NotNull(left);
- Ensure.NotNull(right);
int lowestSignificantDigits = LowestSignificantDigits(left, right);
return left.ReduceSignificance(lowestSignificantDigits).CompareTo(right.ReduceSignificance(lowestSignificantDigits));
}
@@ -336,19 +422,39 @@ public static int CompareTo(PreciseNumber left, PreciseNumber right)
public static SignificantNumber operator +(SignificantNumber left, SignificantNumber right) =>
Add(left, right);
- ///
+ ///
+ /// Determines whether a significant number and a are equal.
+ ///
+ /// The significant number.
+ /// The precise number.
+ /// if the two numbers are equal; otherwise, .
public static bool operator ==(SignificantNumber left, PreciseNumber right) =>
Equal(left, right);
- ///
+ ///
+ /// Determines whether a and a significant number are equal.
+ ///
+ /// The precise number.
+ /// The significant number.
+ /// if the two numbers are equal; otherwise, .
public static bool operator ==(PreciseNumber left, SignificantNumber right) =>
Equal(left, right);
- ///
+ ///
+ /// Determines whether a significant number and a are not equal.
+ ///
+ /// The significant number.
+ /// The precise number.
+ /// if the two numbers are not equal; otherwise, .
public static bool operator !=(SignificantNumber left, PreciseNumber right) =>
NotEqual(left, right);
- ///
+ ///
+ /// Determines whether a and a significant number are not equal.
+ ///
+ /// The precise number.
+ /// The significant number.
+ /// if the two numbers are not equal; otherwise, .
public static bool operator !=(PreciseNumber left, SignificantNumber right) =>
NotEqual(left, right);
@@ -439,7 +545,7 @@ internal static void AssertDoesImplementGenericInterface(Type type, Type generic
///
/// The type to check.
/// The generic interface to check for.
- /// true if the type implements the generic interface; otherwise, false.
+ /// if the type implements the generic interface; otherwise, .
/// Thrown when the specified type is not a valid generic interface.
internal static bool DoesImplementGenericInterface(Type type, Type genericInterface)
{
@@ -454,11 +560,9 @@ internal static bool DoesImplementGenericInterface(Type type, Type genericInterf
/// Returns the result of raising the current significant number to the specified power.
///
/// The power to raise the significant number to.
- /// A new instance of that is the result of raising the current instance to the specified power.
- public new SignificantNumber Pow(PreciseNumber power)
+ /// The current number raised to , rounded to the fewest significant digits of the two.
+ public SignificantNumber Pow(PreciseNumber power)
{
- Ensure.NotNull(power);
-
if (Equal(power, Zero))
{
return One;
@@ -475,7 +579,7 @@ internal static bool DoesImplementGenericInterface(Type type, Type genericInterf
int significantDigits = LowestSignificantDigits(this, power);
// Use logarithm and exponential to support decimal powers
- double logValue = Math.Log(Math.Abs(To()));
+ double logValue = Math.Log(Math.Abs(Value.To()));
return Math.Exp(logValue * power.To()).ToSignificantNumber(significantDigits);
}
@@ -483,18 +587,16 @@ internal static bool DoesImplementGenericInterface(Type type, Type genericInterf
/// Returns the result of raising e to the specified power.
///
/// The power to raise e to.
- /// A new instance of that is the result of raising e to the specified power.
- public static new SignificantNumber Exp(PreciseNumber power)
+ /// e raised to , rounded to the fewest significant digits of the two.
+ public static SignificantNumber Exp(PreciseNumber power)
{
- Ensure.NotNull(power);
-
if (Equal(power, Zero))
{
return One;
}
else if (Equal(power, One))
{
- return E.ToSignificantNumber();
+ return E;
}
int significantDigits = LowestSignificantDigits(E, power);
@@ -504,7 +606,7 @@ internal static bool DoesImplementGenericInterface(Type type, Type genericInterf
}
///
- /// Compares the current instance with another and returns an integer that indicates their relative position in the sort order.
+ /// Compares the current instance with another at the lower of their significant digit counts.
///
/// The to compare with the current instance.
///
@@ -521,152 +623,311 @@ internal static bool DoesImplementGenericInterface(Type type, Type genericInterf
///
///
///
- public int CompareTo(SignificantNumber? other) =>
- other is null ? 1 : CompareTo(this, other);
+ public int CompareTo(SignificantNumber other) =>
+ CompareTo(this, other);
+
+ ///
+ /// Compares the current instance with an object.
+ ///
+ /// The object to compare with the current instance.
+ ///
+ /// A signed integer that indicates the relative values of the current instance and . A
+ /// object sorts before every number.
+ ///
+ ///
+ /// A or is compared at the lower of the two significant
+ /// digit counts. Any other object is passed to .
+ ///
+ public int CompareTo(object? obj) =>
+ obj switch
+ {
+ null => 1,
+ SignificantNumber significantNumber => CompareTo(significantNumber),
+ PreciseNumber preciseNumber => CompareTo(this, preciseNumber),
+ _ => Value.CompareTo(obj),
+ };
+
+ ///
+ /// Compares the value of the current instance with another number, without applying significant figure rules.
+ ///
+ /// The type of the other number.
+ /// The number to compare with the current instance.
+ /// A signed integer that indicates the relative values of the current instance and .
+ public int CompareTo(TInput other)
+ where TInput : INumber =>
+ typeof(TInput) == typeof(SignificantNumber)
+ ? Value.CompareTo(((SignificantNumber)(object)other).Value)
+ : Value.CompareTo(other);
+
+ ///
+ /// Compares the value of the current instance with another number, without applying significant figure rules.
+ ///
+ /// The type of the other number.
+ /// The number to compare with the current instance.
+ /// A signed integer that indicates the relative values of the current instance and .
+ public int CompareTo(INumber? obj)
+ where TNumber : INumber =>
+ obj is SignificantNumber significantNumber
+ ? Value.CompareTo(significantNumber.Value)
+ : Value.CompareTo(obj);
///
/// Returns the absolute value of the specified .
///
/// The to compute the absolute value for.
- /// A new representing the absolute value of .
+ /// The absolute value of .
public static SignificantNumber Abs(SignificantNumber value) =>
- PreciseNumber.Abs(value).ToSignificantNumber();
+ new(PreciseNumber.Abs(value.Value));
+
+ ///
+ /// Returns the absolute value of the current instance.
+ ///
+ /// The absolute value of the number this instance holds.
+ public PreciseNumber Abs() => Value.Abs();
+
+ ///
+ /// Rounds the current instance to the specified number of decimal digits.
+ ///
+ /// The number of digits to keep after the decimal point.
+ /// The rounded value.
+ public PreciseNumber Round(int decimalDigits) => Value.Round(decimalDigits);
+
+ ///
+ /// Reduces the current instance to the specified number of significant digits.
+ ///
+ /// The number of significant digits to keep.
+ /// The reduced value.
+ public PreciseNumber ReduceSignificance(int significantDigits) => Value.ReduceSignificance(significantDigits);
+
+ ///
+ /// Clamps the current instance between a minimum and a maximum.
+ ///
+ /// The type of the bounds.
+ /// The lowest value to return.
+ /// The highest value to return.
+ /// The clamped value.
+ public PreciseNumber Clamp(TNumber min, TNumber max)
+ where TNumber : INumber =>
+ Value.Clamp(min, max);
+
+ ///
+ /// Returns the larger of two numbers, compared by value without applying significant figure rules.
+ ///
+ /// The first number.
+ /// The second number.
+ /// when it is greater than ; otherwise, .
+ public static SignificantNumber Max(SignificantNumber x, SignificantNumber y) =>
+ new(PreciseNumber.Max(x.Value, y.Value));
+
+ ///
+ /// Returns the smaller of two numbers, compared by value without applying significant figure rules.
+ ///
+ /// The first number.
+ /// The second number.
+ /// when it is less than ; otherwise, .
+ public static SignificantNumber Min(SignificantNumber x, SignificantNumber y) =>
+ new(PreciseNumber.Min(x.Value, y.Value));
+
+ ///
+ /// Clamps a number between a minimum and a maximum, compared by value without applying significant figure rules.
+ ///
+ /// The number to clamp.
+ /// The lowest value to return.
+ /// The highest value to return.
+ /// The clamped value.
+ public static SignificantNumber Clamp(SignificantNumber value, SignificantNumber min, SignificantNumber max) =>
+ new(PreciseNumber.Clamp(value.Value, min.Value, max.Value));
+
+ ///
+ /// Rounds a number to the specified number of decimal digits.
+ ///
+ /// The number to round.
+ /// The number of digits to keep after the decimal point.
+ /// The rounded value.
+ public static SignificantNumber Round(SignificantNumber value, int decimalDigits) =>
+ new(PreciseNumber.Round(value.Value, decimalDigits));
+
+ ///
+ /// Returns the square of the current instance.
+ ///
+ /// The value multiplied by itself.
+ public PreciseNumber Squared() => Value.Squared();
+
+ ///
+ /// Returns the cube of the current instance.
+ ///
+ /// The value multiplied by itself twice.
+ public PreciseNumber Cubed() => Value.Cubed();
+
+ ///
+ /// Converts the current instance to the specified numeric type.
+ ///
+ /// The type to convert to.
+ /// The converted value.
+ /// Thrown when the value is outside the range of .
+ public TOutput To()
+ where TOutput : INumber =>
+ Value.To();
+
+ ///
+ public override string ToString() => PreciseNumber.ToString(Value, null, null);
+
+ ///
+ /// Converts the current instance to a string using the specified format provider.
+ ///
+ /// An object that provides culture-specific formatting information.
+ /// The string representation of the number.
+ public string ToString(IFormatProvider? formatProvider) => PreciseNumber.ToString(Value, null, formatProvider);
+
+ ///
+ /// Converts the current instance to a string using the specified format.
+ ///
+ /// The format to use.
+ /// The string representation of the number.
+ public string ToString(string format) => PreciseNumber.ToString(Value, format, null);
+
+ ///
+ public string ToString(string? format, IFormatProvider? formatProvider) => PreciseNumber.ToString(Value, format, formatProvider);
+
+ ///
+ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) =>
+ Value.TryFormat(destination, out charsWritten, format, provider);
///
/// Determines whether the specified is canonical.
///
/// The to check.
- /// true if the is canonical; otherwise, false.
+ /// if the is canonical; otherwise, .
public static bool IsCanonical(SignificantNumber value) =>
- PreciseNumber.IsCanonical(value);
+ PreciseNumber.IsCanonical(value.Value);
///
/// Determines whether the specified is a complex number.
///
/// The to check.
- /// true if the is a complex number; otherwise, false.
+ /// if the is a complex number; otherwise, .
public static bool IsComplexNumber(SignificantNumber value) =>
- PreciseNumber.IsComplexNumber(value);
+ PreciseNumber.IsComplexNumber(value.Value);
///
/// Determines whether the specified is an even integer.
///
/// The to check.
- /// true if the is an even integer; otherwise, false.
+ /// if the is an even integer; otherwise, .
public static bool IsEvenInteger(SignificantNumber value) =>
- PreciseNumber.IsEvenInteger(value);
+ PreciseNumber.IsEvenInteger(value.Value);
///
/// Determines whether the specified is finite.
///
/// The to check.
- /// true if the is finite; otherwise, false.
+ /// if the is finite; otherwise, .
public static bool IsFinite(SignificantNumber value) =>
- PreciseNumber.IsFinite(value);
+ PreciseNumber.IsFinite(value.Value);
///
/// Determines whether the specified is an imaginary number.
///
/// The to check.
- /// true if the is an imaginary number; otherwise, false.
+ /// if the is an imaginary number; otherwise, .
public static bool IsImaginaryNumber(SignificantNumber value) =>
- PreciseNumber.IsImaginaryNumber(value);
+ PreciseNumber.IsImaginaryNumber(value.Value);
///
/// Determines whether the specified represents infinity.
///
/// The to check.
- /// true if the represents infinity; otherwise, false.
+ /// if the represents infinity; otherwise, .
public static bool IsInfinity(SignificantNumber value) =>
- PreciseNumber.IsInfinity(value);
+ PreciseNumber.IsInfinity(value.Value);
///
/// Determines whether the specified is an integer.
///
/// The to check.
- /// true if the is an integer; otherwise, false.
+ /// if the is an integer; otherwise, .
public static bool IsInteger(SignificantNumber value) =>
- PreciseNumber.IsInteger(value);
+ PreciseNumber.IsInteger(value.Value);
///
/// Determines whether the specified is not a number (NaN).
///
/// The to check.
- /// true if the is NaN; otherwise, false.
+ /// if the is NaN; otherwise, .
public static bool IsNaN(SignificantNumber value) =>
- PreciseNumber.IsNaN(value);
+ PreciseNumber.IsNaN(value.Value);
///
/// Determines whether the specified is negative.
///
/// The to check.
- /// true if the is negative; otherwise, false.
+ /// if the is negative; otherwise, .
public static bool IsNegative(SignificantNumber value) =>
- PreciseNumber.IsNegative(value);
+ PreciseNumber.IsNegative(value.Value);
///
/// Determines whether the specified represents negative infinity.
///
/// The to check.
- /// true if the represents negative infinity; otherwise, false.
+ /// if the represents negative infinity; otherwise, .
public static bool IsNegativeInfinity(SignificantNumber value) =>
- PreciseNumber.IsNegativeInfinity(value);
+ PreciseNumber.IsNegativeInfinity(value.Value);
///
/// Determines whether the specified is a normal number.
///
/// The to check.
- /// true if the is a normal number; otherwise, false.
+ /// if the is a normal number; otherwise, .
public static bool IsNormal(SignificantNumber value) =>
- PreciseNumber.IsNormal(value);
+ PreciseNumber.IsNormal(value.Value);
///
/// Determines whether the specified is an odd integer.
///
/// The to check.
- /// true if the is an odd integer; otherwise, false.
+ /// if the is an odd integer; otherwise, .
public static bool IsOddInteger(SignificantNumber value) =>
- PreciseNumber.IsOddInteger(value);
+ PreciseNumber.IsOddInteger(value.Value);
///
/// Determines whether the specified is positive.
///
/// The to check.
- /// true if the is positive; otherwise, false.
+ /// if the is positive; otherwise, .
public static bool IsPositive(SignificantNumber value) =>
- PreciseNumber.IsPositive(value);
+ PreciseNumber.IsPositive(value.Value);
///
/// Determines whether the specified represents positive infinity.
///
/// The to check.
- /// true if the represents positive infinity; otherwise, false.
+ /// if the represents positive infinity; otherwise, .
public static bool IsPositiveInfinity(SignificantNumber value) =>
- PreciseNumber.IsPositiveInfinity(value);
+ PreciseNumber.IsPositiveInfinity(value.Value);
///
/// Determines whether the specified is a real number.
///
/// The to check.
- /// true if the is a real number; otherwise, false.
+ /// if the is a real number; otherwise, .
public static bool IsRealNumber(SignificantNumber value) =>
- PreciseNumber.IsRealNumber(value);
+ PreciseNumber.IsRealNumber(value.Value);
///
/// Determines whether the specified is subnormal.
///
/// The to check.
- /// true if the is subnormal; otherwise, false.
+ /// if the is subnormal; otherwise, .
public static bool IsSubnormal(SignificantNumber value) =>
- PreciseNumber.IsSubnormal(value);
+ PreciseNumber.IsSubnormal(value.Value);
///
/// Determines whether the specified is zero.
///
/// The to check.
- /// true if the is zero; otherwise, false.
+ /// if the is zero; otherwise, .
public static bool IsZero(SignificantNumber value) =>
- PreciseNumber.IsZero(value);
+ PreciseNumber.IsZero(value.Value);
///
/// Returns the larger magnitude of two instances.
@@ -675,7 +936,7 @@ public static bool IsZero(SignificantNumber value) =>
/// The second to compare.
/// The with the larger magnitude.
public static SignificantNumber MaxMagnitude(SignificantNumber x, SignificantNumber y) =>
- PreciseNumber.MaxMagnitude(x, y).ToSignificantNumber();
+ new(PreciseNumber.MaxMagnitude(x.Value, y.Value));
///
/// Returns the larger magnitude of two instances, or the first one if both have the same magnitude.
@@ -684,7 +945,7 @@ public static SignificantNumber MaxMagnitude(SignificantNumber x, SignificantNum
/// The second to compare.
/// The with the larger magnitude, or if both have the same magnitude.
public static SignificantNumber MaxMagnitudeNumber(SignificantNumber x, SignificantNumber y) =>
- PreciseNumber.MaxMagnitudeNumber(x, y).ToSignificantNumber();
+ new(PreciseNumber.MaxMagnitudeNumber(x.Value, y.Value));
///
/// Returns the smaller magnitude of two instances.
@@ -693,7 +954,7 @@ public static SignificantNumber MaxMagnitudeNumber(SignificantNumber x, Signific
/// The second to compare.
/// The with the smaller magnitude.
public static SignificantNumber MinMagnitude(SignificantNumber x, SignificantNumber y) =>
- PreciseNumber.MinMagnitude(x, y).ToSignificantNumber();
+ new(PreciseNumber.MinMagnitude(x.Value, y.Value));
///
/// Returns the smaller magnitude of two instances, or the first one if both have the same magnitude.
@@ -702,7 +963,7 @@ public static SignificantNumber MinMagnitude(SignificantNumber x, SignificantNum
/// The second to compare.
/// The with the smaller magnitude, or if both have the same magnitude.
public static SignificantNumber MinMagnitudeNumber(SignificantNumber x, SignificantNumber y) =>
- PreciseNumber.MinMagnitudeNumber(x, y).ToSignificantNumber();
+ new(PreciseNumber.MinMagnitudeNumber(x.Value, y.Value));
///
/// Parses a span of characters into a using the specified style and format provider.
@@ -711,8 +972,8 @@ public static SignificantNumber MinMagnitudeNumber(SignificantNumber x, Signific
/// A bitwise combination of enumeration values that indicates the permitted format of .
/// An object that provides culture-specific formatting information.
/// A parsed from the input span.
- public static new SignificantNumber Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) =>
- PreciseNumber.Parse(s, style, provider).ToSignificantNumber();
+ public static SignificantNumber Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) =>
+ new(PreciseNumber.Parse(s, style, provider));
///
/// Parses a string into a using the specified style and format provider.
@@ -721,21 +982,106 @@ public static SignificantNumber MinMagnitudeNumber(SignificantNumber x, Signific
/// A bitwise combination of enumeration values that indicates the permitted format of .
/// An object that provides culture-specific formatting information.
/// A parsed from the input string.
- public static new SignificantNumber Parse(string s, NumberStyles style, IFormatProvider? provider) =>
- PreciseNumber.Parse(s, style, provider).ToSignificantNumber();
+ public static SignificantNumber Parse(string s, NumberStyles style, IFormatProvider? provider) =>
+ new(PreciseNumber.Parse(s, style, provider));
+
+ ///
+ /// Parses a span of characters into a using the specified format provider.
+ ///
+ /// The span of characters to parse.
+ /// An object that provides culture-specific formatting information.
+ /// A parsed from the input span.
+ /// Thrown when the input span is not in a valid format.
+ public static SignificantNumber Parse(ReadOnlySpan s, IFormatProvider? provider) =>
+ new(PreciseNumber.Parse(s, provider));
+
+ ///
+ /// Parses a string into a using the specified format provider.
+ ///
+ /// The string to parse.
+ /// An object that provides culture-specific formatting information.
+ /// A parsed from the input string.
+ /// Thrown when the input string is not in a valid format.
+ public static SignificantNumber Parse(string s, IFormatProvider? provider) =>
+ new(PreciseNumber.Parse(s, provider));
+
+ ///
+ /// Attempts to parse a span of characters into a using the specified style and format provider.
+ ///
+ /// The span of characters to parse.
+ /// A bitwise combination of enumeration values that indicates the permitted format of .
+ /// An object that provides culture-specific formatting information.
+ /// When this method returns, contains the parsed number if parsing succeeded, or zero if it failed.
+ /// if the parsing succeeded; otherwise, .
+ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, out SignificantNumber result)
+ {
+ bool parsed = PreciseNumber.TryParse(s, style, provider, out PreciseNumber preciseResult);
+ result = new(preciseResult);
+ return parsed;
+ }
+
+ ///
+ /// Attempts to parse a string into a using the specified style and format provider.
+ ///
+ /// The string to parse.
+ /// A bitwise combination of enumeration values that indicates the permitted format of .
+ /// An object that provides culture-specific formatting information.
+ /// When this method returns, contains the parsed number if parsing succeeded, or zero if it failed.
+ /// if the parsing succeeded; otherwise, .
+ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out SignificantNumber result)
+ {
+ bool parsed = PreciseNumber.TryParse(s, style, provider, out PreciseNumber preciseResult);
+ result = new(preciseResult);
+ return parsed;
+ }
+
+ ///
+ /// Attempts to parse a span of characters into a using the specified format provider.
+ ///
+ /// The span of characters to parse.
+ /// An object that provides culture-specific formatting information.
+ /// When this method returns, contains the parsed number if parsing succeeded, or zero if it failed.
+ /// if the parsing succeeded; otherwise, .
+ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out SignificantNumber result)
+ {
+ bool parsed = PreciseNumber.TryParse(s, provider, out PreciseNumber preciseResult);
+ result = new(preciseResult);
+ return parsed;
+ }
+
+ ///
+ /// Attempts to parse a string into a using the specified format provider.
+ ///
+ /// The string to parse.
+ /// An object that provides culture-specific formatting information.
+ /// When this method returns, contains the parsed number if parsing succeeded, or zero if it failed.
+ /// if the parsing succeeded; otherwise, .
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out SignificantNumber result)
+ {
+ bool parsed = PreciseNumber.TryParse(s, provider, out PreciseNumber preciseResult);
+ result = new(preciseResult);
+ return parsed;
+ }
///
/// Attempts to convert a value of type to a using a checked conversion.
///
/// The type of the value to convert.
/// The value to convert.
- /// When this method returns, contains the converted , if the conversion succeeded; otherwise, null.
- /// true if the conversion succeeded; otherwise, false.
- public static bool TryConvertFromChecked(TOther value, [NotNullWhen(true)] out SignificantNumber? result) where TOther : INumberBase
+ /// When this method returns, contains the converted number if the conversion succeeded, or zero if it failed.
+ /// if the conversion succeeded; if isn't supported.
+ /// Follows .
+ public static bool TryConvertFromChecked(TOther value, out SignificantNumber result)
+ where TOther : INumberBase
{
- bool tryResult = PreciseNumber.TryConvertFromChecked(value, out PreciseNumber? preciseResult);
- result = tryResult ? preciseResult.ToSignificantNumber() : null;
- return tryResult;
+ if (TryUnwrap(value, out result))
+ {
+ return true;
+ }
+
+ bool converted = PreciseNumber.TryConvertFromChecked(value, out PreciseNumber preciseResult);
+ result = new(preciseResult);
+ return converted;
}
///
@@ -743,13 +1089,20 @@ public static bool TryConvertFromChecked(TOther value, [NotNullWhen(true
///
/// The type of the value to convert.
/// The value to convert.
- /// When this method returns, contains the converted , if the conversion succeeded; otherwise, null.
- /// true if the conversion succeeded; otherwise, false.
- public static bool TryConvertFromSaturating(TOther value, [NotNullWhen(true)] out SignificantNumber? result) where TOther : INumberBase
+ /// When this method returns, contains the converted number if the conversion succeeded, or zero if it failed.
+ /// if the conversion succeeded; if isn't supported.
+ /// Follows .
+ public static bool TryConvertFromSaturating(TOther value, out SignificantNumber result)
+ where TOther : INumberBase
{
- bool tryResult = PreciseNumber.TryConvertFromSaturating(value, out PreciseNumber? preciseResult);
- result = tryResult ? preciseResult.ToSignificantNumber() : null;
- return tryResult;
+ if (TryUnwrap(value, out result))
+ {
+ return true;
+ }
+
+ bool converted = PreciseNumber.TryConvertFromSaturating(value, out PreciseNumber preciseResult);
+ result = new(preciseResult);
+ return converted;
}
///
@@ -757,13 +1110,20 @@ public static bool TryConvertFromSaturating(TOther value, [NotNullWhen(t
///
/// The type of the value to convert.
/// The value to convert.
- /// When this method returns, contains the converted , if the conversion succeeded; otherwise, null.
- /// true if the conversion succeeded; otherwise, false.
- public static bool TryConvertFromTruncating(TOther value, [NotNullWhen(true)] out SignificantNumber? result) where TOther : INumberBase
+ /// When this method returns, contains the converted number if the conversion succeeded, or zero if it failed.
+ /// if the conversion succeeded; if isn't supported.
+ /// Follows .
+ public static bool TryConvertFromTruncating(TOther value, out SignificantNumber result)
+ where TOther : INumberBase
{
- bool tryResult = PreciseNumber.TryConvertFromTruncating(value, out PreciseNumber? preciseResult);
- result = tryResult ? preciseResult.ToSignificantNumber() : null;
- return tryResult;
+ if (TryUnwrap(value, out result))
+ {
+ return true;
+ }
+
+ bool converted = PreciseNumber.TryConvertFromTruncating(value, out PreciseNumber preciseResult);
+ result = new(preciseResult);
+ return converted;
}
///
@@ -771,131 +1131,86 @@ public static bool TryConvertFromTruncating(TOther value, [NotNullWhen(t
///
/// The type to convert to.
/// The to convert.
- /// When this method returns, contains the converted value of type , if the conversion succeeded; otherwise, null.
- /// true if the conversion succeeded; otherwise, false.
- public static bool TryConvertToChecked(SignificantNumber value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase =>
- PreciseNumber.TryConvertToChecked(value, out result);
+ /// When this method returns, contains the converted value if the conversion succeeded.
+ /// if the conversion succeeded; if isn't supported.
+ /// Follows .
+ public static bool TryConvertToChecked(SignificantNumber value, [MaybeNullWhen(false)] out TOther result)
+ where TOther : INumberBase =>
+ TryWrap(value, out result) || PreciseNumber.TryConvertToChecked(value.Value, out result);
///
/// Attempts to convert a to a value of type using a saturating conversion.
///
/// The type to convert to.
/// The to convert.
- /// When this method returns, contains the converted value of type , if the conversion succeeded; otherwise, null.
- /// true if the conversion succeeded; otherwise, false.
- public static bool TryConvertToSaturating(SignificantNumber value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase =>
- PreciseNumber.TryConvertToSaturating(value, out result);
+ /// When this method returns, contains the converted value if the conversion succeeded.
+ /// if the conversion succeeded; if isn't supported.
+ /// Follows .
+ public static bool TryConvertToSaturating(SignificantNumber value, [MaybeNullWhen(false)] out TOther result)
+ where TOther : INumberBase =>
+ TryWrap(value, out result) || PreciseNumber.TryConvertToSaturating(value.Value, out result);
///
/// Attempts to convert a to a value of type using a truncating conversion.
///
/// The type to convert to.
/// The to convert.
- /// When this method returns, contains the converted value of type , if the conversion succeeded; otherwise, null.
- /// true if the conversion succeeded; otherwise, false.
- public static bool TryConvertToTruncating(SignificantNumber value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase =>
- PreciseNumber.TryConvertToTruncating(value, out result);
+ /// When this method returns, contains the converted value if the conversion succeeded.
+ /// if the conversion succeeded; if isn't supported.
+ /// Follows .
+ public static bool TryConvertToTruncating(SignificantNumber value, [MaybeNullWhen(false)] out TOther result)
+ where TOther : INumberBase =>
+ TryWrap(value, out result) || PreciseNumber.TryConvertToTruncating(value.Value, out result);
///
- /// Attempts to parse a span of characters into a using the specified style and format provider.
+ /// Converts a or without going through
+ /// 's conversions, which don't recognize .
///
- /// The span of characters to parse.
- /// A bitwise combination of enumeration values that indicates the permitted format of .
- /// An object that provides culture-specific formatting information.
- ///
- /// When this method returns, contains the parsed , if the parsing succeeded; otherwise, null.
- ///
- /// true if the parsing succeeded; otherwise, false.
- /// Thrown when is null.
- /// Thrown when is not in a valid format.
- public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [NotNullWhen(true)] out SignificantNumber? result)
+ /// The type of the value to convert.
+ /// The value to convert.
+ /// When this method returns, contains the converted number, or zero for any other type.
+ /// when is one of the two types.
+ private static bool TryUnwrap(TOther value, out SignificantNumber result)
{
- bool tryResult = PreciseNumber.TryParse(s, style, provider, out PreciseNumber? preciseResult);
- result = tryResult ? preciseResult?.ToSignificantNumber() : null;
- return tryResult;
- }
+ if (typeof(TOther) == typeof(SignificantNumber))
+ {
+ result = (SignificantNumber)(object)value!;
+ return true;
+ }
- ///
- /// Parses a span of characters into a using the specified format provider.
- ///
- /// The span of characters to parse.
- /// An object that provides culture-specific formatting information.
- /// A parsed from the input span.
- /// Thrown when the input span is not in a valid format.
- /// Thrown when the input span is null.
- public static new SignificantNumber Parse(ReadOnlySpan s, IFormatProvider? provider) =>
- PreciseNumber.Parse(s, provider).ToSignificantNumber();
+ if (typeof(TOther) == typeof(PreciseNumber))
+ {
+ result = new((PreciseNumber)(object)value!);
+ return true;
+ }
- ///
- /// Attempts to parse a span of characters into a using the specified format provider.
- ///
- /// The span of characters to parse.
- /// An object that provides culture-specific formatting information.
- /// When this method returns, contains the parsed , if the parsing succeeded; otherwise, null.
- /// true if the parsing succeeded; otherwise, false.
- public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [NotNullWhen(true)] out SignificantNumber? result)
- {
- bool tryResult = PreciseNumber.TryParse(s, provider, out PreciseNumber? preciseResult);
- result = tryResult ? preciseResult?.ToSignificantNumber() : null;
- return tryResult;
+ result = default;
+ return false;
}
///
- /// Parses a string into a using the specified format provider.
- ///
- /// The string to parse.
- /// An object that provides culture-specific formatting information.
- /// A parsed from the input string.
- /// Thrown when the input string is not in a valid format.
- /// Thrown when the input string is null.
- public static new SignificantNumber Parse(string s, IFormatProvider? provider) =>
- PreciseNumber.Parse(s, provider).ToSignificantNumber();
-
- ///
- /// Attempts to parse a string into a using the specified format provider.
+ /// Converts to a or without going through
+ /// 's conversions, which don't recognize .
///
- /// The string to parse.
- /// An object that provides culture-specific formatting information.
- /// When this method returns, contains the parsed , if the parsing succeeded; otherwise, null.
- /// true if the parsing succeeded; otherwise, false.
- public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [NotNullWhen(true)] out SignificantNumber? result)
+ /// The type to convert to.
+ /// The value to convert.
+ /// When this method returns, contains the converted value when is one of the two types.
+ /// when is one of the two types.
+ private static bool TryWrap(SignificantNumber value, [MaybeNullWhen(false)] out TOther result)
{
- bool tryResult = PreciseNumber.TryParse(s, provider, out PreciseNumber? preciseResult);
- result = tryResult ? preciseResult?.ToSignificantNumber() : null;
- return tryResult;
- }
+ if (typeof(TOther) == typeof(PreciseNumber))
+ {
+ result = (TOther)(object)value.Value;
+ return true;
+ }
- ///
- /// Attempts to parse a string into a using the specified style and format provider.
- ///
- /// The string to parse.
- /// A bitwise combination of enumeration values that indicates the permitted format of .
- /// An object that provides culture-specific formatting information.
- ///
- /// When this method returns, contains the parsed , if the parsing succeeded; otherwise, .
- ///
- /// true if the parsing succeeded; otherwise, false.
- /// Thrown when is null.
- /// Thrown when is not in a valid format.
- public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out SignificantNumber result)
- {
- bool tryResult = TryParse(s, provider, out PreciseNumber? preciseResult);
- result = tryResult ? preciseResult?.ToSignificantNumber() : Zero;
- return tryResult;
- }
+ if (typeof(TOther) == typeof(SignificantNumber))
+ {
+ result = (TOther)(object)value;
+ return true;
+ }
-#if NET8_0_OR_GREATER
- ///
- /// Provides a specific implementation of the IUtf8SpanFormattable.TryFormat method
- /// to resolve ambiguity.
- /// The destination span for the UTF-8 formatted output.
- /// The number of bytes written to the destination span.
- /// The format specifier.
- /// The format provider.
- /// true if the formatting was successful; otherwise, false.
- ///
- bool IUtf8SpanFormattable.TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) =>
- // Explicitly delegate to the base implementation to resolve ambiguity.
- ((IUtf8SpanFormattable)this).TryFormat(utf8Destination, out bytesWritten, format, provider);
-#endif
+ result = default;
+ return false;
+ }
}
diff --git a/SignificantNumber/SignificantNumberExtensions.cs b/SignificantNumber/SignificantNumberExtensions.cs
index 4bba281..ec635c1 100644
--- a/SignificantNumber/SignificantNumberExtensions.cs
+++ b/SignificantNumber/SignificantNumberExtensions.cs
@@ -18,8 +18,8 @@ public static class SignificantNumberExtensions
/// The input number to convert.
/// The converted .
///
- /// If the input number is already a , it is returned as-is.
- /// Otherwise, the input is converted to a and then to a .
+ /// If the input number is already a , it is returned unchanged.
+ /// Otherwise, the input is converted to a , which the result holds.
///
public static SignificantNumber ToSignificantNumber(this TInput input)
where TInput : INumber
@@ -29,18 +29,9 @@ public static SignificantNumber ToSignificantNumber(this TInput input)
ArgumentNullException.ThrowIfNull(input);
#pragma warning restore KTSU0003
- Type inputType = input.GetType();
- Type significantNumberType = typeof(SignificantNumber);
- bool isSignificantNumber = inputType == significantNumberType || inputType.IsSubclassOf(significantNumberType);
-
- if (isSignificantNumber)
- {
- return (SignificantNumber)(object)input;
- }
-
- PreciseNumber preciseNumber = input.ToPreciseNumber();
-
- return SignificantNumber.CreateFromComponents(preciseNumber.Exponent, preciseNumber.Significand);
+ return typeof(TInput) == typeof(SignificantNumber)
+ ? (SignificantNumber)(object)input
+ : new SignificantNumber(ToPreciseNumberValue(input));
}
///
@@ -61,10 +52,25 @@ public static SignificantNumber ToSignificantNumber(this TInput input, i
throw new ArgumentOutOfRangeException(nameof(significantDigits), "Significant digits must be greater than zero.");
}
- PreciseNumber preciseNumber = input
- .ToPreciseNumber()
+ PreciseNumber preciseNumber = ToPreciseNumberValue(input)
.ReduceSignificance(significantDigits);
- return SignificantNumber.CreateFromComponents(preciseNumber.Exponent, preciseNumber.Significand);
+ return new SignificantNumber(preciseNumber);
}
+
+ ///
+ /// Converts a number to a , unwrapping a directly.
+ ///
+ /// The type of the input number.
+ /// The input number to convert.
+ /// The input as a .
+ ///
+ /// recognizes only built-in numeric types and
+ /// itself, so a has to be unwrapped here.
+ ///
+ private static PreciseNumber ToPreciseNumberValue(TInput input)
+ where TInput : INumber =>
+ typeof(TInput) == typeof(SignificantNumber)
+ ? ((SignificantNumber)(object)input).Value
+ : input.ToPreciseNumber();
}
diff --git a/docs/migration-guide-2.0.md b/docs/migration-guide-2.0.md
new file mode 100644
index 0000000..0d3941a
--- /dev/null
+++ b/docs/migration-guide-2.0.md
@@ -0,0 +1,91 @@
+# Migrating from SignificantNumber 1.x to 2.0
+
+SignificantNumber 2.0 makes `SignificantNumber` a value type that holds a `PreciseNumber`, and requires `ktsu.PreciseNumber` 2.0. The significant figure rules are unchanged. Addition and subtraction round to the fewest decimal places, and multiplication, division, and modulus round to the fewest significant digits. What changes is how the type relates to `PreciseNumber`, `null`, and generic math.
+
+## Quick checklist
+
+1. Update `ktsu.PreciseNumber` to 2.0 alongside this package, and read its [migration guide](https://github.com/ktsu-dev/PreciseNumber/blob/main/docs/migration-guide-2.0.md).
+2. Remove `null` checks and `null` assignments for `SignificantNumber` values.
+3. Replace code that treats a `SignificantNumber` as a `PreciseNumber` object, such as `is PreciseNumber` checks or `ReferenceEquals`.
+4. Add an explicit cast where a `PreciseNumber` is assigned to a `SignificantNumber`.
+5. Check the `TryParse` failure path, which now yields zero instead of `null`.
+
+## Why
+
+`PreciseNumber` 2.0 is a `readonly record struct`, and a struct can't be inherited, so `SignificantNumber` can no longer derive from it. Holding one instead keeps every result inline in its variable, field, or array element, the same benefit `PreciseNumber` 2.0 gets. It also lets `SignificantNumber` satisfy `where T : struct, INumber` and take part in generic math through `CreateChecked`, `CreateSaturating`, and `CreateTruncating`.
+
+## 1. SignificantNumber is a value type
+
+`default(SignificantNumber)` is zero, and equals `SignificantNumber.Zero`. An uninitialized field or array element is a valid number.
+
+```csharp
+// Was:
+SignificantNumber? total = null;
+if (total is null) { total = SignificantNumber.Zero; }
+
+// Now:
+SignificantNumber total = default; // zero
+```
+
+A `SignificantNumber?` still compiles, but it's now a `Nullable`.
+
+## 2. It holds a PreciseNumber instead of deriving from one
+
+The held value is the new `Value` property. A `SignificantNumber` converts to a `PreciseNumber` implicitly, so passing one where a `PreciseNumber` is expected keeps compiling. The other direction needs an explicit cast, because it chooses the significant figure rules.
+
+```csharp
+PreciseNumber precise = 12.5.ToPreciseNumber();
+
+// Was:
+SignificantNumber significant = precise.ToSignificantNumber();
+PreciseNumber back = significant;
+
+// Now, either of these:
+SignificantNumber significant = (SignificantNumber)precise;
+SignificantNumber alsoSignificant = precise.ToSignificantNumber();
+PreciseNumber back = significant; // still implicit
+```
+
+These members used to be inherited and are now declared on `SignificantNumber`, with the same names and return types: `Exponent`, `Significand`, `SignificantDigits`, `Abs()`, `Round(int)`, `ReduceSignificance(int)`, `Clamp`, `Squared()`, `Cubed()`, `To()`, the `ToString` overloads, and `TryFormat`. `NegativeOne`, `E`, `Pi`, and `Tau` are now typed `SignificantNumber`, and so are the static `Max`, `Min`, `Clamp`, and `Round`.
+
+Removed:
+
+| Removed | Replacement |
+|---|---|
+| The protected constructors taking an exponent and a significand | `SignificantNumber.Parse` with scientific notation, or `(SignificantNumber)precise` |
+| Inherited static members that weren't redeclared, such as `PreciseNumber.MakeCommonized` | Call them on `PreciseNumber` with `.Value` |
+| `ReferenceEquals` identity, and `AreSame` in tests | Compare values with `==` or `Equals` |
+
+`ToSignificantNumber()` on a value that's already a `SignificantNumber` still returns the same value, but as a copy rather than the same object.
+
+## 3. Signatures that accepted null
+
+| Member | 1.x | 2.0 |
+|---|---|---|
+| `CompareTo` | `CompareTo(SignificantNumber? other)` returned `1` for `null` | `CompareTo(SignificantNumber other)` |
+| `CompareTo(object?)` | Inherited from `PreciseNumber` | Declared here. Returns `1` for `null`, and compares a boxed `SignificantNumber` or `PreciseNumber` with the significant figure rules |
+| `TryParse` (four overloads) | `out SignificantNumber? result`, `null` on failure | `out SignificantNumber result`, zero on failure |
+| `TryConvertFrom*` | `out SignificantNumber? result`, and always failed because `PreciseNumber` 1.x threw | `out SignificantNumber result`, converting as `PreciseNumber` 2.0 does |
+
+```csharp
+// Was:
+if (SignificantNumber.TryParse(text, CultureInfo.InvariantCulture, out SignificantNumber? parsed)) { Use(parsed); }
+
+// Now:
+if (SignificantNumber.TryParse(text, CultureInfo.InvariantCulture, out SignificantNumber parsed)) { Use(parsed); }
+```
+
+## 4. Generic math conversions work
+
+The six `TryConvertFrom*` and `TryConvertTo*` members delegate to `PreciseNumber` 2.0, so they cover every built-in numeric type and `BigInteger` with its checked, saturating, and truncating behavior. They also convert to and from `PreciseNumber` itself.
+
+```csharp
+static T ToMeters(T feet) where T : INumber => feet * T.CreateChecked(0.3048);
+
+SignificantNumber meters = ToMeters(10.ToSignificantNumber()); // 3.048, rounded by the multiplication rule
+double asDouble = double.CreateChecked(meters);
+```
+
+## 5. UTF-8 formatting no longer recurses
+
+1.x declared `IUtf8SpanFormattable.TryFormat` by casting itself to the interface and calling the same method, which recursed until the stack overflowed. 2.0 uses the implementation `INumberBase` provides, which formats through `TryFormat` on characters.