2014-09-05 5 views
2

복잡한 분수를 받아들이고 표시해야하는 시스템에서 작업하고 있습니다. 분수를 받아들이고이를 double으로 바꾸는 코드는 작동하지만, 그 값을 표시하려면 분수 표현으로 다시 변환해야합니다.십진법과 분수

편집 : overflow 문제가 수정되었지만 1/3 또는 5/6과 같은 분수를 해결하지 못했습니다. 그래서 나는 이것을하기에 매우 해킹 된 방법을 고안했다. 나는 모든 분수 0 -> 64 1 -> 64, 및 가장 단순화 된 폼을 저장하는 10 진수 표현을 생성하는 코드가 있습니다. 이렇게하면 목록을 반복하고 가장 가까운 소수점을 찾아 간단하게 표시 할 수 있습니다. 일단 내가 코드를 게시 할 것입니다.

대다수의 숫자에 대해 작동하는 코드가 있는데, 때로는 1/321과 같은 작은 조각이 나옵니다. 이 방법은 double로 변환 되긴하지만 다시 변환 할 수는 없습니다. 왜냐하면 내 방식에서는 분자가 정수 오버플로를 발생시키기 때문입니다.

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); 
    } 
+4

잘 끝나지 않을 것입니다 이중에 진수 값을 퍼팅. Double은 2 진 유형입니다. –

+0

앞뒤로 변환하지 않고 값을 전달하는 새로운'Fraction' 유형을 만들 수 있습니까? 반복되는 소수 자릿수를 어떻게 처리 할 것으로 예상합니까? –

+0

@DavidHeffernan이 값을 저장하는 데 double을 사용하면 안되는 이유에 대해 혼란 스럽습니다. 대다수의 경우 전체 숫자와 단순 분수만으로는 충분할 것이라는 것을 이해했습니다. 나는이 복잡한 부분을 차단하는 결과를 초래할 수 있습니다. –

답변

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); 
    }  
} 

UPDATE는 : 으악는 operator + 코드를 포함하는 것을 잊었다. 고쳤다.

2

유지 : 거기에 더 나은 방법, 또는 안전하게 올바른 결과에 필요한 정밀도를 잃지 않고 걷고 이러한 변환 어떻게든지있을 경우 경우 여기

, 내 코드 궁금하네요됩니다 분수 등의 수 :

struct Fraction 
{ 
    private int _numerator; 
    private int _denominator; 

    public int Numerator { get { return _numerator; } } 
    public int Denominator { get { return _denominator; } } 
    public double Value { get { return ((double) Numerator)/Denominator; } } 

    public Fraction(int n, int d) 
    { 
     // move negative to numerator. 
     if(d < 0) 
     { 
      _numerator = -n; 
      _denominator = -d; 
     } 
     else if(d > 0) 
     { 
      _numerator = n; 
      _denominator = d; 
     } 
     else 
      throw new NumberFormatException("Denominator cannot be 0"); 
    } 



    public void ToString() 
    { 
     string ret = ""; 
     int whole = Numerator/Denominator; 
     if(whole != 0) 
      ret += whole + " "; 
     ret += Math.Abs(Numerator % Denominator) + "/" + Denominator; 
     return ret; 
    } 
} 
+1

이것은 몇 가지 경우 중 하나처럼 보입니다. 구조체를 사용하는 것이 더 이해하기 쉽습니다 (읽기 전용 속성을 사용하도록 업데이트 됨, 분명히). –

+0

저는이 양을 분수 포맷으로 필요로하는 것보다 자주 이중/부동으로 조작하고 있습니다. 또한 SQL 데이터베이스에 저장되므로 10 진수 표현을 사용하는 것이 훨씬 더 적합합니다. –

+1

@Adam은 모두 k/2^n 형식의 값입니다. 당신은 당신의 믿음과는 반대로 10 진수 표현을 사용하고 있지 않다는 것을 알고 있어야합니다. 당신이 동의 할 때까지는 진전을 이룰 수 없습니다. –

2

당신 사용 BigRational, 마이크로 소프트는 CodePlex에 자신의 BCL 프로젝트에서 발표했다. 그것은 임의로 큰 유리수를 지원하며 실제로 내부적으로 비율로 저장합니다. 좋은 점은 모든 연산자가 당신을 위해 오버로드되어 있기 때문에 보통 숫자 형으로 크게 처리 할 수 ​​있다는 것입니다.

흥미롭게도 10 진수로 숫자를 인쇄하는 방법이 없습니다. 이 코드를 작성한 코드는 previous answer of mine에 작성했습니다. 그러나 성능이나 품질에 대한 보장은 없습니다 (필자는이 점을 간과해서는 안됩니다).

0

이이 방법을 확인하십시오 :

/// <summary> 
    /// Converts Decimals into Fractions. 
    /// </summary> 
    /// <param name="value">Decimal value</param> 
    /// <returns>Fraction in string type</returns> 
    public string DecimalToFraction(double value) 
    { 
     string result; 
     double numerator, realValue = value; 
     int num, den, decimals, length; 
     num = (int)value; 
     value = value - num; 
     value = Math.Round(value, 5); 
     length = value.ToString().Length; 
     decimals = length - 2; 
     numerator = value; 
     for (int i = 0; i < decimals; i++) 
     { 
      if (realValue < 1) 
      { 
       numerator = numerator * 10; 
      } 
      else 
      { 
       realValue = realValue * 10; 
       numerator = realValue; 
      } 
     } 
     den = length - 2; 
     string ten = "1"; 
     for (int i = 0; i < den; i++) 
     { 
      ten = ten + "0"; 
     } 
     den = int.Parse(ten); 
     num = (int)numerator; 
     result = SimplifiedFractions(num, den); 
     return result; 
    } 

    /// <summary> 
    /// Converts Fractions into Simplest form. 
    /// </summary> 
    /// <param name="num">Numerator</param> 
    /// <param name="den">Denominator</param> 
    /// <returns>Simplest Fractions in string type</returns> 
    string SimplifiedFractions(int num, int den) 
    { 
     int remNum, remDen, counter; 
     if (num > den) 
     { 
      counter = den; 
     } 
     else 
     { 
      counter = num; 
     } 
     for (int i = 2; i <= counter; i++) 
     { 
      remNum = num % i; 
      if (remNum == 0) 
      { 
       remDen = den % i; 
       if (remDen == 0) 
       { 
        num = num/i; 
        den = den/i; 
        i--; 
       } 
      } 
     } 
     return num.ToString() + "/" + den.ToString(); 
    } 
}