Алгоритм обнаружения повторяющихся десятичных знаков?

Есть ли алгоритм для выяснения следующих вещей?

  1. Если результатом деления является повторяющееся десятичное число (в двоичном формате).
  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

Есть ли способ сделать это? Эффективность вызывает большие опасения. Описание алгоритма предпочтительнее кода, но я приму тот ответ, который смогу получить.

Также стоит отметить, что база не имеет большого значения; Я могу преобразовать алгоритм в двоичный (или, если он находится, скажем, в базе 256, чтобы использовать chars для простоты, я мог бы просто использовать это). Я говорю это, потому что, если вы объясняете, вам может быть проще объяснить в базе 10 :).


person Imagist    schedule 22.08.2009    source источник
comment
Какие еще условия вы использовали для получения результата? Почему повторяющиеся цифры не 01, 01, 10 и 0011?   -  person Guffa    schedule 22.08.2009
comment
@Guffa Моей причиной было поставить 1 на первое место, потому что начальные нули не [значимы] [1], а конечные нули — да. Если бы число было примерно таким, как 111.010101..., повторяющиеся числа были бы 01, потому что в этом случае первый 0 является значащим. [1]:en.wikipedia.org/wiki/Significant_digits   -  person Imagist    schedule 22.08.2009
comment
@Guffa (продолжение) Для меня это не важно. Если бы вы сказали мне, как это сделать так, чтобы возвращались 01, 01, 01 и 0011, я был бы счастлив. :)   -  person Imagist    schedule 22.08.2009
comment
Похоже на projecteuler.net/problem=26 ;-)   -  person schoetbi    schedule 15.01.2014


Ответы (6)


Чтобы найти повторяющийся шаблон, просто следите за значениями, которые вы используете в строке:

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.
person Guffa    schedule 22.08.2009
comment
Я не уверен, что это решит его проблему, потому что некоторые дроби повторяются после определенного количества цифр, например, 5/6 = 0,8333333. поэтому в вашей модели он будет использовать 8, чтобы найти повторение. - person user20844; 22.08.2009
comment
@letseatunch: 5/6 = 101/110 = 0,11010101010101010... Если вы запустите FindPattern(5,6), он найдет шаблон, повторяющийся от цифры -2 с длиной 2. - person Guffa; 22.08.2009
comment
Мне потребовалось некоторое время, чтобы понять ваш код, потому что я не очень хорошо знаю C#, но я думаю, что это именно то, что я искал. Я пишу это на С++, и хранение чисел не совсем такое, но должно быть достаточно легко перенести это. Спасибо большое за помощь! - person Imagist; 22.08.2009

  1. если делитель не является степенью числа 2 (как правило, содержит простые множители, не являющиеся общими с базой представления)
  2. длина повторяющегося цикла будет зависеть от наибольшего простого множителя делимого (но не связана с длиной представления этого множителя — см. 1/7 в десятичном виде), но длина первого цикла может отличаться от единицы повторения (например, 11/28 = 1/4+1/7 в десятичном виде).
  3. фактический цикл будет зависеть от числителя.
person Steve Gilham    schedule 22.08.2009
comment
+1 Спасибо за ваш комментарий. Это дает мне некоторое представление о проблеме. В частности, важна идея о том, что продолжительность цикла и фактический цикл определяются разными факторами. Я знал, что это будет важно для хранения цикла, но я не думал, что это может быть важно для расчета цикла. Однако я до сих пор не понимаю, как рассчитать информацию. - person Imagist; 22.08.2009

Я могу дать подсказку: повторяющиеся десятичные дроби в основании десять являются дробями со знаменателем, имеющим по крайней мере один простой множитель, отличный от двух и пяти. Если знаменатель не содержит простых множителей два или пять, их всегда можно представить со знаменателем, состоящим из всех девяток. Тогда числитель — это повторяющаяся часть, а количество девяток — длина повторяющейся части.

3     _
- = 0.3
9

1   142857     ______
- = ------ = 0.142857
7   999999

Если в знаменателе есть простые множители два или пять, повторяющаяся часть начинается не с первой позиции.

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

Но я не могу вспомнить, как получить неповторяющуюся часть и ее длину.

Кажется, это хорошо переводится в основание два. Неповторяющимися являются только дроби со знаменателем степени двойки. Это можно легко проверить, утверждая, что установлен только один бит в знаменателе.

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

Что касается основания десять, я не могу сказать, как обращаться со знаменателями, содержащими, но не являющимися степенью двойки, например, 12 = 3 * 2^2.

person Daniel Brückner    schedule 22.08.2009
comment
+1 По этой логике в основании 2 повторяющиеся десятичные дроби - это дроби со знаменателями, имеющими простые множители, отличные от 2 (я знал это). Я не знал, что если у них есть простой делитель 1, он начинается не с первой позиции (это полезная информация!). - person Imagist; 22.08.2009

Во-первых, один из ваших примеров неверен. Повторяющаяся часть 1/5 — это 0011, а не 1100, и она начинается в самом начале дробной части.

Повторяющееся десятичное число выглядит примерно так:

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

в котором n и d - это то, что вы хотите.

Например,

1/10(dec) = 1/1010(bin) = 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. Ключевая часть повторяющегося десятичного представления генерируется путем деления на (2n - 1) или на любое его число, кратное 2. Таким образом, вы можете либо найти способ выразить свой знаменатель как таковой (например, построить постоянные таблицы), либо выполнить деление больших чисел (что относительно медленно) и найдите петлю. Нет быстрого способа сделать это.

person Todd Li    schedule 22.08.2009
comment
+1 за ваш технический вклад. Однако метод Гуффы кажется довольно эффективным и кажется, что он будет линейным по отношению к длине числа, что достаточно быстро, учитывая, что он, вероятно, будет чаще всего использоваться с меньшими числами. Хотя это позволяет мне поддерживать операции с плавающей запятой произвольной точности, реальная цель состоит в том, чтобы поддерживать точность чисел с основанием 10 (т. е. в большинстве языков 1.1 основание 10 дает 1,100000001 или что-то в этом роде из-за повторяющихся десятичных знаков). - person Imagist; 22.08.2009
comment
На самом деле есть лучшие способы, учитывая вашу цель: вы можете хранить рациональные числа в виде дробей вместо их расширения, или вы можете просто выполнять математику в базе 10. Обработка повторяющихся десятичных дробей не совсем проста, как я себе представляю. :) - person Todd Li; 22.08.2009

Ознакомьтесь с десятичным расширением и особенно о периоде дроби.

person Nick Dandoulakis    schedule 22.08.2009
comment
+1 Спасибо за ваш пост. Это помогло мне понять проблему. - person Imagist; 22.08.2009

Вы можете выполнить полное деление, отмечая остатки. Структура остатков даст вам структуру любого рационального десятичного числа:

  1. последний остаток равен нулю: это десятичная дробь без повторяющейся части
  2. первый и последний остаток равны: десятичная дробь повторяется сразу после точки
  3. расстояние между первым и первым остатком равно последнему - неповторяющиеся цифры, остаток - повторяющаяся часть

В общем, расстояния дадут вам количество цифр для каждой части.

Вы можете увидеть этот алгоритм в кодировке C++ в методе decompose() здесь.

Попробуйте 228142/62265, у него есть период 1776 цифр!

person Heiko Schäfer    schedule 07.12.2015