十进制到分数

Ada*_*ost 2 c# math integer-overflow

我正在研究一个需要接受和显示复杂分数的系统.接受分数并将它们转换为double作品的代码,但是当我想显示该值时,我需要转换回小数表示.

编辑:我已经修复了溢出问题,但是没有解决像1/3或5/6这样的分数.所以我设计了一种非常黑客的方法来做到这一点.我有代码生成1-> 64的每个分数0-> 64的十进制表示,并保存最简化的形式.这样,我可以遍历列表并找到最接近的分数,并简单地显示它.我有一些后会发布代码.

我现在的代码适用于绝大多数数字,但偶尔我会得到一小部分数字1/321.这会转换为double,但不能转换回来,因为在我的方法中,分子会导致整数溢出.

这是我的代码,我想知道是否有更好的方法,或者是否有一些方法可以安全地将这些转换为long而不会失去正确结果所需的精度:

public static String DecimalToFraction(double dec)
    {
        string str = dec.ToString();
        if (str.Contains('.'))
        {
            String[] parts = str.Split('.');
            long whole = long.Parse(parts[0]);
            long numerator = long.Parse(parts[1]);
            long denominator = (long)Math.Pow(10, parts[1].Length);
            long divisor = GCD(numerator, denominator);
            long num = numerator / divisor;
            long den = denominator / divisor;

            String fraction = num + "/" + den;
            if (whole > 0)
            {
                return whole + " " + fraction;
            }
            else
            {
                return fraction;
            }
        }
        else
        {
            return str;
        }
    }

    public static long GCD(long a, long b)
    {
        return b == 0 ? a : GCD(b, a % b);
    }
Run Code Online (Sandbox Code Playgroud)

InB*_*een 5

最近我不得不编写类似的场景.在我的情况下,从十进制到有理数的转换必须在数学上更正确,所以我最终实现了一个Continued Fraction算法.

虽然它是根据我的具体实施量身定制的RationalNumber,但你应该明白这一点.这是一个相对简单的算法,对任何有理数近似都能很好地工作.请注意,实现将为您提供具有所需精度的最接近的近似值.

/// <summary>
/// Represents a rational number with 64-bit signed integer numerator and denominator.
/// </summary>
[Serializable]
public struct RationalNumber : IComparable, IFormattable, IConvertible, IComparable<RationalNumber>, IEquatable<RationalNumber>
{
     private const int MAXITERATIONCOUNT = 20;

     public RationalNumber(long number) {...}
     public RationalNumber(long numerator, long denominator) {...}
     public RationalNumber(RationalNumber numerator, RationalNumer denominator) {...}

     ...
     /// <summary>
     /// Defines an implicit conversion of a 64-bit signed integer to a rational number.
     /// </summary>
     /// <param name="value">The value to convert to a rational number.</param>
     /// <returns>A rational number that contains the value of the value parameter as its numerator and 1 as its denominator.</returns>
     public static implicit operator RationalNumber(long value)
     {
         return new RationalNumber(value);
     }

     /// <summary>
     /// Defines an explicit conversion of a rational number to a double-precision floating-point number.
     /// </summary>
     /// <param name="value">The value to convert to a double-precision floating-point number.</param>
     /// <returns>A double-precision floating-point number that contains the resulting value of dividing the rational number's numerator by it's denominator.</returns>
     public static explicit operator double(RationalNumber value)
     {
         return (double)value.numerator / value.Denominator;
     }

     ...
     /// <summary>
     /// Adds two rational numbers.
     /// </summary>
     /// <param name="left">The first value to add.</param>
     /// <param name="right">The second value to add.</param>
     /// <returns>The sum of left and right.</returns>
     public static RationalNumber operator +(RationalNumber left, RationalNumber right)
     {
         //First we try directly adding in a checked context. If an overflow occurs we use the least common multiple and return the result. If it overflows again, it
         //will be up to the consumer to decide what he will do with it.
         //Cost penalty should be minimal as adding numbers that cause an overflow should be very rare.

         RationalNumber result;

         try
         {
             long numerator = checked(left.numerator * right.Denominator + right.numerator * left.Denominator);
             long denominator = checked(left.Denominator * right.Denominator);
             result = new RationalNumber(numerator,denominator);
         }
         catch (OverflowException)
         {
             long lcm = RationalNumber.getLeastCommonMultiple(left.Denominator, right.Denominator);
             result = new RationalNumber(left.numerator * (lcm / left.Denominator) + right.numerator * (lcm / right.Denominator), lcm);
         }

         return result;
     }

     private static long getGreatestCommonDivisor(long i1, long i2)
     {
        Debug.Assert(i1 != 0 || i2 != 0, "Whoops!. Both arguments are 0, this should not happen.");

        //Division based algorithm
        long i = Math.Abs(i1);
        long j = Math.Abs(i2);
        long t;

        while (j != 0)
        {
            t = j;
            j = i % j;
            i = t;
        }

        return i;
    }

    private static long getLeastCommonMultiple(long i1, long i2)
    {
        if (i1 == 0 && i2 == 0)
            return 0;

        long lcm = i1 / getGreatestCommonDivisor(i1, i2) * i2;
        return lcm < 0 ? -lcm : lcm;
     }
     ...

     /// <summary>
     /// Returns the nearest rational number approximation to a double-precision floating-point number with a specified precision.
     /// </summary>
     /// <param name="target">Target value of the approximation.</param>
     /// <param name="precision">Minimum precision of the approximation.</param>
     /// <returns>Nearest rational number with, at least, the required precision.</returns>
     /// <exception cref="System.ArgumentException">Can not find a rational number approximation with specified precision.</exception>
     /// <exception cref="System.OverflowException">target is larger than Mathematics.RationalNumber.MaxValue or smaller than Mathematics.RationalNumber.MinValue.</exception>
     /// <remarks>It is important to clarify that the method returns the first rational number found that complies with the specified precision. 
     /// The method is not required to return an exact rational number approximation even if such number exists.
     /// The returned rational number will always be in coprime form.</remarks>
     public static RationalNumber GetNearestRationalNumber(double target, double precision)
     {
         //Continued fraction algorithm: http://en.wikipedia.org/wiki/Continued_fraction
         //Implemented recursively. Problem is figuring out when precision is met without unwinding each solution. Haven't figured out how to do that.
         //Current implementation evaluates a Rational approximation for increasing algorithm depths until precision criteria is met or maximum depth is reached (MAXITERATIONCOUNT)
         //Efficiency is probably improvable but this method will not be used in any performance critical code. No use in optimizing it unless there is a good reason.
         //Current implementation works reasonably well.

         RationalNumber nearestRational = RationalNumber.zero;
         int steps = 0;

         while (Math.Abs(target - (double)nearestRational) > precision)
         {
             if (steps > MAXITERATIONCOUNT)
                 throw new ArgumentException(Strings.RationalMaximumIterationsExceptionMessage, "precision");

             nearestRational = getNearestRationalNumber(target, 0, steps++);
         }

         return nearestRational;
     }

    private static RationalNumber getNearestRationalNumber(double number, int currentStep, int maximumSteps)
    {
        long integerPart;
        integerPart = checked((long)number);
        double fractionalPart = number - integerPart;

        while (currentStep < maximumSteps && fractionalPart != 0)
        {
            return integerPart + new RationalNumber(1, getNearestRationalNumber(1 / fractionalPart, ++currentStep, maximumSteps));
        }

        return new RationalNumber(integerPart);
    }     
}
Run Code Online (Sandbox Code Playgroud)

更新:哎呀,忘了包含operator +代码.固定它.