Git Product home page Git Product logo

fractions's Introduction

Fractions

Introduction

This package provides the Fraction type, used for representing rational numbers. It offers a comprehensive set of features for:

  • Creating fractions from various data types (integers, decimals, strings, etc.)
  • Performing common mathematical operations like addition, subtraction, multiplication, division, remainder, absolute value, and more.
  • Rounding fractions to a specified precision.
  • Converting fractions to different data types (decimals, strings, etc.).
  • Formatting fractions for output using various notations (general, mixed number, decimal notation, etc.).

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);
// ..

Note

For compatibility reasons, the Fraction that is produced by these operators is automatically reduced to its lowest terms, which may have a significant performance impact. If the performance is a concern, you should consider using one of the constructors that supports specifying the whether the terms should be reduced or not.

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, as well as a bool specifying whether the resulting fraction should be normalized (reduced).

Important

Please refer to the Working with non-normalized fractions section for more information about the possible side effects when working with non-reduced fractions.

Static creation methods

Note

All methods that were present in version 7.*, continue to return a Fraction that is automatically reduced to its lowest terms. Starting from version 8.0.0 these methods are now supplemented by an overload that adds an additional boolean parameter, specifying whether the terms should be reduced.

Important

Starting from version 8.0.0, the FromDouble(..) overloads no longer throw an ArgumentException when passed double.NaN, double.PositiveInfinity or double.NegativeInfinity. These values are instead represented as 0/0, +1/0 or -1/0.
For more information see the section about working with NaN and Infinity.

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", CultureInfo.GetCultureInfo("de-DE"))
// Returns 3/2 which is 1.5
Console.WriteLine(value);

Tip

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.

Here is a table presenting the different overloads, and the default parameters that are assumed for each of them:

Method NumberStyles IFormatProvider Normalize
Fraction.FromString(string) Number null ✔️
Fraction.FromString(string, boolean) Number null
Fraction.FromString(string, IFormatProvider) Number ✔️
Fraction.FromString(string, NumberStyles, IFormatProvider) ✔️
Fraction.FromString(string, NumberStyles, IFormatProvider, bool)
Fraction.TryParse(string, out Fraction) Number null ✔️
Fraction.TryParse(string, NumberStyles, IFormatProvider, out Fraction) ✔️
Fraction.TryParse(ReadOnlySpan<char>, NumberStyles, IFormatProvider, bool, out Fraction)

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", CultureInfo.GetCultureInfo("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 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);

Working with non-normalized fractions

Important

For performance reasons, as of version 8.0.0, mathematical operations such as addition and multiplication no longer reduce the result to it's lowest terms, unless both operands are already simplified. This change in behavior may introduce unexpected results when, for example, calling ToString on a Fraction that is the result of an expression, having one or more of the values de-serialized using the default JsonFractionConverter settings (i.e. without explicit reduction).

Symbol Description
$NF$ Non-normalized (possibly reducible) fraction, created with normalize: false
$F$ Fraction created with normalize: true (irreducible)
$⊙$ Mathematical operation having two operands ($+$, $-$, $*$, $/$, $mod$).

The following rules apply:

$F ⊙ F = F$
$F ⊙ NF = NF$
$NF ⊙ F = NF$
$NF ⊙ NF = NF$

That said, the following applies for normalized fractions:

var a = new Fraction(4, 4, normalize: true); // a is 1/1
var b = new Fraction(2);    // b is 2/1 (automatically normalized)
var result = a / b;         // result is 1/2

$\frac{1}{1}/\frac{2}{1}=\frac{1}{2}$

However, for non-normalized fractions the following applies:

var a = new Fraction(4, 4, normalize: false);
var b = new Fraction(2);    // b is 2/1 (automatically normalized)
var result = a / b;         // result is 4/8

$\frac{4}{4}/\frac{2}{1}=\frac{4}{8}$

Working with NaN and Infinity

Starting from version 8.0.0, it is now possible for a Fraction to be constructed with 0 in the denominator. This is typically the result of a division by zero which, depending on the sign of the dividend, now returns Fraction.PositiveInfinity, Fraction.NegativeInfinity or Fraction.NaN.

Subsequent mathematical operations with these values follow the same rules, as implemented by the double type. For example, any number multiplied by infinity results in infinity (or negative infinity, depending on the sign), and any number divided by infinity is zero.

You can check if a Fraction represents one of the special values using the properties:

  • Fraction.IsPositiveInfinity
  • Fraction.IsNegativeInfinity
  • Fraction.IsNaN

Tip

You could also check if a Fraction represents either NaN or Infinity by testing whether it's Denomintor.IsZero.

Equality operators

Fraction implements the following interfaces:

  • IEquatable<Fraction>,
  • IComparable,
  • IComparable<Fraction>

Important

Please note that the default behavior of the .Equals(Fraction) method has changed with version 8.0.0. Equals now compares the calculated value from the $numerator/denominator$ ($Equals(\frac{1}{2}, \frac{2}{4}) = true$).

In case you want to compare the numerator and denominator exactly (i.e. $Equals(\frac{1}{2}, \frac{2}{4}) = false$) then you can use the new FractionComparer.StrictEquality comparer.

That said:

var a = new Fraction(1, 2, normalize: true);
var b = new Fraction(1, 2, normalize: false);
var c = new Fraction(2, 4, normalize: false); // the fraction is not reduced

// result1 is true
var result1 = a == a;

// result2 is true
var result2 = a == b;

// result3 is true
var result3 = a == c;

// Special case:
// same behavior as with double, see https://learn.microsoft.com/en-us/dotnet/api/system.double.op_equality#remarks
// result4 is false 
var result4 = Fraction.NaN == Fraction.NaN;

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

fractions's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

fractions's Issues

Fraction.ToDouble() returns NaN for {2 * MaxValue, 2 * MaxValue}

Let's illustrate this with an example:

new Fraction((BigInteger)double.MaxValue * 2, (BigInteger)double.MaxValue * 2, false)

  Name Value Type
A 3.59538627e+308 System.Numerics.BigInteger
B 3.59538627e+308 System.Numerics.BigInteger
  State Unknown Fractions.FractionState
StringFormats "1" Fractions.Fraction.FractionDebugView.StringFormatsView
ValueFormats NaN Fractions.Fraction.FractionDebugView.NumericFormatsView
  Decimal 1 decimal
  Double NaN double
  Integer 1 int
  Long 1 long

I actually discovered the issue when one of my square roots turned up with a 0:

        var largeNumber = BigInteger.Pow(10, 309);
        var fraction = new Fraction(4 * largeNumber, largeNumber, false);
        var actual = fraction.Sqrt();  // is Fraction.Zero..

..but I would have likely caught it when testing the INumber interface and the TryConvertToChecked, TryConvertToSaturating and TryConvertToTruncating (which are the only functions that require special attention).

I've already started implementing the INumber<Fraction> interface (two weeks ago) but wanted to finish up the tests for my (still unpublished) PR for UnitsNet - when one of the tests failed (note this wasn't some static test concocted with an extreme value, it was an actual root-sum-of-squares that failed one of the tests in my own code-base)..

Possible OverflowException in Fraction.FromDoubleRounded

This is a pretty extreme example (it's a small niche of values for which this could happen) but still - here's an example for which the Fraction.FromDoubleRounded(double, bool) is throwing an exception:

Fraction.FromDoubleRounded(1.0 / (double.MaxValue - 100))

"BigInteger cannot represent infinity."

   at System.Numerics.BigInteger..ctor(Double value)
   at Fractions.Fraction.FromDoubleRounded(Double value, Boolean reduceTerms) Fraction.ConvertFromDouble.cs:line 213
   at Fractions.Fraction.FromDoubleRounded(Double value) in Fraction.ConvertFromDouble.cs:line 142

ToDecimal() is missing the last digit of precision

If you create the following fraction:
var test = Fraction.FromString("1488999520739749466415116125446/201514171878494548217495987785")

And get the Decimal and compare to online calculator

test .ToDecimal().ToString():
7.3890560989306502272304274605

Calculator: https://www.mathsisfun.com/calculator-precision.html
7.3890560989306502272304274605750078...

Calculator: https://keisan.casio.com/calculator
7.389056098930650227230427460575008

The last digit of the ToDecimal() should be rounded to 6 and not 5
Or am I wrong?

Update target frameworks

Currently, supported target frameworks are:

<TargetFrameworks>netstandard1.1;netstandard2.0;netstandard2.1;net45;net48;net5.0;netcoreapp3.1</TargetFrameworks>

However, these list of frameworks have two issues:

  1. .NET Standard versions (1.1, 2.0, 2.1) already cover specific versions (like .NET 4.5), meaning it's an unecessary duplication.
  2. Some of them (.NET Standard 1.1 and .NET Framework 4.5) cover unsupported versions.

Here are some references to understand what I am talking about:

Unecessary duplications

As you can see in the dynamic table of versions covered by .NET Standard:

  • .NET Framework 4.5 is already covered by .NET Standard 1.1
  • .NET Framework 4.8 is already covered by .NET Standard 1.1, 2.0 and 2.1
  • .NET Core 3.1 is already covered by .NET Standard 1.1, 2.0 and 2.1
  • .NET 5.0 is already covered by .NET Standard 1.1, 2.0 and 2.1

So you could simplify it like this:

<TargetFrameworks>netstandard1.1;netstandard2.0;netstandard2.1</TargetFrameworks>

Unsupported versions

The main question about this point is to know if why there is a direct reference to .NET Framework 4.5?
Because .NET Framework 4.5 is not supported anymore since January 12, 2016. Furthermore, even if .NET Framework 4.5.2 is still supported, its support will end on April 26, 2022 so it could be a good idea to anticipate and change this now.
Moreover, .NET Framework 4.5.2, 4.6 and 4.6.1 versions support will end on April 26, 2022 so, as you can see in the dynamic table of versions covered by .NET Standard, .NET Standard 1.1 will soon become useless. So, the final version of targeted frameworks should be:

<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>

I am raising this issue because I am using your librairy and if its covering unsupported framework versions, then it may lead to security issues that my clients are concerned about.
So I would be glad to open a PR to fix that and do whatever is necessary for you to release a new version with these modifications.

Improved to ToDouble

Hi, I have been fiddling around to see if and how your library could be extended to support the new INumber interface in C# so that something like this will work Enumerable.Range(1,100).Select(x=>new Fraction(1,x*x)).Sum(); // doesn't wort today as Sum is NOT generic. The result was: implementing INumber is pretty straight forward.
But I stumbled upon the issue that converting the result failed returning double.NaN.
I had a quick look into the code for the ToDouble conversion. I think this would give a better result - but also be slower:
private static readonly int maxDoubleDigits = (int)Math.Floor(Math.Log10(double.MaxValue) + 1);
private static readonly BigInteger maxDoubleAsBigInt = new BigInteger(double.MaxValue);

    public double ToDouble()
    {
        if (IsZero)
        {
            return 0;
        }
        // here result could already be larger as double.MaxValue - but then the result is double.NaN anyways...
       // this can be avoided by the calling code be first getting the full part of the fraction and then only consider the fractional part for double conversion
        var result = BigInteger.DivRem(_numerator, _denominator, out BigInteger remainder);
        // ToDo: is _denominator always positive?
        if (_denominator > maxDoubleAsBigInt )
        {
            // get the number of digits of the _denominator
            int digits = (int)Math.Floor(BigInteger.Log10(_denominator) + 1);
            // create a factor that will reduce the number of digits to what double can still handle
            BigInteger factor = BigInteger.Pow(10, digits - maxDoubleDigits + 1);
            // now divide remainder and _denominator by the same factor (which will cancel them out - algebraically speaking).
            // and then divide the "shortened" values 
            double fraction = (double)(remainder / factor);
            fraction /= (double)(_denominator / factor);
            return (double)result + fraction;
        }
        else
        {
            return (double)result + (((double)remainder) / ((double)_denominator));
        }

}

An alternative would be to add a function (BigInteger FullPart, double Fraction) ToDouble()....

Thanks for providing this great library!

[Discussion] Use cases for the FractionState

I'm curious about the use cases you have for the FractionState and weather you intend to extend or reduce the support for Improper (i.e. non-reduced/non-normalized) Fractions moving forward.

Here are some observations we can make:

  • there is currently no Improper option in the FractionState
  • if no support for an explicitly Improper state is required, the field could be replaced by a the equivalent private readonly bool _hasBeenNormalized (which, if necessary, could still be used to return the original State property)
  • most operations with fractions such as Multiply and Divide currently result in a Fraction with an IsNormalized state (regardless of the input state(s))
  • an app that only uses Normalized fractions can be thought of as using an upgraded version of the double type where as one that operates with Improper fractions is (IMO) similar to operating on an upgraded version of the decimal type
  • Operations with Normalized fractions are generally faster than the equivalent operations on Improper fractions
  • it is possible to represent the two use cases (Fraction, NormalizedFraction) using separate types having an implicit conversion between one another (without the need for any _state fields)

Here is an example of what I mean by having decimal-like operators:

Console.WriteLine(0.10m * 0.10m); // Outputs: "0.0100" which is effectively "100/10000" (as in 10/100 * 10/100)
Console.WriteLine(0.100m / 0.10m); // Outputs: "1.0" which is effectively "10/10" (as in 100/1000 / 10/100)

DecimalFormatter.Format("G", new Fraction(-10, -20, false), CultureInfo.InvariantCulture) returns "0.5000000000000001"

Here's the output from the debugger

  Name Value Type
new Fraction(-10, -20, false) {-10/-20} Fractions.Fraction
  ▶ A -10 System.Numerics.BigInteger
  ▶ B -20 System.Numerics.BigInteger
  State Unknown Fractions.FractionState
  ◢ StringFormats "0.51" Fractions.Fraction.FractionDebugView.StringFormatsView
  GeneralFormat "0.5000000000000001" string
  ShortFormat "0.51" string
  SimplifiedFraction "-10/-20" string
  ◢ ValueFormats 0.5 Fractions.Fraction.FractionDebugView.NumericFormatsView
  Decimal 0.50 decimal
  Double 0.5 double
  Integer 0 int
  Long 0 long

Rounding functions

Would it be possible to add methods for Floor / Ceil / Round to round the Fraction to the nearest integer? This would be helpful for cases where for example you want to convert out afterwards to BigInteger or other integer types, but doing so at the moment will lose the information from the fractional value to allow for the appropriate rounding to be performed.

I know that for small enough values I could do the rounding via a decimal or double workaround step first, but when it comes to BigInteger there isn't a similar workaround. The use case I have is for an incremental game where I want to multiply very large BigInteger numbers by a fractional scalar, for example 1.3x. I'm doing this by performing the calculation as Fraction and then using .ToBigInteger afterwards, but this is always returning a floored integer and ideally I'd like to have the options of round or ceil as well, without having to write some clunky > or < comparison code.

Square root of a Fraction?

Have a Square root function for a Fraction ever been on the table?
I need it from time to time.

Im guessing that it needs to be done in an iterative process and it might not be a easy as it sounds.
I could start working on it for this package if it would be accepted?

Improvement: Constructor that takes 2 decimals

First of all, thank you for this project it works really great!

I wish for a contractor that takes 2 decimals

ex

Fraction value = new Fraction(4, 0.0254);
so it sets it to 40000 / 254

I tried using
Fraction value = Fraction.FromString("4/0.0254");
but that ignores the dot and returns a false result

Why (19/9) % (19/27) == 0 ?

So google says (19/9) % (19/27) is 0.21019721019.

Yet this code (from latest nuget version):

            var a = new Fraction(19, 9);
            var b = new Fraction(19, 27);

            var mod = a % b;
            if (mod == 0) {
                Console.WriteLine("Bug");
            }

Gives:
image

Reduce Precision of Fraction

I am looking into creating more math operations for this library however i am hitting a wall where if I do too many calculation on a Fraction its 'size' gets too large and slows down the calculation.
Many of the math operations will have a fixed Precision so it doesn't make much sense if the fraction can grow forever.

ex.
1071283/28187739 (decimal: 0.0380052830771563 )

Reduce precision so the largest BigInt is 4 -->
107/2815 (decimal: 0.038010657 )

I have looked into creating a method that does this but I cant seem to find a good way.
Do you have an idea for this?

Fraction.TryParse no longer accepts a nullable value (v8)

I think there is a regression (but I cannot find where the change occurred):

In v7 this used to work:

public static bool TryParse(string? valueString, NumberStyles parseNumberStyles, IFormatProvider? formatProvider, out QuantityValue quantityValue)
{
    if (Fraction.TryParse(valueString, parseNumberStyles, formatProvider, out Fraction fraction))
    {
        quantityValue = new QuantityValue(fraction);
        return true;
    }

    quantityValue = default;
    return false;
}

Error (active) CS8604 Possible null reference argument for parameter 'value' in 'bool Fraction.TryParse(string value, NumberStyles numberStyles, IFormatProvider? formatProvider, out Fraction fraction)'.

Incorrect result when rounding 1/3 with MidpointRounding.ToEven

I've messed up one of the edge cases in the implementation of #23

  Name Value Type
Fraction.Round(new Fraction(1,3), 2, MidpointRounding.ToEven) {17/50} Fractions.Fraction
  Fraction.Round(new Fraction(1,3), 2, MidpointRounding.ToEven).ToDecimal() 0.34 decimal

Support for NaN and Infinity

Support for NaN and Infinity

Hey,
I've been digging into the code and I think we can add support for NaN and +/- Infinity. Here's what I'm thinking:

  1. Update GetReducedFraction(..) to return 0/0 (NaN) for 0 in the numerator and denominator. Right now, it's returning Zero.
  2. For a positive numerator and 0 in the denominator, return +1/0 (+Inf).
  3. For a negative numerator and 0 in the denominator, return -1/0 (-Inf).

We might need to tweak FractionState to {Zero, Unknown, Normalized} to avoid default(Fraction) being NaN. Also, in that case, Reduce() should check for FractionState.Zero and return a true Zero (0/1).
There might be a few more checks needed (like in the constructor from double), but I think it's doable.

I'm ready to make these changes and submit a PR if you're on board with this.

Let me know what you think!

Reciprocal method

Hi,
I've started to use this package but it's missing Reciprocal method. I can add it with extension method but that doesn't have access to private constructor of Fraction.

I've created a PR adding the method and tests.
#21

List of possible issues with string parsing in v7

Here is the summary of the tests that are failing in the current code base (a total of 107 per framework):

  1. Parsing_a_fraction_from_a_long_decimal_string_with_specific_number_style (23 tests)
  • Should_not_work_with_groups_in_the_middle("123456789987654321.,123456789987654321")
  • Should_not_work_with_groups_in_the_middle("123456789987654321.123456789,987654321")
  • Should_return_the_expected_value_when_the_string_is_valid("1234,56789987654321.-",Any,) // all strings with the +/- sign on the right side
  • Should_return_the_expected_value_when_the_string_is_valid(" (.1) ",Any,) // all strings with "(..)" as negative pattern
  1. Same issues are repeated with the scientific notation
  2. All currency formats are affected as well
  3. When parsing a string with a culture where the symbols are not the standard length (e.g. having "minus" instead of "-" for the negative sign).

When parsing a string with the sign / currency symbol to the right side, the results are incorrect due to the fact that the symbols to the right do not contribute to the number of decimals in the denominator:

The_result_should_be_minus_7_over_2("3,5-") Failed: Expected object to be equal to -7/2, but found -7/20.
Expected object to be equal to -7/2, but found -7/20.
   at FluentAssertions.Execution.LateBoundTestFramework.Throw(String message)
   at FluentAssertions.Execution.AssertionScope.FailWith(Func`1 failReasonFunc)
   at FluentAssertions.Numeric.ComparableTypeAssertions`2.Be(T expected, String because, Object[] becauseArgs)
   at Fractions.Tests.FractionSpecs.FromString.When_creating_a_fraction_from_minus_3_5_with_German_culture.The_result_should_be_minus_7_over_2(String value) in C:\Users\galin\source\repos\Fractions-fork\tests\Fractions.Tests\FractionSpecs\FromString\Method_FromString.cs:line 142

A PR is coming in a bit..

incorrect to say decimal fraction 0.1 represented by .0011 repeating

Here and elsewhere in Microsoft and Stackoverflow forums, the advice on approximating the rational number one-tenth can be approximated in binary notation by .'0011', that is .001100110011 and so on. But it should read as .0'0011' or .0001100110011 and so on. I've checked this manually for the first 9 binary places to get 51/512ths, which is close enough for me to put my correction. The current wording approximates one-fifth, not one-tenth.

[BUG] Reciprocal of a negative fraction has invalid state

Consider the following scenario:

  Name Value Type
  new Fraction(-1, 10).Reciprocal().ToDecimal() -10 decimal
  Expression Value Type
  new Fraction(-1, 10).Reciprocal().IsEquivalentTo(new Fraction(-10)) false bool

Normally this should have been true for the Equals as well, but I think this illustrates the issue better.

This problem stems from the fact that the sign of the fraction is not handled here

    public static Fraction Reciprocal(Fraction fraction) =>
        new Fraction(fraction.Denominator, fraction.Numerator, fraction.State);

According to GetReducedFraction a Normalized fraction should have it's signs normalized:

            if (denominator.Sign == -1) {
                // Denominator must not be negative after normalization
                numerator = BigInteger.Negate(numerator);
                denominator = BigInteger.Negate(denominator);
            }

I'd be willing to create a PR, but I was hoping that we could merge my "cleanup" of the test project first, if you don't mind..

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.