有没有更有效的方法将数字分成数字?

时间:2012-02-10 08:19:28

标签: c performance digits

我必须将数字拆分成数字才能在液晶显示屏上显示。现在我使用以下方法:

pos = 7;

do
{
    LCD_Display(pos, val % 10);
    val /= 10;
    pos--;
} while (pos >= 0 && val);

这种方法的问题在于MSP430微控制器上的除法和模运算非常慢。有没有替代这种方法的东西,既不涉及分裂,也不会减少操作次数?

注意:我不能使用任何库函数,例如itoa。这些库很大,而且功能本身也非常耗费资源(无论是循环次数还是RAM使用率)。

5 个答案:

答案 0 :(得分:13)

您可以在具有预定义基数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);
}

答案 1 :(得分:5)

是的,有another way,最初由Terje Mathiesen发明(至少是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

目前,我已经欺骗了一点,并在每个表的开头(factorsshifts)假设一个额外的项目编写代码。这不是严格必要的,但是以浪费8个字节的数据为代价简化了代码。这也很容易取消,但我暂时没有打扰。

在任何情况下,在相当多的缺乏专用分区硬件的中低端处理器上,取消分区会使这一点变得更快。

答案 2 :(得分:1)

另一种方法是使用double dabble。这是一种将二进制转换为BCD的方法,只需要添加和位移,因此非常适合微控制器。分割成BCD后,您可以轻松打印出每个数字

答案 3 :(得分:0)

我会使用一个临时字符串,例如:

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 */

编辑:既然你不能使用库的itoa,那么我认为你的解决方案已经是最好的了,为你编译并打开了最大优化。

您可以看一下:Most optimized way to calculate modulus in C

答案 4 :(得分:0)

这是我尝试完整的解决方案。信用应该去Guffa提供一般的想法。这适用于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;
}