【问题标题】:Is there a more efficient way of splitting a number into its digits?有没有更有效的方法将数字拆分成数字?
【发布时间】:2012-03-02 17:49:11
【问题描述】:

为了在 LCD 上显示它,我必须将一个数字分成数位。现在我使用以下方法:

pos = 7;

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

这种方法的问题是除法和模运算在 MSP430 微控制器上非常慢。这种方法有什么替代方法,或者不涉及除法,或者减少操作次数?

注意:我不能使用任何库函数,例如itoa。这些库很大,而且函数本身也很耗资源(在周期数和 RAM 使用方面)。

【问题讨论】:

  • 这看起来像是一个微优化,但我编写的示例代码大约需要 2.5 毫秒,如果您使用电池供电,这已经是很长时间了。
  • 一些编译器能够将除法和模优化为乘法和移位。您使用什么编译器和优化?
  • 巴塞尔:完全正确。如果您使用一些最近启用了优化的 gcc(例如 -O2 标志),则应该对其进行优化。
  • 我正在使用 IAR,现在优化设置为低(尽管它对这部分代码没有影响)。除法需要很长时间,尤其是当 val 是 uint32_t 时。每次操作大约需要 450 个周期。

标签: c performance digits


【解决方案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.1ms 下降到不到 700us。
  • 您应该将 num 声明为 const,因为它是一个常量查找表。除了“const-correctness”的好处之外,它还可能使代码在某些平台上更高效。
  • @Lundin 绝对! num 数组在使用它的文件中应该是static const
  • @alex 将 static 关键字与 const 一起使用是一种可以争论的做法。由于在文件范围内声明 const 表变量最有意义,因此默认情况下它们将获得静态存储持续时间。此外,这完全取决于 const 是否应该可以从多个文件访问,或者它是否应该仅由当前文件使用。 const 变量是 C 中少数可以拥有全局变量的情况之一,因为没有外部调用者可以更改其内容以实现意大利面条式代码。
【解决方案2】:

是的,another way,最初是由 Terje Mathiesen 发明的(至少是 AFAIK)。你(有点)乘以倒数,而不是除以 10。当然,诀窍在于整数不能直接表示倒数。为了弥补这一点,您使用缩放整数。如果我们有浮点,我们可以用类似这样的方式提取数字:

input = 123

first digit = integer(10 * (fraction(input * .1))
second digit = integer(100 * (fraction(input * .01))

... 以此类推,根据需要设置尽可能多的数字。要对整数执行此操作,我们基本上只需将它们缩放 232 (并将每个向上四舍五入,因为我们将使用截断数学)。在 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 &amp;= (unsigned)-1;,在汇编语言中,您通常可以只使用存储结果的低 32 位寄存器,而忽略高 32 位的任何内容。同样,inter &gt;&gt;= 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 字节数据为代价简化了代码。消除它也很容易,但我暂时没有打扰。

在任何情况下,取消除法后,在许多缺乏专用除法硬件的中低端处理器上,这一速度会大大提高。

【讨论】:

  • 它可以工作,虽然我没有机会在实际的开发板上试用它,看看它的性能如何。谢谢!
  • @alex:稍微看了一下,我不太确定它在这种情况下的效果如何。 MSP 430 显然缺少乘法指令,这可能会对此有所伤害。
【解决方案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 */
    

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

    你可以看看这个:Most optimized way to calculate modulus in C

    【讨论】:

    • 我不能使用任何库函数,它们占用大量代码空间,而且通常非常耗资源。
    【解决方案5】:

    这是我对完整解决方案的尝试。信用应该归功于 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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-22
      • 1970-01-01
      • 2018-05-20
      • 2021-05-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多