ktsu.SignificantNumber
3.0.0
Prefix Reserved
dotnet add package ktsu.SignificantNumber --version 3.0.0
NuGet\Install-Package ktsu.SignificantNumber -Version 3.0.0
<PackageReference Include="ktsu.SignificantNumber" Version="3.0.0" />
<PackageVersion Include="ktsu.SignificantNumber" Version="3.0.0" />
<PackageReference Include="ktsu.SignificantNumber" />
paket add ktsu.SignificantNumber --version 3.0.0
#r "nuget: ktsu.SignificantNumber, 3.0.0"
#:package ktsu.SignificantNumber@3.0.0
#addin nuget:?package=ktsu.SignificantNumber&version=3.0.0
#tool nuget:?package=ktsu.SignificantNumber&version=3.0.0
ktsu.SignificantNumber
SignificantNumber is a numeric value type whose arithmetic follows the rules for significant figures. It holds a ktsu.PreciseNumber and rounds every result to the precision its operands justify.
Features
- Addition, subtraction, and modulus round to the fewest decimal places among the operands, and multiplication and division 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 structwhosedefaultis zero, holding aPreciseNumberthat it converts to implicitly - Implements
INumber<SignificantNumber>, includingCreateChecked,CreateSaturating, andCreateTruncatingfor every built-in numeric type,BigInteger, andPreciseNumber
Upgrading from 1.x? See the 2.0 migration guide.
Table of contents
Performance
<picture> <source media="(prefers-color-scheme: dark)" srcset="docs/benchmarks/performance-dark.svg"> <img alt="Allocated bytes per operation, and time relative to a fixed reference workload, for each SignificantNumber release" src="docs/benchmarks/performance.svg"> </picture>
Every release measures a fixed set of benchmarks and adds a point to the chart; the numbers behind
it are in docs/benchmarks/history.json, and the suite is
SignificantNumber.Benchmarks.
Read the two halves differently. Allocation is exact — the same code allocates the same bytes on any machine, so a step in the top row is always a real change. Time is measured on shared CI runners, where the host a job happens to land on varies more than most releases do, so each time is divided by a reference workload measured in the same job. That cancels most of the difference between machines; what is left is indicative rather than precise.
The step at 2.0 is the type becoming a readonly record struct over PreciseNumber instead of
deriving from it. A 200-digit addition went from 54,688 bytes and 128 μs to 112 bytes and 340 ns.
Installation
Install the package with the .NET CLI:
dotnet add package ktsu.SignificantNumber
Or add the package reference directly in your project file:
<PackageReference Include="ktsu.SignificantNumber" Version="x.x.x" />
Usage
Creating a SignificantNumber
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
ToSignificantNumber converts through INumber<T>. These types are supported:
Integer types:
intlongshortsbyteuintulongushortbyteBigInteger
Floating point types:
doublefloatHalfdecimal
ktsu types:
PreciseNumberSignificantNumber
Examples
using System.Numerics;
using ktsu.PreciseNumber;
using ktsu.SignificantNumber;
// Integer types
int intValue = 12345;
SignificantNumber significantNumberFromInt = intValue.ToSignificantNumber();
BigInteger bigIntValue = new BigInteger(9876543210);
SignificantNumber significantNumberFromBigInt = bigIntValue.ToSignificantNumber();
// Floating point types
double doubleValue = 123.45;
SignificantNumber significantNumberFromDouble = doubleValue.ToSignificantNumber();
Half halfValue = (Half)123.45;
SignificantNumber significantNumberFromHalf = halfValue.ToSignificantNumber();
float floatValue = 123.45f;
SignificantNumber significantNumberFromFloat = floatValue.ToSignificantNumber();
decimal decimalValue = 123.45m;
SignificantNumber significantNumberFromDecimal = decimalValue.ToSignificantNumber();
// A PreciseNumber, with an explicit cast because it opts into the significant figure rules
PreciseNumber precise = 12.5.ToPreciseNumber();
SignificantNumber significantNumberFromPrecise = (SignificantNumber)precise;
Arithmetic operations
SignificantNumber result1 = number1 + number2;
SignificantNumber result2 = number1 - number2;
SignificantNumber result3 = number1 * number2;
SignificantNumber result4 = number1 / number2;
// Square and cube operations, which return the unrounded PreciseNumber
PreciseNumber squared = number1.Squared();
PreciseNumber cubed = number1.Cubed();
// Power operation
SignificantNumber powerResult = number1.Pow(3.ToPreciseNumber());
The operators also accept a PreciseNumber on either side, and the result is a SignificantNumber.
Comparison operations
bool isEqual = number1 == number2;
bool isGreater = number1 > number2;
bool isLessOrEqual = number1 <= number2;
Ordering operators, Equals and CompareTo compare values exactly, so Array.Sort, OrderBy, SortedSet and Comparer<SignificantNumber>.Default all see one consistent order.
To ask whether two measurements agree to the precision they share, use SignificantNumber.CompareAtSignificance(left, right). It compares both numbers at the lower of their significant digit counts, so 1.23 and 1.2 compare equal. That comparison is not transitive (1.2 also equals 1.17, but 1.23 does not), so don't sort with it.
int exact = number1.CompareTo(number2);
int atSharedPrecision = SignificantNumber.CompareAtSignificance(number1, number2);
Formatting and parsing
Format a SignificantNumber as a string:
string formatted = number1.ToString("G", CultureInfo.InvariantCulture);
Console.WriteLine(formatted); // Outputs the formatted number
Parse one from text, including scientific notation:
SignificantNumber parsed = SignificantNumber.Parse("1.23E4", NumberStyles.Float, CultureInfo.InvariantCulture);
if (SignificantNumber.TryParse("123.45", CultureInfo.InvariantCulture, out SignificantNumber result))
{
Console.WriteLine(result);
}
TryParse yields zero when parsing fails.
Extension methods
ToSignificantNumber
Converts a supported numeric type to a SignificantNumber. An overload takes the number of significant digits to keep.
Usage
public static SignificantNumber ToSignificantNumber<TInput>(this TInput input)
where TInput : INumber<TInput>
public static SignificantNumber ToSignificantNumber<TInput>(this TInput input, int significantDigits)
where TInput : INumber<TInput>
Parameters
input: The number to convert.significantDigits: The number of significant digits to keep. It must be greater than zero.
Returns
SignificantNumber: The converted number.
Example
double floatingPointValue = 123.45;
SignificantNumber significantNumberFromFloat = floatingPointValue.ToSignificantNumber();
int integerValue = 12345;
SignificantNumber significantNumberFromInt = integerValue.ToSignificantNumber();
SignificantNumber result = significantNumberFromFloat + significantNumberFromInt;
// result = 12468.45
Conversion
To<TOutput>
Converts a SignificantNumber to the specified numeric type.
Usage
public TOutput To<TOutput>()
where TOutput : INumber<TOutput>
Returns
TOutput: The converted value of theSignificantNumber.
Example
SignificantNumber significantNumber = SignificantNumber.Parse("12345E3", NumberStyles.Float, CultureInfo.InvariantCulture);
double result = significantNumber.To<double>();
Console.WriteLine(result); // Outputs 12345000
Generic code converts the same way through CreateChecked, CreateSaturating, and CreateTruncating:
static T ToMeters<T>(T feet) where T : INumber<T> => feet * T.CreateChecked(0.3048);
SignificantNumber meters = ToMeters(10.ToSignificantNumber());
double asDouble = double.CreateChecked(meters);
A SignificantNumber converts to a PreciseNumber implicitly, and Value returns the PreciseNumber it holds.
Precision
Significand and exponent
A SignificantNumber holds a PreciseNumber, which stores two components:
- Significand: The significant digits of the number, stored as a
BigInteger. - Exponent: The power of ten that scales the significand.
Significand, Exponent, and SignificantDigits are available directly on SignificantNumber.
Precision handling
- Floating point input: A
floatkeeps up to 8 significant digits, and adoubleup 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:
Roundrounds to a number of decimal digits, andReduceSignificanceto a number of significant digits.
Example of precision
Consider the number 123.456000:
- As a
SignificantNumber, it's stored as123456e-3after removing the trailing zeros and adjusting the exponent. - Adding
1.2to it rounds the sum to one decimal place, giving124.7, because1.2has the fewest decimal places.
API reference
Properties
PreciseNumber Value- Gets thePreciseNumberthe number holds.int Exponent,BigInteger Significand, andint SignificantDigits- Get the components of the held value.static SignificantNumber NegativeOne,One, andZero- Get -1, 1, and 0.Zerois alsodefault.static SignificantNumber E,Pi, andTau- Get the mathematical constants.static int Radix- Gets the radix, or base, for the 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 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 value of the current instance with another significant number exactly.int CompareTo<TInput>(TInput other) where TInput : INumber<TInput>- 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>(TNumber min, TNumber max) where TNumber : INumber<TNumber>- Clamps the current instance between the minimum and maximum values.SignificantNumber Pow(PreciseNumber power)- Raises the current instance to a power.PreciseNumber ToPreciseNumber()- Returns thePreciseNumberthe 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<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)- Attempts to format the current instance into the provided span.TOutput To<TOutput>() where TOutput : INumber<TOutput>- Converts the current significant number to the specified numeric type.
Static methods
static SignificantNumber FromPreciseNumber(PreciseNumber value)- Creates a significant number that holds aPreciseNumber.static SignificantNumber Add,Subtract,Multiply,Divide, andMod(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), andClamp(SignificantNumber value, SignificantNumber min, SignificantNumber max)- Compare by value.static int CompareAtSignificance(PreciseNumber left, PreciseNumber right)- Compares two numbers at the lower of their significant digit counts. Not a total order, so not for sorting.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 aSignificantNumber.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.static bool IsEvenInteger(SignificantNumber value)- Determines whether the specified value is an even integer.static bool IsFinite(SignificantNumber value)- Determines whether the specified value is finite.static bool IsImaginaryNumber(SignificantNumber value)- Determines whether the specified value is an imaginary number.static bool IsInfinity(SignificantNumber value)- Determines whether the specified value is infinite.static bool IsInteger(SignificantNumber value)- Determines whether the specified value is an integer.static bool IsNaN(SignificantNumber value)- Determines whether the specified value is NaN.static bool IsNegative(SignificantNumber value)- Determines whether the specified value is negative.static bool IsNegativeInfinity(SignificantNumber value)- Determines whether the specified value is negative infinity.static bool IsNormal(SignificantNumber value)- Determines whether the specified value is normal.static bool IsOddInteger(SignificantNumber value)- Determines whether the specified value is an odd integer.static bool IsPositive(SignificantNumber value)- Determines whether the specified value is positive.static bool IsPositiveInfinity(SignificantNumber value)- Determines whether the specified value is positive infinity.static bool IsRealNumber(SignificantNumber value)- Determines whether the specified value is a real number.static bool IsSubnormal(SignificantNumber value)- Determines whether the specified value is subnormal.static bool IsZero(SignificantNumber value)- Determines whether the specified value is zero.static SignificantNumber MaxMagnitude(SignificantNumber x, SignificantNumber y)- Returns the larger of the magnitudes of two significant numbers.static SignificantNumber MaxMagnitudeNumber(SignificantNumber x, SignificantNumber y)- Returns the larger of the magnitudes of two significant numbers.static SignificantNumber MinMagnitude(SignificantNumber x, SignificantNumber y)- Returns the smaller of the magnitudes of two significant numbers.static SignificantNumber MinMagnitudeNumber(SignificantNumber x, SignificantNumber y)- Returns the smaller of the magnitudes of two significant numbers.
Operators
static implicit operator PreciseNumber(SignificantNumber value)- Converts to the heldPreciseNumber.static explicit operator SignificantNumber(PreciseNumber value)- Creates a significant number that holds aPreciseNumber.static SignificantNumber operator -(SignificantNumber value)- Negates a significant number.static SignificantNumber operator +,-,*,/, and%- Apply the significant figure rules. Each also accepts aPreciseNumberon either side.static SignificantNumber operator +(SignificantNumber value)- Returns the unary plus of a significant number.static SignificantNumber operator ++and--- Increment and decrement by one.static bool operator ==and!=- Determine whether two numbers are equal, including against aPreciseNumber.static bool operator >,<,>=, and<=- Compare two numbers, including against aPreciseNumber.
Contributing
Contributions are welcome. Submit a pull request or open an issue.
License
This project is licensed under the MIT License. See the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- ktsu.PreciseNumber (>= 2.6.6)
-
net7.0
- ktsu.PreciseNumber (>= 2.6.6)
-
net8.0
- ktsu.PreciseNumber (>= 2.6.6)
-
net9.0
- ktsu.PreciseNumber (>= 2.6.6)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ktsu.SignificantNumber:
| Package | Downloads |
|---|---|
|
ktsu.PhysicalQuantity
Physical Quantity |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 49 | 10/5/2026 |
| 2.0.16-pre.2 | 46 | 10/5/2026 |
| 2.0.16-pre.1 | 54 | 9/30/2026 |
| 2.0.15 | 102 | 9/29/2026 |
| 2.0.14 | 91 | 9/29/2026 |
| 2.0.14-pre.1 | 58 | 9/28/2026 |
| 2.0.13 | 106 | 9/28/2026 |
| 2.0.12 | 124 | 9/26/2026 |
| 2.0.11 | 162 | 9/26/2026 |
| 2.0.10 | 103 | 9/25/2026 |
| 2.0.9 | 90 | 9/24/2026 |
| 2.0.8 | 104 | 9/23/2026 |
| 2.0.7 | 97 | 9/22/2026 |
| 2.0.6 | 104 | 9/21/2026 |
| 2.0.5 | 108 | 9/18/2026 |
| 2.0.4 | 102 | 9/17/2026 |
| 2.0.3 | 105 | 9/16/2026 |
| 2.0.2 | 109 | 9/16/2026 |
| 2.0.1 | 106 | 9/16/2026 |
| 2.0.0 | 106 | 9/15/2026 |
## v3.0.0 (major)
Changes since v2.0.0:
- Use Assert.AreSequenceEqual and Assert.Contains in the new comparison tests ([@Claude](https://github.com/Claude))
- Compare exact values in IComparable so sorting is a total order [major] ([@Claude](https://github.com/Claude))
- Round sums and differences to tens and hundreds when an operand is that coarse [patch] ([@Claude](https://github.com/Claude))
- Move CI onto the shared ci-shared.yml pipeline ([@Claude](https://github.com/Claude))
- Move Pow's logarithm path into a helper to keep its complexity within bounds ([@Claude](https://github.com/Claude))
- Cover an exact operand on either side of a multiplication or division ([@Claude](https://github.com/Claude))
- Count the result's integer digits without a floating-point comparison ([@Claude](https://github.com/Claude))
- Compute Pow and Exp beyond the range of a double [patch] ([@Claude](https://github.com/Claude))
- Keep the remainder of % smaller than the divisor [patch] ([@Claude](https://github.com/Claude))
- Stop multiplying by zero from throwing when the zero is on the right [patch] ([@Claude](https://github.com/Claude))
- Keep the base's precision when Pow or Exp takes an integer exponent [patch] ([@Claude](https://github.com/Claude))
- Keep the sign of a negative base in Pow, and reject undefined powers [patch] ([@Claude](https://github.com/Claude))
- Seed the abstraction-cost pair across every release [patch] ([@Claude](https://github.com/Claude))
- Measure this type against a bare double, and chart it per release [patch] ([@Claude](https://github.com/Claude))
- Seed the chart with every release the suite can measure [patch] ([@Claude](https://github.com/Claude))
- Chart performance per release, and show it in the README [patch] ([@Claude](https://github.com/Claude))
- Gate Dependabot auto-merge on CI actually being green ([@Claude](https://github.com/Claude))