如何知道分数中的重复小数?

时间:2012-01-20 18:44:20

标签: c# algorithm class

我已经知道分数是否重复小数。这是功能。

public bool IsRepeatingDecimal
{
    get
    {
        if (Numerator % Denominator == 0)
            return false;

        var primes = MathAlgorithms.Primes(Denominator);

        foreach (int n in primes)
        {
            if (n != 2 && n != 5)
                return true;
        }

        return false;
    }
}

现在,我正在努力获得重复的数字。我正在查看此网站:http://en.wikipedia.org/wiki/Repeating_decimal

public decimal RepeatingDecimal()
{
    if (!IsRepeatingDecimal) throw new InvalidOperationException("The fraction is not producing repeating decimals");

    int digitsToTake;
    switch (Denominator)
    {
        case 3:
        case 9: digitsToTake = 1; break;
        case 11: digitsToTake = 2; break;
        case 13: digitsToTake = 6; break;
        default: digitsToTake = Denominator - 1; break;
    }

    return MathExtensions.TruncateAt((decimal)Numerator / Denominator, digitsToTake);
}

但我真的意识到,有些数字有一个部分小数有限,后来有无数。例如:1/28

你知道更好的方法吗?还是算法?

5 个答案:

答案 0 :(得分:23)

一个非常简单的算法是:实现长除法。记录您所做的每个中级部门。一旦你看到一个与你之前完成的分工相同的分裂,你就会有重复的东西。

示例:7/13。

1. 13 goes into   7 0 times with remainder  7; bring down a 0.
2. 13 goes into  70 5 times with remainder  5; bring down a 0.
3. 13 goes into  50 3 times with remainder 11; bring down a 0.
4. 13 goes into 110 8 times with remainder  6; bring down a 0.
5. 13 goes into  60 4 times with remainder  8; bring down a 0.
6. 13 goes into  80 6 times with remainder  2; bring down a 0.
7. 13 goes into  20 1 time  with remainder  7; bring down a 0.
8. We have already seen 13/70 on line 2; so lines 2-7 have the repeating part

算法给出了538461作为重复部分。我的计算器说7/13是0.538461538。看起来对我来说!剩下的就是实现细节,或者找到更好的算法!

答案 1 :(得分:5)

如果你有一个(正)减少的分数numerator / denominator,当且仅当denominator没有除2或5之外的素因子时,分数的十进制展开终止。如果它有任何其他素数因子,十进制扩展将是周期性的。但是,分母可以被2和5中的至少一个整除,并且它不会产生略微不同的行为。我们有三种情况:

  1. denominator = 2^a * 5^b,然后十进制扩展终止小数点后的max {a, b}位数。
  2. denominator = 2^a * 5^b * m其中m > 1不能被2或5整除,那么小数展开的小数部分由两部分组成,前期的长度{ {1}}和句点,其长度由max {a, b}确定,与分子无关。
  3. m不能被2或5整除,那么十进制扩展纯粹是周期性的,这意味着句点在小数点之后立即开始。
  4. 案件的处理1.和2.有一个共同的部分,让denominator > 1,然后

    c = max {a, b}

    案例1的numerator / denominator = (numerator * 2^(c-a) * 5^(c-b)) / (10^c * m) 。请注意,我们乘以分子的因子m = 12^(c-a)之一是1.然后通过展开得到小数展开

    5^(c-b)

    并将小数点(numerator * 2^(c-a) * 5^(c-b)) / m 移到左侧。在第一种情况下(c),这部分是微不足道的。

    案例2.和3的处理也有一个共同的部分,计算一个分数

    m = 1

    其中n / m n没有共同的素因子(m)。我们可以用m > 1n = q*m + r(余数除0 <= r < m),q是分数的组成部分,而且无趣。

    由于假设分数减少,我们得r = n % m,因此我们希望找到分数r > 0的扩展,其中r / m0 < r < m不能被2整除如上所述,这种扩展纯粹是周期性的,因此找到期限意味着找到完全扩展。

    让我们开始寻找启发期。因此,m应为(最短)句点的长度和k句点的长度。所以

    p = d_1d1_2...d_k

    最后一个术语是几何系列r / m = 0.d_1d_2...d_kd_1d_2...d_kd_1... = (d_1d_2...d_k)/(10^k) + (d_1d_2...d_k)/(10^(2k)) + (d_1d_2...d_k)/(10^(3k)) + ... = p/(10^k) * (1 + 1/(10^k) + 1/(10^(2k)) + 1/(10^(3k)) + ...) ,其中1 + q + q^2 + q^3 + ...的总和为|q| < 1。 在我们的案例中,1/(1-q),因此总和为0 < q = 1/(10^k) < 1。因此我们已经看到了

    1 / (1 - 1/(10^k)) = 10^k / (10^k-1)

    由于r / m = p / (10^k-1) r没有共同因素,这意味着ms10^k - 1 = s*m。如果我们知道p = s*r,期间的长度,我们可以通过计算找到期间的数字

    k

    并使用前导零填充,直到我们有p = ((10^k - 1)/m) * r 个数字。 (注意:只有当k足够小或大整数类型可用时才这么简单。要计算例如标准固定宽度整数类型的17/983的周期,请使用长除法,如@所解释的那样。 Patrick87。)

    所以仍然需要找到这个时期的长度。我们可以恢复上面的推理,并发现如果km,那么我们可以写

    10^u - 1

    r / m = t/(10^u - 1) = t/(10^u) + t/(10^(2u)) + t/(10^(3u)) + ... = 0.t_1t_2...t_ut_1t_2...t_ut_1... 的句点长度为r/m。因此,最短期间的长度是最小正u,以便um,或换句话说,最小正10^u - 1u }。

    我们可以在O(m)时间内找到它

    10^u % m == 1

    现在,找到这段时间的长度并不比找到句点的数字和长度以及长除法更有效,并且足够小u = 0; a = 1; do { ++u; a = (10*a) % m; while(a != 1); 这是最有效的方法。

    m

    只要int[] long_division(int numerator, int denominator) { if (numerator < 1 || numerator >= denominator) throw new IllegalArgumentException("Bad call"); // now we know 0 < numerator < denominator if (denominator % 2 == 0 || denominator % 5 == 0) throw new IllegalArgumentException("Bad denominator"); // now we know we get a purely periodic expansion int[] digits = new int[denominator]; int k = 0, n = numerator; do { n *= 10; digits[k++] = n / denominator; n = n % denominator; }while(n != numerator); int[] period = new int[k]; for(n = 0; n < k; ++n) { period[n] = digits[n]; } return period; } 没有溢出就行,当然10*(denominator - 1)可以是32位或64位整数。

    但对于大分母来说,这是低效率的,通过考虑分母的素数因子分解,可以更快地找到周期长度和周期。关于期间长度,

    • 如果分母是主要权力int,则m = p^k的句点长度是r/m
    • 的除数
    • 如果(p-1) * p^(k-1)a是互质且b,则m = a * b的句点长度是r/m和{的句点长度的最小公倍数{1}}。

    总的来说,1/a的句点长度是1/b的除数,其中r/m Carmichael函数

    因此,要查找λ(m)的句点长度,找到λ的素数因子分解,并找到所有素数幂{4}},找到r/m的句点 - 等效, 10模m的乘法阶数,已知为p^k的除数。由于这些数字的除数不是很多,所以很快就会完成。 然后找到所有这些中最不常见的倍数。

    对于句点本身(数字),如果有一个大整数类型并且周期不是太长,那么公式

    1/(p^k)

    是一种快速计算方法。如果周期太长或者没有大整数类型可用,那么有效地计算数字会更加混乱,而且我不记得究竟是怎么做的。

答案 2 :(得分:2)

一种方法是重复手工分割的方式,并记下每个阶段的剩余部分。当剩余部分重复时,其余过程也必须重复。例如。 1.0 / 7的数字是0.1余数3然后0.14剩余2然后0.142剩余6然后0.1428余数4然后0.14285余数5然后0.142857余数1这是1再次启动它amd所以你得到0.1428571余数3并且它再次重复从那里。

答案 3 :(得分:1)

长除法算法非常好,所以我没有什么可以添加的。

但请注意,您的算法IsRepeatingDecimal可能不起作用且无效。

如果你的分数不是不可约的,那就不会有效,也就是说如果存在一个大于1的整数,它将你的分子和分母分开。例如,如果您提供7/14,那么当算法返回false时,您的算法将返回true。

要减少分数,找到分子和分母之间的gcd,并用这个gcd除以。

如果您认为该分数是不可简化的,那么您的测试

if (Numerator % Denominator == 0)

可以简单地替换为

if (Denominator == 1)

但这仍然是不必要的,因为如果Denominator为1,那么你的列表'primes'将是空的,你的算法无论如何都会返回false。

最后,调用MathAlgorithms.Primes(Denominator)对于大数字来说将是昂贵的,并且可以避免。实际上,你需要做的就是将你的分母除以5(分别为2),直到它不再被5整除(相应的2)。如果最终结果为1,则返回false,否则返回true。

答案 4 :(得分:1)

我来到这里是希望能够复制并粘贴代码来执行此操作,但是它不存在。因此,在阅读@ Patrick87的答案后,我继续进行了编码。我花了一些时间对其进行彻底的测试,并给它起了一个好名字。我以为我会把它留在这里,这样别人就不必浪费时间了。

功能: 如果小数点终止,它将进行处理。它会计算时间段,并将其放在名为period的单独变量中,以防您想知道提示的 length

限制: 如果瞬变+ reptend的时间长于System.Decimal表示的时间,它将失败。

public static string FormatDecimalExpansion(RationalNumber value)
{
    RationalNumber currentValue = value;

    string decimalString = value.ToDecimal().ToString();
    int currentIndex = decimalString.IndexOf('.');

    Dictionary<RationalNumber, int> dict = new Dictionary<RationalNumber, int>();
    while (!dict.ContainsKey(currentValue))
    {
        dict.Add(currentValue, currentIndex);

        int rem = currentValue.Numerator % currentValue.Denominator;
        int carry = rem * 10;

        if (rem == 0) // Terminating decimal
        {
            return decimalString;
        }

        currentValue = new RationalNumber(carry, currentValue.Denominator);
        currentIndex++;
    }

    int startIndex = dict[currentValue];
    int endIndex = currentIndex;
    int period = (endIndex - startIndex); // The period is the length of the reptend

    if (endIndex >= decimalString.Length)
    {
        throw new ArgumentOutOfRangeException(nameof(value),
            "The value supplied has a decimal expansion that is longer" +
            $" than can be represented by value of type {nameof(System.Decimal)}.");
    }

    string transient = decimalString.Substring(0, startIndex);
    string reptend = decimalString.Substring(startIndex, period);

    return transient + $"({reptend})";
}

为了很好,我将包含我的RationalNumber类。 注意:它是从IEquatable继承的,因此可以与字典一起正常使用:


public struct RationalNumber : IEquatable<RationalNumber>
{
    public int Numerator;
    public int Denominator;

    public RationalNumber(int numerator, int denominator)
    {
        Numerator = numerator;
        Denominator = denominator;
    }

    public decimal ToDecimal()
    {
        return Decimal.Divide(Numerator, Denominator);
    }

    public bool Equals(RationalNumber other)
    {
        return (Numerator == other.Numerator && Denominator == other.Denominator);
    }

    public override int GetHashCode()
    {
        return new Tuple<int, int>(Numerator, Denominator).GetHashCode();
    }

    public override string ToString()
    {
        return $"{Numerator}/{Denominator}";
    }
}

享受!