【问题标题】:Algorithm for detecting repeating decimals?检测重复小数的算法?
【发布时间】:2010-11-21 21:12:22
【问题描述】:

有没有一种算法可以解决以下问题?

  1. 如果除法的结果是重复的小数(二进制)。
  2. 如果重复,从哪个数字(表示为 2 的幂)开始?
  3. 哪些数字重复?

一些例子:

1/2 = 1/10 = 0.1 // 1 = false, 2 = N/A, 3 = N/A, 4 = N/A
1/3 = 1/11 = 0.010101... // 1 = true, 2 = -2, 3 = 10
2/3 = 10/11 = 0.101010... // 1 = true, 2 = -1, 3 = 10
4/3 = 100/11 = 1.010101... // 1 = true, 2 = 0, 3 = 10
1/5 = 1/101 = 0.001100110011... // 1 = true, 2 = -3, 3 = 1100

有没有办法做到这一点?效率是一个大问题。算法的描述比代码更受欢迎,但我会接受我能得到的答案。

还值得注意的是,基地并不是什么大问题。我可以将算法转换为二进制(或者如果它在,比如 base 256 以方便使用chars,我可以使用它)。我这样说是因为如果你在解释,你可能会更容易在 base 10 中解释:)。

【问题讨论】:

  • 您还使用了哪些条件来获得结果?为什么不重复“01”、“01”、“10”和“0011”的数字?
  • @Guffa 我的理由是把 1 放在第一位,因为前导零不是 [显着][1],而尾随零是。如果数字类似于“111.010101...”,则重复数字将是“01”,因为在这种情况下,第一个 0 很重要。 [1]:en.wikipedia.org/wiki/Significant_digits
  • @Guffa(续) 不过这对我来说并不重要。如果您告诉我如何以返回“01”、“01”、“01”和“0011”的方式执行此操作,我会很高兴。 :)
  • 听起来像projecteuler.net/problem=26 ;-)

标签: algorithm rational-numbers


【解决方案1】:

你可以做一个 long division,注意其余部分。余数的结构将为您提供任何有理小数的结构:

  1. 最后一个余数为零:它是一个没有任何重复部分的小数
  2. 第一个和最后一个余数相等:小数点在点之后重复
  3. 第一个和第一个余数等于最后一个的距离是不重复的数字,余数是重复的部分

一般来说,距离会给你每个部分的位数。

你可以在方法decompose()here中看到这个算法用C++编码。

Try228142/62265,它有一个1776位的句号!

【讨论】:

    【解决方案2】:
    1. 如果除数不是 2 的幂(通常,包含不与表示基数共享的素数)
    2. 重复周期长度将由被除数的最大素因子驱动(但与该因子表示的长度无关——见十进制的 1/7),但第一个周期长度可能与重复周期不同单位(例如 11/28 = 1/4+1/7 十进制)。
    3. 实际周期取决于分子。

    【讨论】:

    • +1 感谢您的评论。这让我对这个问题有了一些了解。特别是周期长度和实际周期由不同因素驱动的想法很重要。我知道这对于存储循环很重要,但我不认为它对于计算循环可能很重要。但是,我仍然看不到如何计算信息。
    【解决方案3】:

    要查找重复模式,只需跟踪沿线使用的值:

    1/5 = 1/101:
    
    1 < 101 => 0
    (decimal separator here)
    10 < 101 => 0
    100 < 101 => 0
    1000 >= 101 => 1
    
      1000 - 101 = 11
    
    110 >= 101 => 1
    
      110 - 101 = 1
    
    10 -> match
    

    当您达到与第二位相同的值时,该过程将从该点重复,一遍又一遍地产生相同的位模式。您的模式“0011”从第二位重复(小数点后的第一个分隔符)。

    如果您希望模式以“1”开头,您可以旋转它直到它匹配该条件:

    "0011" from the second bit
    "0110" from the third bit
    "1100" from the fourth bit
    

    编辑:
    C# 中的示例:

    void FindPattern(int n1, int n2) {
       int digit = -1;
       while (n1 >= n2) {
          n2 <<= 1;
          digit++;
       }
       Dictionary<int, int> states = new Dictionary<int, int>();
       bool found = false;
       while (n1 > 0 || digit >= 0) {
          if (digit == -1) Console.Write('.');
          n1 <<= 1;
          if (states.ContainsKey(n1)) {
             Console.WriteLine(digit >= 0 ? new String('0', digit + 1) : String.Empty);
             Console.WriteLine("Repeat from digit {0} length {1}.", states[n1], states[n1] - digit);
             found = true;
             break;
          }
          states.Add(n1, digit);
          if (n1 < n2) {
             Console.Write('0');
          } else {
             Console.Write('1');
             n1 -= n2;
          }
          digit--;
       }
       if (!found) {
          Console.WriteLine();
          Console.WriteLine("No repeat.");
       }
    }
    

    用你的例子调用它输出:

    .1
    No repeat.
    .01
    Repeat from digit -1 length 2.
    .10
    Repeat from digit -1 length 2.
    1.0
    Repeat from digit 0 length 2.
    .0011
    Repeat from digit -1 length 4.
    

    【讨论】:

    • 我不确定这是否能解决他的问题,因为某些分数在一定位数后重复,例如 5/6 = .8333333。所以在你的模型下,它会使用 8 来查找重复。
    • @letseatunch: 5/6 = 101/110 = 0.11010101010101010... 如果你运行 FindPattern(5,6) 它会发现从数字 -2 开始重复的模式,长度为 2。
    • 我花了一点时间来理解您的代码,因为我不太了解 C#,但我认为这正是我所期待的。我是用 C++ 编写的,数字存储并非完全如此,但移植它应该很容易。非常感谢您的帮助!
    【解决方案4】:

    我可以给出一个提示 - 以十为底的重复小数都是分数,分母至少有一个除二和五之外的质因数。如果分母不包含质因数二或五,它们总是可以用全九的分母来表示。那么提名者就是重复部分,九的个数就是重复部分的长度。

    3     _
    - = 0.3
    9
    
    1   142857     ______
    - = ------ = 0.142857
    7   999999
    

    如果分母中有两个或五个质因数,则重复部分不在第一个位置开始。

    17    17        ______
    -- = ----- = 0.4857142
    35   5 * 7
    

    但我不记得如何推导出非重复部分及其长度。

    这似乎很好地转化为基数二。只有分母为 2 次方的分数是不重复的。这可以通过断言只设置了分母中的单个位来轻松检查。

    1/2 =   1/10   = 0.1
    1/4 =   1/100  = 0.01
    3/4 =  11/100  = 0.11
    5/8 = 101/1000 = 0.101
    

    所有分母为奇数的分数都应该是重复的,模式和长度可以通过以2^n-1的形式表示分母的分数来获得。

                                                         __
     1/3            =  1/(2^2-1) =        1/11       = 0.01
                                                         __
     2/3            =  2/(2^2-1) =       10/11       = 0.10
                           __
     4/3  => 1 + 1/3 =>  1.01
                           __
    10/3  => 3 + 1/3 => 11.01
                                                         ____
     1/5  =   3/15  =  3/(2^4-1) =       11/1111     = 0.0011
                                                         ________
    11/17 = 165/255 = 11/(2^8-1) = 10100101/11111111 = 0.10100101
    

    至于以十为底,我不知道如何处理包含但不是 2 的幂的分母 - 例如 12 = 3 * 2^2

    【讨论】:

    • +1 按照这个逻辑,以 2 为底,重复小数是分母的质因数不是 2 的分数(我知道这一点)。我不知道如果他们有一个素因子 1,它会从第一个位置以外的地方开始(这​​是有用的信息!)。
    【解决方案5】:

    首先,你的一个例子是错误的。 1/5的重复部分是0011而不是1100,它从小数部分的最开头开始。

    重复小数类似于:

    a/b = c + d(2-n + 2-n-k + 2-n-2k + ...)
        = c + 2-n * d / (1 - 2-k)

    其中nd 是你想要的。

    例如,

    1/10<sub>(dec)</sub> = 1/1010<sub>(bin)</sub> = 0.0001100110011... // 1 = true, 2 = -1, 3 = 0011

    可以用公式表示

    a = 1, b = 10(dec), c = 0, d = 0.0011(bin), n = 1, k = 4;
    (1 - 2-k) = 0.1111

    因此,1/10 = 0.1 * 0.0011/0.1111。重复十进制表示的关键部分是通过除以 (2<sup>n</sup> - 1) 或其任意 2 的倍数生成的。因此,您可以找到一种方法来表达分母(例如构建常量表),或者进行大数除法 (相对较慢)并找到循环。没有快速的方法来做到这一点。

    【讨论】:

    • +1 为您提供技术意见。然而,Guffa 的方法似乎非常有效,并且似乎与数字的长度呈线性关系,这已经足够快了,因为这可能最常用于较小的数字。虽然这使我能够支持任意精度的浮点运算,但真正的目的是保持以 10 为底的数字精确(即,在大多数语言中,1.1 以 10 为底的数字是 1.100000001 或类似的,因为重复小数)。
    • 实际上,根据您的目的,有更好的方法:您可以将有理数保留为分数形式而不是扩展它们,或者您可以简单地以 10 为底进行数学运算。处理重复小数并不像我一样容易会想象。 :)
    【解决方案6】:

    查看decimal expansion,特别是分数的周期。

    【讨论】:

    • +1 感谢您的帖子。这有助于我理解问题。
    猜你喜欢
    • 2011-01-14
    • 2012-11-10
    • 2020-11-18
    • 2014-03-07
    • 1970-01-01
    • 2021-08-16
    相关资源
    最近更新 更多