Найшвидший спосіб отримати ціле число мод 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 для виведення.

Це досить швидко, якщо у вас є апаратний множник у ПОС, я використовував PIC18F97J60. Якщо у вас на ПКС немає апаратного множника, спробуйте скористатися 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 циклів . Я не впевнений, яким мікрофоном ви користуєтесь, але ви можете ним користуватися певним чином, навіть якщо вам доведеться перенести його.

EDIT: Хтось каже в Stack Overflow , якщо ви можете допустити трохи помилок і мати великий тимчасовий регістр, це спрацює:

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;

Ви також можете змінити кілька ifs в цикл, потужність десять отримана множенням або таблицею пошуку.


2

Ця примітка до програми описує алгоритми арифметики BCD, включаючи перетворення з двійкового в BCD і навпаки. Додаток від Atmel, що є AVR, але описані алгоритми не залежать від процесора.


1

Я не маю гарної відповіді, але на нашому сестринському сайті Stack Overflow є велика дискусія щодо тієї самої теми поділу та оптимізації модуля.

Чи вистачає пам'яті для впровадження таблиці пошуку?

Hackers Delight пропонує документ про оптимальні алгоритми поділу.


ні, не вистачає пам'яті. Я хочу це зробити, використовуючи додавання, віднімання та зсув бітів.
Донотало

1

Чи вважали ви весь час утримувати це значення як BCD (використовуючи прості спеціальні "збільшення BCD" та "BCD add" підпрограми), а не утримуючи це значення у двійковій формі та перетворюючи на BCD за необхідності (використовуючи більш складне для розуміння "перетворення від бінарної до підпрограми BCD "?

Свого часу всі комп’ютери зберігали всі дані у вигляді десяткових цифр (десятипозиційні передачі, два з п’яти кодових вакуумних трубок, BCD тощо), і ця спадщина зберігається і сьогодні. (див. Чому тактові мікросхеми в режимі реального часу використовують BCD ).


Число, яке відображатиметься на РК-дисплеї, є змінним, яке становить від -1999 до 1999 р. Це позначає температуру і обчислюється у двійковому форматі.
Донотало

1

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

Перетворення BCD

Чи розглядали ви, як використовувати перевірену підпрограму бінарного до BCD, що перебуває на полиці, спеціально оптимізовану для PIC16F?

Зокрема, люди з PICList витратили багато часу на оптимізацію переходів бінарних в BCD на PIC16F. Ці підпрограми (кожна з яких оптимізована для певного розміру) узагальнені в "Методах математичної конверсії PIC Microcontoller Radix" http://www.piclist.com/techref/microchip/math/radix/index.htm

ціле ділення і мод

У процесорі, подібному до PIC16F, підпрограма, спеціалізована для ділення на константу, часто набагато швидша, ніж у звичайній програмі "розділити змінну A на змінну B". Ви можете поставити константу (у цьому випадку "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 до LSB TempH * 51. Це приблизний коефіцієнт
  4. Помножте приблизний коефіцієнт на 10, відкинувши MSB значення.
  5. Відніміть LSB цього результату від LSB вихідного числа.
  6. Якщо це значення 10 або більше (максимум буде 19), відніміть 10 і додайте 1 до приблизного коефіцієнта

Я б запропонував написати програму divmod, MSB числа якої буде W, а LSB, на яку вказує FSR; рутина повинна зберігати коефіцієнт у FSR з подальшим зменшенням, а решту залишити в W. Щоб поділити 32-бітну довжину на 10, тоді б використовується щось на зразок:

  movlw 0
  lfsr 0, _ число + 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.