获得整数mod 10和整数除数10的最快方法?


10

如果硬件不支持模数或除法运算,则需要更多的CPU周期来通过软件模拟模数/除法。如果操作数为10,有没有更快的方法来计算除法和模数?

在我的项目中,我经常需要计算整数模数10。特别是,我正在PIC16F上工作,需要在LCD上显示一个数字。有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();

还有其他地区使用类似的代码。


为什么几十个电话/秒是一个问题?除非该项目功能齐全且没有错误,否则我不会打扰。
尼克T

我注意到,如果我在主忙循环中连续显示一些数字,则按钮响应会变慢。即,要检测到某个按钮已被按下,我必须再按一下该按钮。当系统时钟运行32768 Hz时,会发生这种情况。
Donotalo 2011年

您在使用中断吗?为什么要使用32kHz xtal?通常,如果您操作得更快并在空闲时进入睡眠状态,则可能会降低电源性能。
尼克T

我正在使用中断。但是仅更新显示就不值得切换到高速振荡。明智的。为我的项目。它必须以低速时钟运行近其使用寿命的90%。
Donotalo 2011年

2
作为一般说明,小亨利·沃伦(Henry S. Warren,Jr.)所著的《骇客的喜悦》一书是巧妙地纠结诡计源头。我在寻找除法建议,并且没有除以10的结果,该结果优于以下任何答案。
RBerteig

Answers:


11

这是我几年前根据在这里找到的一种BCD二进制算法。我使用的是外部BCD到7段显示驱动器,因此结果可以作为打包的BCD直接写到正确的端口以输出。

如果您在PIC中有硬件乘法器,那将是相当快的,我使用的是PIC18F97J60。如果您的PIC上没有硬件乘法器,请考虑将shift +加法用于乘法。

这将接受一个无符号的16位int并返回5位压缩的BCD,可以对其进行修改并使它更快地达到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十进制数字”,因为它不涉及任何乘法。
Donotalo 2011年

8

假设无符号整数,可以通过移位形成除法和乘法。从(整数)除法和乘法中,可以得出模。

乘以10:

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

除以10更困难。我知道几种除法算法。如果我没记错的话,可以使用移位和减法快速除以10,但是我不记得确切的方法。如果不是这样,那么这是一个管理<130个周期的除法算法。我不确定您使用的是哪个Micro,但是即使您必须移植它,也可以通过某种方式使用它。

编辑:有人在Stack Overflow上说,如果您可以容忍一些错误并拥有一个大的临时寄存器,那么它将起作用:

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

假设您有除法和乘法,取模很简单:

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

6

您可以使用double dabble算法将二进制从BCD转换为压缩的BCD,而无需进行任何除法。它仅使用shift加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更改为循环,通过乘法或查找表获得10的幂。


2

本应用笔记介绍了用于BCD算术的算法,包括从二进制到BCD的转换,反之亦然。该应用笔记由Atmel(AVR)提供,但所描述的算法与处理器无关。


1

我没有一个好的答案,但是在我们的姐妹网站Stack Overflow上,有一个关于除法和模优化的完全相同主题的精彩讨论

您是否有足够的内存来实现查找表?

Hackers Delight有一篇关于最佳除法算法的论文


不,没有足够的内存。我想使用加,减和位移来做到这一点。
Donotalo 2011年

1

您是否考虑过始终将值保留为BCD(使用简单的特殊“ BCD增量”和“ BCD加”子例程),而不是将值保留为二进制形式并根据需要转换为BCD(使用更难以理解的“转换”从二进制到BCD”子例程)?

一次,所有计算机都将所有数据存储为十进制数字(十位齿轮,五分之二的代码真空管,BCD等),而这一遗留至今仍然存在。(请参阅为什么实时时钟芯片使用BCD)。


LCD上显示的数字是变量,范围是-1999到1999。它表示温度,并以二进制格式计算。
Donotalo 2011年

1

PICList是人们编写PIC处理器的绝佳资源。

BCD转换

您是否考虑过使用专门为PIC16F优化的现成的经过测试的二进制转BCD子程序?

特别是,PICList上的人员花费了大量时间来优化PIC16F上的二进制到BCD的转换。这些例程(每个例程针对特定大小进行了手动优化)在“ PIC Microcontoller Radix Conversion Math Methods”(http://www.piclist.com/techref/microchip/math/radix/index.htm)中进行了概述

整数除法和模

在PIC16F之类的CPU上,专门用于除以常数的子例程通常比通用的“将变量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:该例程通过以下过程为0-2559范围内的12位数字进行计算:

  1. 假设原始数字在OrigH:OrigL中
  2. 将原始数字除以二,然后将其存储在TempH:TempL中
  3. 将TempL * 51的MSB添加到TempH * 51的LSB。那是近似商
  4. 将近似商乘以10,丢弃该值的MSB。
  5. 从原始编号的LSB中减去该结果的LSB。
  6. 如果该值等于或大于10(最大值为19),请减去10并在近似商中加1

我建议编写一个divmod例程,该数字的MSB将在W中,而FSR指向LSB。该例程应将商后减后存储在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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.