小数到分数
本文关键字:小数 | 更新日期: 2023-09-27 18:21:00
我正在开发一个需要接受和显示复杂分数的系统。接受分数并将其转换为double
的代码是有效的,但当我想显示该值时,我需要转换回分数表示。
编辑:我已经解决了溢出问题,但没有解决1/3或5/6这样的分数。因此,我设计了一种非常巧妙的方法来做到这一点。我有一段代码,可以生成0->64和1->64上的每个分数的十进制表示,并保存最简化的形式。这样,我可以迭代列表,找到最接近的分数,并简单地显示它。一旦我有了代码就会发布
我现在有了适用于绝大多数数字的代码,但偶尔我会得到像1/321
这样的一小部分。它被转换为双精度,但无法转换回,因为在我的方法中,分子会导致整数溢出。
这是我的代码,我想知道是否有更好的方法,或者是否有某种方法可以安全地将这些转换为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);
}
最近我不得不编写一个类似的场景。在我的例子中,从十进制到有理数的转换必须在数学上更正确,所以我最终实现了连分式算法。
尽管它是根据我对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 +
代码了。修复它。
您可以使用BigRational,这是微软在其codeplex上的BCL项目下发布的。它支持任意大的有理数,并且实际上将其作为比率存储在内部。好的方面是,您可以将它很大程度上视为一个普通的数字类型,因为所有的运算符都是重载的。
有趣的是,它缺乏将数字打印为十进制的方法。不过,在我之前的回答中,我写了一些代码来实现这一点。然而,它的性能或质量没有任何保证(我几乎不记得写过它)。
将数字保留为分数:
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;
}
}
请检查以下两种方法:
/// <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();
}
}