簡體   English   中英

如何知道分數中的重復小數?

[英]How to know the repeating decimal in a fraction?

我已經知道分數何時重復小數。 這是功能。

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

你知道一個更好的方法來做到這一點嗎? 還是算法?

一個非常簡單的算法是:實現長除法。 記錄你做的每一個中間部門。 一旦你看到一個與你之前做過的相同的划分,你就會看到重復的內容。

示例: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。 對我來說看起來不錯! 剩下的就是實現細節,或者尋找更好的算法!

如果您有一個(正)約分分數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,則小數膨脹的小數部分由兩個部分組成,長度的預期max {a, b}和周期,其長度由m決定,與分子無關。
  3. denominator > 1不能被 2 或 5 整除,那么十進制擴展是純粹的周期性,這意味着周期在小數點后立即開始。

案例 1. 和 2. 的處理有一個共同的部分,讓c = max {a, b} ,那么

numerator / denominator = (numerator * 2^(c-a) * 5^(c-b)) / (10^c * m)

其中,對於情況 1, m = 1 1。請注意,我們乘以分子的因子2^(ca)5^(cb)是 1。然后通過展開得到十進制展開

(numerator * 2^(c-a) * 5^(c-b)) / m

並將小數點c位向左移動。 在第一種情況 ( m = 1 ) 中,該部分是微不足道的。

情況2.和3.的處理也有一個共同的部分,分數的計算

n / m

其中nm沒有共同的素因數(並且m > 1 )。 我們可以將n = q*m + r寫成0 <= r < m (用余數除法, r = n % m ),q 是分數的整數部分,相當無趣。

由於假設分數減少了,我們有r > 0 ,所以我們想要找到分數r / m的展開式,其中0 < r < m並且m不能被 2 或 5 整除。 如上所述,這樣的展開式純粹是周期性的,所以找到周期意味着找到完全擴展。

讓我們開始啟發式地查找句點。 所以讓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 |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)

由於rm沒有公因子,這意味着有一個s其中10^k - 1 = s*mp = s*r 如果我們知道k ,周期的長度,我們可以通過計算簡單地找到周期的數字

p = ((10^k - 1)/m) * r

並用前導零填充,直到我們有k位數字。 (注意:只有當k足夠小或可以使用大整數類型時才那么簡單。要使用標准固定寬度整數類型計算例如 17/983 的周期,請使用 @Patrick87 解釋的長除法。)

因此,仍然需要找到周期的長度。 我們可以把上面的推理反過來,發現如果m除以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 所以最短周期的長度是最小正值u使得m除以10^u - 1 ,或者換句話說,最小正值u使得10^u % m == 1

我們可以在 O(m) 時間內找到它

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)不溢出, int可以工作,當然int可以根據需要是 32 位或 64 位整數。

但是對於大分母,這是低效的,可以通過考慮分母的質因數分解來更快地找到周期長度和周期。 關於周期長度,

  • 如果分母是素數冪, m = p^k ,則r/m的周期長度是(p-1) * p^(k-1)的除數
  • 如果ab互質且m = a * b ,則r/m的周期長度是1/a1/b的周期長度的最小公倍數。

綜上所述, r/m的周期長度是λ(m)的除數,其中λCarmichael 函數

因此,要找到的周期長度r/m ,找到的質因數分解m和用於所有素功率因數p^k ,找到的周期1/(p^k) -等同地,10模的乘法順序p^k ,已知它是(p-1) * p^(k-1)的除數。 由於這些數字沒有很多除數,所以很快就完成了。 然后找到所有這些的最小公倍數。

對於句點本身(數字),如果可以使用大整數類型並且句點不太長,則公式

p = (10^k - 1)/m * r

是一種快速計算它的方法。 如果句點太長或沒有可用的大整數類型,則有效地計算數字會更加混亂,而且我不記得這是如何完成的。

一種方法是重復手動長除法的方式,並在每個階段記下余數。 當余數重復時,其余的過程也必須重復。 例如,1.0/7 的數字是 0.1 余數 3 然后 0.14 余數 2 然后 0.142 余數 6 然后 0.1428 余數 4 然后 0.14285 余數 5 然后 0.142857 余數 1 這是再次啟動它的 1 amd 所以你得到 0.142385 重復 0.142385再次從那里。

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

但請注意,您的算法 IsRepeatingDecimal 可能不起作用並且效率低下。

如果您的分數不可約,即存在一個大於 1 的整數可以同時整除您的分子和分母,則它將不起作用。 例如,如果您提供 7/14,那么您的算法將在應該返回 false 時返回 true。

要減少您的分數,請找到分子和分母之間的 gcd,然后將兩者除以這個 gcd。

如果你假設分數是不可約的,那么你的測試

if (Numerator % Denominator == 0)

可以簡單地替換為

if (Denominator == 1)

但這仍然是不必要的,因為如果分母為 1,那么您的列表“素數”將是空的,並且您的算法無論如何都會返回 false。

最后,調用 MathAlgorithms.Primes(Denominator) 對於大數來說會很昂貴並且可以避免。 實際上,您需要做的就是將分母除以 5(分別為 2),直到它不再能被 5 整除(分別為 2)。 如果最終結果為 1,則返回 false,否則返回 true。

我來到這里希望能夠復制和粘貼代碼來執行此操作,但它不存在。 因此,在閱讀@Patrick87 的答案后,我繼續進行了編碼。 我花了一些時間徹底測試它並給它取了一個好聽的名字。 我想我會把它留在這里,這樣其他人就不必浪費時間了。

特點:如果小數終止,它會處理。 它計算周期並將其放入一個名為period的單獨變量中,以防您想知道 reptend 的長度

限制:如果瞬態 + 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}";
    }
}

享受!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM