Fractions.Json 7.7.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Fractions.Json --version 7.7.1                
NuGet\Install-Package Fractions.Json -Version 7.7.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Fractions.Json" Version="7.7.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Fractions.Json --version 7.7.1                
#r "nuget: Fractions.Json, 7.7.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Fractions.Json as a Cake Addin
#addin nuget:?package=Fractions.Json&version=7.7.1

// Install Fractions.Json as a Cake Tool
#tool nuget:?package=Fractions.Json&version=7.7.1                

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>) for int, uint, long, ulong, BigInteger, decimal and double (without rounding).
  • new Fraction (<numerator>, <denominator>) using BigInteger for numerator and denominator.
  • new Fraction (<numerator>, <denominator>, <reduce>) using BigInteger 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/41/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 precision
  • Fraction.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

Build status

Just run dotnet build -c release.

Required software frameworks

  • .Net 8.0 SDK
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 33 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