Есть ли более эффективный способ разделения числа на его цифры?
Мне нужно разбить число на свои цифры, чтобы отобразить его на ЖК-дисплее. Сейчас я использую следующий метод:
pos = 7;
do
{
LCD_Display(pos, val % 10);
val /= 10;
pos--;
} while (pos >= 0 && val);
Проблема с этим методом заключается в том, что операции деления и по модулю чрезвычайно медленны на микроконтроллере MSP430. Есть ли альтернатива этому методу, что либо не связано с делением, либо уменьшает количество операций?
Примечание. Я не могу использовать какие-либо библиотечные функции, такие как itoa
. Библиотеки большие, а сами функции довольно ресурсоемкие (как с точки зрения количества циклов, так и использования ОЗУ).
Ответы
Ответ 1
Вы можете делать вычитания в цикле с предопределенными значениями базы 10.
Мой C немного ржавый, но что-то вроде этого:
int num[] = { 10000000,1000000,100000,10000,1000,100,10,1 };
for (pos = 0; pos < 8; pos++) {
int cnt = 0;
while (val >= num[pos]) {
cnt++;
val -= num[pos];
}
LCD_Display(pos, cnt);
}
Ответ 2
Да, там другой способ, изначально изобретенный (по крайней мере, AFAIK) Терье Матиесеном. Вместо того, чтобы делить на 10, вы (вроде) умножаетесь на обратную. Трюк, конечно же, заключается в том, что в целых числах вы не можете напрямую представлять обратную связь. Чтобы компенсировать это, вы работаете с масштабированными целыми числами. Если бы мы имели плавающую точку, мы могли бы извлечь цифры с чем-то вроде:
input = 123
first digit = integer(10 * (fraction(input * .1))
second digit = integer(100 * (fraction(input * .01))
... и т.д. столько цифр, сколько необходимо. Чтобы сделать это с помощью целых чисел, мы в основном просто масштабируем их на 2 32 (и каждый раунд, так как мы будем использовать усечение математики). В C алгоритм выглядит следующим образом:
#include <stdio.h>
// here are our scaled factors
static const unsigned long long factors[] = {
3435973837, // ceil((0.1 * 2**32)<<3)
2748779070, // ceil((0.01 * 2**32)<<6)
2199023256, // etc.
3518437209,
2814749768,
2251799814,
3602879702,
2882303762,
2305843010
};
static const char shifts[] = {
3, // the shift value used for each factor above
6,
9,
13,
16,
19,
23,
26,
29
};
int main() {
unsigned input = 13754;
for (int i=8; i!=-1; i--) {
unsigned long long inter = input * factors[i];
inter >>= shifts[i];
inter &= (unsigned)-1;
inter *= 10;
inter >>= 32;
printf("%u", inter);
}
return 0;
}
Операции в цикле будут отображаться непосредственно на инструкции для большинства 32-разрядных процессоров. Ваша типичная команда умножения займет 2 32-битных входа и даст 64-битный результат, который нам нужен именно здесь. Обычно это будет немного быстрее, чем инструкция деления. В типичном случае некоторые операции будут (или, по крайней мере, с некоторой осторожностью, могут) исчезнуть на языке ассемблера. Например, когда я сделал inter &= (unsigned)-1;
, на языке ассемблера вы обычно можете использовать нижний 32-разрядный регистр, где был сохранен результат, и просто игнорировать все, что содержит верхние 32 бита. Аналогично, inter >>= 32;
просто означает, что мы используем значение в верхнем 32-битном регистре и игнорируем нижний 32-разрядный регистр.
Например, на языке ассемблера x86 это выглядит примерно так:
mov ebx, 9 ; maximum digits we can deal with.
mov esi, offset output_buffer
next_digit:
mov eax, input
mul factors[ebx*4]
mov cl, shifts[ebx]
shrd eax, edx, cl
mov edx, 10 ; overwrite edx => inter &= (unsigned)-1
mul edx
add dl, '0'
mov [esi], dl ; effectively shift right 32 bits by ignoring 32 LSBs in eax
inc esi
dec ebx
jnz next_digit
mov [esi], bl ; zero terminate the string
На данный момент я обманул крошечный бит и написал код, предполагающий дополнительный элемент в начале каждой таблицы (factors
и shifts
). Это не является строго необходимым, но упрощает код за счет потери 8 байтов данных. Это тоже очень легко покончить с этим, но на данный момент я не беспокоился.
В любом случае, устранение этого подразделения значительно ускоряет его на довольно многих процессорах с низким и средним уровнем, которым не хватает специализированного оборудования.
Ответ 3
Другой способ - использовать double dabble. Это способ преобразования двоичного кода в BCD только с добавлением и сдвигами бит, что очень подходит для микроконтроллеров. После разделения на BCD вы можете легко распечатать каждое число
Ответ 4
Я бы использовал временную строку, например:
char buffer[8];
itoa(yourValue, buffer, 10);
int pos;
for(pos=0; pos<8; ++pos)
LCD_Display(pos, buffer[pos]); /* maybe you'll need a cast here */
edit: поскольку вы не можете использовать библиотеку itoa, тогда Я думаю, что ваше решение уже является лучшим, обеспечивая компиляцию с включенной оптимизацией max.
Вы можете взглянуть на это: Самый оптимизированный способ расчета модуля в C
Ответ 5
Это моя попытка полного решения. Кредит должен пойти к Гуффе для обеспечения общей идеи. Это должно работать для 32-битных целых чисел, подписанных или иным образом и 0.
#include <stdlib.h>
#include <stdio.h>
#define MAX_WIDTH (10)
static unsigned int uiPosition[] = {
1u,
10u,
100u,
1000u,
10000u,
100000u,
1000000u,
10000000u,
100000000u,
1000000000u,
};
void uitostr(unsigned int uiSource, char* cTarget)
{
int i, c=0;
for( i=0; i!=MAX_WIDTH; ++i )
{
cTarget[i] = 0;
}
if( uiSource == 0 )
{
cTarget[0] = '0';
cTarget[1] = '\0';
return;
}
for( i=MAX_WIDTH -1; i>=0; --i )
{
while( uiSource >= uiPosition[i] )
{
cTarget[c] += 1;
uiSource -= uiPosition[i];
}
if( c != 0 || cTarget[c] != 0 )
{
cTarget[c] += 0x30;
c++;
}
}
cTarget[c] = '\0';
}
void itostr(int iSource, char* cTarget)
{
if( iSource < 0 )
{
cTarget[0] = '-';
uitostr((unsigned int)(iSource * -1), cTarget + 1);
}
else
{
uitostr((unsigned int)iSource, cTarget);
}
}
int main()
{
char szStr[MAX_WIDTH +1] = { 0 };
// signed integer
printf("Signed integer\n");
printf("int: %d\n", 100);
itostr(100, szStr);
printf("str: %s\n", szStr);
printf("int: %d\n", -1);
itostr(-1, szStr);
printf("str: %s\n", szStr);
printf("int: %d\n", 1000000000);
itostr(1000000000, szStr);
printf("str: %s\n", szStr);
printf("int: %d\n", 0);
itostr(0, szStr);
printf("str: %s\n", szStr);
return 0;
}