Fractions.Json
7.7.0
See the version list below for details.
dotnet add package Fractions.Json --version 7.7.0
NuGet\Install-Package Fractions.Json -Version 7.7.0
<PackageReference Include="Fractions.Json" Version="7.7.0" />
paket add Fractions.Json --version 7.7.0
#r "nuget: Fractions.Json, 7.7.0"
// Install Fractions.Json as a Cake Addin #addin nuget:?package=Fractions.Json&version=7.7.0 // Install Fractions.Json as a Cake Tool #tool nuget:?package=Fractions.Json&version=7.7.0
Fractions
Introduction
This package contains a data type to calculate with rational numbers. It supports basic mathematic operators such as:
- addition
- subtraction
- multiplication
- division
- remainder
- ..
The fraction data type implements operator overloads and implicit type conversion for convenience.
Creation
You can implicitly cast int
, uint
, long
, ulong
, decimal
or BigInteger
to Fraction
:
Fraction a = 3; // int
Fraction b = 4L; // long
Fraction c = 3.3m; // decimal
Fraction d = new BigInteger(3);
// ..
You can explicitly cast double
to Fraction
, however doing so directly has some important caveats that you should be aware of:
var a = (Fraction)3.3; // returns {3715469692580659/1125899906842624} which is 3.299999999999999822364316059975
You can explicitly cast from Fraction
to any supported data type (int
, uint
, long
, ulong
, BigInteger
, decimal
, double
). However, be aware that an OverflowException
will be thrown, if the target data type's boundary values are exceeded.
Constructors
There a three types of constructors available:
new Fraction (<value>)
forint
,uint
,long
,ulong
,BigInteger
,decimal
anddouble
(without rounding).new Fraction (<numerator>, <denominator>)
usingBigInteger
for numerator and denominator.new Fraction (<numerator>, <denominator>, <reduce>)
usingBigInteger
for numerator and denominator +bool
to indicate if the resulting fraction shall be normalized (reduced).
Static creation methods
Fraction.FromDecimal(decimal)
Fraction.FromDouble(double)
Fraction.FromDoubleRounded(double)
Fraction.FromDoubleRounded(double, int)
(using a maximum number of significant digits)Fraction.FromString(string)
(using current culture)Fraction.FromString(string, IFormatProvider)
Fraction.FromString(string, NumberStyles, IFormatProvider)
Fraction.TryParse(string, out Fraction)
(using current culture)Fraction.TryParse(string, NumberStyles, IFormatProvider, out Fraction)
Fraction.TryParse(ReadOnlySpan<char>, NumberStyles, IFormatProvider, bool, out Fraction)
Creation from double
without rounding
The double
data type in C# uses a binary floating-point representation, which complies with the IEC 60559:1989 (IEEE 754) standard for binary floating-point arithmetic. This representation can't accurately represent all decimal fractions. For example, the decimal fraction 0.1 is represented as the repeating binary fraction .0001100110011.... As a result, a double
value can only provide an approximate representation of the decimal number it's intended to represent.
Large values in the numerator / denominator
When you convert a double
to a Fraction
using the Fraction.FromDouble
method, the resulting fraction is an exact representation of the double
value, not the decimal number that the double
is intended to approximate. This is why you can end up with large numerators and denominators.
var value = Fraction.FromDouble(0.1);
Console.WriteLine(value); // Ouputs "3602879701896397/36028797018963968" which is 0.10000000000000000555111512312578
The output fraction is an exact representation of the double
value 0.1, which is actually slightly more than 0.1 due to the limitations of binary floating-point representation.
Comparing fractions created with double precision
Using a Fraction
that was created using this method for strict Equality/Comparison should be avoided. For example:
var fraction1 = Fraction.FromDouble(0.1);
var fraction2 = new Fraction(1, 10);
Console.WriteLine(fraction1 == fraction2); // Outputs "False"
If you need to compare a Fraction
created from double
with others fractions you should either do so by using a tolerance or consider constructing the Fraction
by specifying the maximum number of significant digits.
Possible rounding errors near the limits of the double precision
When a double
value is very close to the limits of its precision, Fraction.FromDouble(value).ToDouble() == value
might not hold true. This is because the numerator and denominator of the Fraction
are both very large numbers. When these numbers are converted to double
for the division operation in the Fraction.ToDouble
method, they can exceed the precision limit of the double
type, resulting in a loss of precision.
var value = Fraction.FromDouble(double.Epsilon);
Console.WriteLine(value.ToDouble() == double.Epsilon); // Outputs "False"
For more detailed information about the behavior of the Fraction.FromDouble
method and the limitations of the double
type, please refer to the XML documentation comments in the source code.
Creation from double
with maximum number of significant digits
The Fraction.FromDoubleRounded(double, int)
method allows you to specify the maximum number of significant digits when converting a double
to a Fraction
. This can help to avoid large numerators and denominators, and can make the Fraction
suitable for comparison operations.
var value = Fraction.FromDoubleRounded(0.1, 15); // Returns a fraction with a maximum of 15 significant digits
Console.WriteLine(value); // Outputs "1/10"
If you care only about minimizing the size of the numerator/denominator, and do not expect to use the fraction in any strict comparison operations, then creating an approximated fraction using the Fraction.FromDoubleRounded(double)
overload should offer the best performance.
Creation from double
with rounding to a close approximation
You can use the Fraction.FromDoubleRounded(double)
method to avoid big numbers in numerator and denominator. Example:
var value = Fraction.FromDoubleRounded(0.1);
Console.WriteLine(value); // Outputs "1/10"
However, please note that while rounding to an approximate value would mostly produce the expected result, it shouldn't be relied on for any strict comparison operations. Consider this example:
var doubleValue = 1055.05585262;
var roundedValue = Fraction.FromDoubleRounded(doubleValue); // returns {4085925351/3872710} which is 1055.0558526199999483565771772222
var literalValue = Fraction.FromDoubleRounded(doubleValue, 15); // returns {52752792631/50000000} which is 1055.05585262 exactly
Console.WriteLine(roundedValue.CompareTo(literalValue); // Outputs "-1" which stands for "smaller than"
Console.WriteLine(roundedValue.ToDouble() == doubleValue); // Outputs "true" as the actual difference is smaller than the precision of the doubles
Creation from string
The following string patterns can be parsed:
[+/-]n
where n is an integer. Examples: +5, -6, 1234, 0[+/-]n.m
where n and m are integers. The decimal point symbol depends on the system's culture settings. Examples: -4.3, 0.45[+/-]n/[+/-]m
where n and m are integers. Examples: 1/2, -4/5, +4/-3, 32/100 Example:
var value = Fraction.FromString("1,5", new CultureInfo("de-DE"))
// Returns 3/2 which is 1.5
Console.WriteLine(value);
You should consider the TryParse
methods when reading numbers as text from user input. Furthermore it is best practice to always supply a culture information (e.g. CultureInfo.InvariantCulture
). Otherwise you will sooner or later parse wrong numbers because of different decimal point symbols or included Thousands character.
Conversion
You can convert a Fraction
to any supported data type by calling:
.ToInt32()
.ToUInt32()
.ToInt64()
.ToUInt64()
.ToBigInteger()
.ToDecimal()
.ToDouble()
.ToString()
(using current culture).ToString(string)
(using format string and the system's current culture).ToString(string,IFormatProvider)
If the target's data type boundary values are exceeded the system will throw an OverflowException
.
Example:
var rationalNumber = new Fraction(1, 3);
var value = rationalNumber.ToDecimal();
// result is 0.33333
Console.WriteLine(Math.Round(value, 5));
String format
Specifier | Description |
---|---|
G | General format: <numerator>/<denominator> e.g. 1/3 |
n | Numerator |
d | Denominator |
z | The fraction as integer |
r | The positive remainder of all digits after the decimal point using the format: <numerator>/<denominator> or string.Empty if the fraction is a valid integer without digits after the decimal point. |
m | The fraction as mixed number e.g. 2 1/3 instead of 7/3 |
Note: The special characters #, and 0 like in #.### are not supported. Consider converting the Fraction
to decimal
/double
if you want to support the custom formats.
Example:
var value = new Fraction(3, 2);
// returns 1 1/2
Console.WriteLine(value.ToString("m", new CultureInfo("de-DE")));
Decimal Notation Formatter
The DecimalNotationFormatter
class allows for formatting Fraction
objects using the standard decimal notation, and the specified format and culture-specific format information.
Unlike standard numeric types such as double
and decimal
, there is no limit to the represented range or precision when using DecimalNotationFormatter
.
Usage
Here is a general example of how to use the DecimalNotationFormatter
:
Fraction value = Fraction.FromString("123456789987654321.123456789987654321");
string formattedValue = DecimalNotationFormatter.Instance.Format("G36", value, CultureInfo.InvariantCulture);
Console.WriteLine(formattedValue); // Outputs "123456789987654321.123456789987654321"
In this example, the Format
method is used to format the value of a Fraction
object into a string using the 'G' (General) format with a precision specifier of 36, which formats the fraction with up to 36 significant digits.
Supported Formats
The Format
method supports the following format strings. For more information about these formats, see the official .NET documentation.
Specifier | Format Name | Fraction | Format | Output |
---|---|---|---|---|
'G' or 'g' | General format | 400/3 |
'G2' | 1.3E+02 |
'F' or 'f' | Fixed-point format | 12345/10 |
'F2' | 1234.50 |
'N' or 'n' | Standard Numeric format | 1234567/1000 |
'N2' | 1,234.57 |
'E' or 'e' | Scientific format | 1234567/1000 |
'E2' | 1.23E+003 |
'P' or 'p' | Percent format | 2/3 |
'P2' | 66.67 % |
'C' or 'c' | Currency format | 1234567/1000 |
'C2' | $1,234.57 |
'R' or 'r' | Round-trip format | 1234567/1000 |
'R' | 1234.567 |
'S' or 's' | Significant Digits After Radix format | 400/3 |
'S2' | 133.33 |
Please note that the 'R' format and the custom formats are handled by casting the Fraction
to double
, which may result in a loss of precision.
Significant Digits After Radix Format
The 'S' format is a non-standard format that formats the fraction with significant digits after the radix and dynamically switches between decimal and scientific notation depending on the value of the fraction.
For fractions where the absolute value is greater than or equal to 0.001 and less than 10000, the 'S' format uses decimal notation. For all other values, it switches to scientific notation.
Here are a few examples:
Fraction value = new Fraction(1, 3);
Console.WriteLine(value.ToString("S")); // Outputs "0.33"
value = newFraction(1, 1000);
Console.WriteLine(value.ToString("S")); // Outputs "0.001"
value = new Fraction(1, 100000);
Console.WriteLine(value.ToString("S")); // Outputs "1E-05"
Mathematic operators
The following mathematic operations are supported:
.Reduce()
returns a normalized fraction (e.g. 2/4 → 1/2).Add(Fraction)
returns the sum of(a + b)
.Subtract(Fraction)
returns the difference of(a - b)
.Multiply(Fraction)
returns the product of(a * b)
.Divide(Fraction)
returns the quotient of(a / b)
.Remainder(Fraction)
returns the remainder (or left over) of(a % b)
.Negate()
returns a negated fraction (same operation as(a * -1)
).Abs()
returns the absolute value|a|
Fraction.Pow(Fraction, int)
returns a base raised to a power(a ^ exponent)
(e.g. 1/10^(-1) → 10/1)Fraction.Round(Fraction, int, MidpointRounding)
returns the fraction, which is rounded to the specified precisionFraction.RoundToBigInteger(Fraction, MidpointRounding)
returns the fraction as rounded BigInteger
As extension method:
FractionExt.Sqrt(this Fraction, int)
returns the square root, specifying the precision after the decimal point.
Example:
var a = new Fraction(1, 3);
var b = new Fraction(2, 3);
var result = a * b;
// returns 2/9 which is 0,2222...
Console.WriteLine(result);
Equality operators
Fraction
implements the following interfaces:
IEquatable<Fraction>
,IComparable
,IComparable<Fraction>
Please note that .Equals(Fraction)
will compare the exact values of numerator and denominator. That said:
var a = new Fraction(1, 2, true);
var b = new Fraction(1, 2, false);
var c = new Fraction(2, 4, false);
// result1 is true
var result1 = a == a;
// result2 is true
var result2 = a == b;
// result3 is false
var result3 = a == c;
You have to use .IsEquivalentTo(Fraction)
if want to test non-normalized fractions for value-equality.
Under the hood
The data type stores the numerator and denominator as BigInteger
. Per default it will reduce fractions to its normalized form during creation. The result of each mathematical operation will be reduced as well. There is a special constructor to create a non-normalized fraction. Be aware that Equals
relies on normalized values when comparing two different instances.
Performance considerations
We have a suite of benchmarks that test the performance of various operations in the Fractions library. These benchmarks provide valuable insights into the relative performance of different test cases. For more detailed information about these benchmarks and how to interpret them, please refer to the Fractions Benchmarks Readme in the benchmarks subfolder.
Build from source
Just run dotnet build -c release
.
Required software frameworks
- .Net 8.0 SDK
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. 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. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Fractions (>= 7.7.0)
- Newtonsoft.Json (>= 13.0.3)
-
.NETStandard 2.1
- Fractions (>= 7.7.0)
- Newtonsoft.Json (>= 13.0.3)
-
net8.0
- Fractions (>= 7.7.0)
- Newtonsoft.Json (>= 13.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
8.1.0 | 37 | 11/20/2024 |
8.0.4 | 129 | 8/19/2024 |
8.0.3 | 361 | 7/22/2024 |
8.0.2 | 124 | 7/4/2024 |
8.0.1 | 116 | 7/3/2024 |
8.0.0 | 101 | 7/3/2024 |
7.7.1 | 119 | 5/6/2024 |
7.7.0 | 115 | 4/17/2024 |
7.6.1 | 116 | 4/15/2024 |
7.6.0 | 116 | 4/13/2024 |
7.5.0 | 114 | 4/12/2024 |
7.4.1 | 6,291 | 3/31/2024 |
7.4.0 | 97 | 3/31/2024 |
7.3.0 | 199 | 10/1/2023 |
7.2.1 | 419 | 12/11/2022 |
7.2.0 | 569 | 11/9/2022 |
7.1.0 | 1,582 | 2/20/2022 |
7.0.0 | 398 | 10/6/2021 |
6.0.0 | 457 | 2/18/2021 |
5.0.1 | 489 | 11/13/2020 |
5.0.0 | 469 | 11/13/2020 |
4.0.1 | 720 | 6/3/2019 |
4.0.0 | 656 | 6/3/2019 |
3.0.1 | 1,121 | 5/1/2017 |
3.0.0 | 1,003 | 5/1/2017 |
2.0.0 | 1,156 | 10/29/2015 |
1.0.0 | 1,889 | 11/16/2013 |