Самый быстрый способ получить целочисленный мод 10 и целочисленное деление 10?


10

Если аппаратное обеспечение не поддерживает операции модуля или деления, для моделирования модуля / деления с помощью программного обеспечения требуется гораздо больше циклов ЦП. Есть ли более быстрый способ вычисления деления и модуля, если операнд равен 10?

В моем проекте мне часто нужно вычислять целочисленный модуль 10. В частности, я работаю над PIC16F и мне нужно показать число на ЖК-дисплее. Поддерживается 4 цифры, поэтому имеется 4 вызова модуля и функции деления (программная реализация). То есть вроде следующего:

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

digit = number % 10;    // call to an expensive function
number /= 10;           // call to an expensive function
somehow_lit_segments();

Есть и другие области, в которых используется похожий код.


Почему проблема с несколькими дюжинами звонков в секунду? Я бы не стал беспокоиться, если бы проект не был полностью функциональным и не содержал ошибок.
Ник Т

Я заметил, что если я постоянно отображаю какое-то число в основном занятом цикле, реакция кнопки становится медленной. То есть, чтобы обнаружить, что кнопка была нажата, мне нужно нажать эту кнопку немного дольше. Это происходит, когда системные часы работают 32768 Гц.
Донотало

Вы используете прерывания? Почему вы используете 32 кГц XTAL; обычно вы можете снизить энергопотребление, если работаете быстрее и засыпаете во время простоя.
Ник Т

Я использую прерывания. но только для обновления дисплея не стоит переключаться на высокоскоростные колебания. сила мудр. для моего проекта. он должен работать на низких скоростях почти 90% своего времени жизни.
Донотало

2
В общем плане, книга Delight хакера Генри С. Уоррен, младший источник для умного битного-вертела обману. Я искал предложения по делению, и в нем нет ничего для деления на 10, которое превосходит любой из ответов ниже.
RBerteig

Ответы:


11

Вот алгоритм двоичного кода в BCD, который я использовал несколько лет назад на основе одного, найденного здесь . Я использовал внешний драйвер дисплея BCD для 7 сегментов, чтобы результат мог быть записан на соответствующие порты напрямую в виде упакованного BCD для вывода.

Это довольно быстро, если у вас есть аппаратный множитель в PIC, я использовал PIC18F97J60. Если у вас нет аппаратного множителя на вашей PIC, рассмотрите возможность использования shift + add для умножения.

Это принимает беззнаковое 16-битное int и возвращает упакованный BCD с 5 цифрами, его можно изменить и сделать быстрее для 4 цифр. Он использует shift + сложения для приблизительного деления на 10, но с учетом ограниченного входного диапазона он является точным для этого использования. Возможно, вы захотите упаковать результат по-другому, чтобы согласовать, как вы используете результат.

void intToPackedBCD( uint16_t n, uint8_t *digits ) {

    uint8_t d4, d3, d2, d1, d0, q;  //d4 MSD, d0 LSD

    d1 = (n>>4)  & 0xF;
    d2 = (n>>8)  & 0xF;
    d3 = (n>>12) & 0xF;

    d0 = 6*(d3 + d2 + d1) + (n & 0xF);
    q = (d0 * 0xCD) >> 11;
    d0 = d0 - 10*q;

    d1 = q + 9*d3 + 5*d2 + d1;
    q = (d1 * 0xCD) >> 11;
    d1 = d1 - 10*q;

    d2 = q + 2*d2;
    q = (d2 * 0x1A) >> 8;
    d2 = d2 - 10*q;

    d3 = q + 4*d3;
    d4 = (d3 * 0x1A) >> 8;
    d3 = d3 - 10*d4;

    digits[0] = (d4<<4) | (d3);
    digits[1] = (d2<<4) | (d1);
    digits[2] = (d0<<4);
}

отличная ссылка, спасибо! он не только оптимизирует скорость, но и уменьшает размер кода. Я добавил в вашу ссылку «12-разрядный двоичный код до 4 десятичных цифр ASCII», потому что это не требует умножения.
Донотало

8

Предполагая, что целые числа без знака, деление и умножение могут быть сформированы из битовых сдвигов. А из (целочисленного) деления и умножения можно вывести по модулю.

Чтобы умножить на 10:

y = (x << 3) + (x << 1);

Делить на 10 сложнее. Я знаю несколько алгоритмов деления. Если я правильно помню, есть способ быстро разделить на 10, используя сдвиги битов и вычитание, но я не могу вспомнить точный метод. Если это не так, то это алгоритм деления, который управляет <130 циклами . Я не уверен, какую микросхему вы используете, но вы можете использовать ее каким-то образом, даже если вам придется ее портировать.

РЕДАКТИРОВАТЬ: Кто-то говорит на переполнение стека , если вы можете допустить небольшую ошибку и иметь большой временный регистр, это будет работать:

temp = (ms * 205) >> 11;  // 205/2048 is nearly the same as /10

Если у вас есть деление и умножение, по модулю все просто:

mod = x - ((x / z) * z)

6

Вы можете конвертировать из двоичного файла в упакованный BCD без деления, используя алгоритм двойного дублирования . Он использует только сдвиг и добавить 3 .

Например, преобразовать 243 10 = 11110011 2 в двоичный

0000 0000 0000   11110011   Initialization
0000 0000 0001   11100110   Shift
0000 0000 0011   11001100   Shift
0000 0000 0111   10011000   Shift
0000 0000 1010   10011000   Add 3 to ONES, since it was 7
0000 0001 0101   00110000   Shift
0000 0001 1000   00110000   Add 3 to ONES, since it was 5
0000 0011 0000   01100000   Shift
0000 0110 0000   11000000   Shift
0000 1001 0000   11000000   Add 3 to TENS, since it was 6
0001 0010 0001   10000000   Shift
0010 0100 0011   00000000   Shift
   2    4    3
       BCD

Этот алгоритм очень эффективен, когда нет аппаратного делителя. Более того, используется только сдвиг влево на 1, поэтому он работает быстро, даже когда нет сдвига ствола


4

В зависимости от количества цифр, которое вам нужно, вы можете использовать метод грубой силы ( d- номер ввода, t- строка вывода ASCII):

t--;
if (d >= 1000) t++; *t = '0'; while (d >= 1000) { d -= 1000; *t += 1; }
if (d >= 100) t++; *t = '0'; while (d >= 100) { d -= 100; *t += 1;}
if (d >= 10) t++; *t = '0'; while (d >= 10) { d -= 10; *t += 1;}
t++; *t = '0' + d;

Вы также можете изменить кратные if в цикл с степенями десяти, полученными умножением или таблицей поиска.



1

У меня нет хорошего ответа, но на нашем родственном сайте Stack Overflow ведется отличная дискуссия на ту же тему деления и оптимизации по модулю.

Достаточно ли у вас памяти для реализации таблицы поиска?

У Hackers Delight есть статья об оптимальных алгоритмах деления.


нет, не хватает памяти Я хочу сделать это, используя сложение, вычитание и сдвиг битов.
Донотало

1

Рассматривали ли вы постоянное хранение этого значения как BCD (используя простые специальные подпрограммы «BCD increment» и «BCD add») вместо того, чтобы хранить это значение в двоичном виде и конвертировать в BCD по мере необходимости (используя более сложное для понимания «преобразование» из двоичного в BCD "подпрограмма)?

Когда-то все компьютеры хранили все данные в виде десятичных цифр (шестизначные шестерни, вакуумные трубки с кодом два из пяти, BCD и т. Д.), И это наследие все еще сохраняется сегодня. (см. Почему чипы часов реального времени используют BCD ).


Число, отображаемое на ЖК-дисплее, является переменной величиной в диапазоне от -1999 до 1999 года. Оно указывает температуру и рассчитывается в двоичном формате.
Донотало

1

PICList удивительный ресурс для людей программирования процессоров PIC.

BCD преобразование

Рассматривали ли вы использование готовой испытанной подпрограммы двоичного кода в BCD, специально оптимизированной для PIC16F?

В частности, люди из PICList потратили много времени на оптимизацию преобразования двоичных данных в BCD на PIC16F. Эти подпрограммы (каждая из которых оптимизирована для конкретного размера) приведены в разделе «Математические методы преобразования радиоканалов PIC» http://www.piclist.com/techref/microchip/math/radix/index.htm

целочисленное деление и мод

На процессоре, подобном PIC16F, подпрограмма, специализирующаяся на делении на константу, часто намного быстрее, чем обычная процедура «деления переменной А на переменную В». Вы можете указать свою константу (в данном случае «0,1») в разделе «Генерация кода для умножения / деления констант» http://www.piclist.com/techref/piclist/codegen/constdivmul.htm или проверить стандартные процедуры рядом с http://www.piclist.com/techref/microchip/math/basic.htm .


1

Учитывая аппаратное умножение 8x8, можно вычислить divmod-10 числа произвольного размера, используя процедуру, которая вычисляет его для 12-разрядного числа в диапазоне 0-2559 с помощью процедуры:

  1. Предположим, оригинальное число в OrigH: OrigL
  2. Разделите оригинальное число на два и сохраните его в TempH: TempL
  3. Добавьте MSB TempL * 51 в младший бит TempH * 51. Это приблизительное частное
  4. Умножьте приблизительное отношение на 10, отбрасывая MSB значения.
  5. Вычтите младший бит этого результата из младшего разряда исходного числа.
  6. Если это значение равно 10 или больше (максимум будет 19), вычтите 10 и добавьте 1 к приблизительному коэффициенту

Я бы предложил написать процедуру divmod, где MSB числа будет в W, а LSB указан FSR; процедура должна хранить частное в FSR с последующим декрементом и оставлять остаток в W. Чтобы разделить 32-битную длину на 10, можно использовать что-то вроде:

  MOVLW 0
  lfsr 0, _number + 3; Указать на MSB
  позвоните _divmod10_step
  позвоните _divmod10_step
  позвоните _divmod10_step
  позвоните _divmod10_step

Шаг divmod-6 будет очень похожим, за исключением использования констант 85 и 6, а не 51 и 10. В любом случае, я ожидаю, что divmod10_step будет 20 циклов (плюс четыре для вызова / возврата), поэтому короткий divmod10 будет будет около 50 циклов, а длинный divmod10 будет около 100 (если один особый случай первый шаг, можно сохранить несколько циклов).


1

это может быть не самый быстрый, но простой способ.

 a = 65535;

    l = 0;
    m = 0;
    n = 0;
    o = 0;
    p = 0;

    while (a >= 10000)
    {   a -= 10000;
        l += 1;
    }
     while (a >= 1000)
    {   a -= 1000;
        m += 1;
    }
     while (a >= 100)
    {   a -= 100;
        n += 1;
    }
     while (a >= 10)
    {   a -= 10;
        o += 1;
    }
     while (a > 0)
    {   a -= 1;
        p += 1;
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.