问题
我正在寻找一些关于如何优化将整数的数字打印uint32_t num = 1234567890;
到带有 Arduino UNO 的字符显示的输入。要考虑的主要指标是内存使用量和编译大小。显示速度太慢了,没有任何提高速度的意义,最小代码长度虽然不错,但不是必需的。
目前,我正在使用提取最低有效数字num%10
,然后删除该数字num/10
,依此类推,直到num
提取所有数字。使用递归,我可以颠倒打印的顺序,因此只需要很少的操作(作为明确的代码行)就可以按正确的顺序打印数字。使用for
循环我需要找到用于写入数字的字符数,然后存储它们,然后才能以正确的顺序打印它们,需要一个数组和 3 个for
循环。
根据 Arduino IDE,当打印各种有符号和无符号整数时,递归使用2010/33字节的存储/内存,而使用扩展类的库时,迭代使用2200/33字节和 2474/52字节。Adafruit_CharacterOLED
Print
有没有办法比我在下面使用递归和迭代编写的函数更好地实现这一点?如果不是,你更喜欢哪一个,为什么? 我觉得可能有更好的方法可以用更少的资源做到这一点——但也许我是堂吉诃德与风车搏斗,代码已经足够好了。
背景
我正在使用 NHD-0420DZW 字符 OLED 显示器,并使用 Newhaven 数据表和 LiquidCrystal 库作为编写我自己的库的指南,并且显示器运行良好。然而,为了尽量减少代码膨胀,我选择不让我的显示库成为 的子类Print
,它是 Arduino 核心库的一部分。在此过程中,已经实现了存储空间(~400 字节)和内存(~19 字节)的显着节省(ATmega328P 具有 32k 存储空间和 2k RAM,因此资源稀缺)。
递归
如果我使用递归,打印方法相当优雅。该数字除以 10,直到达到基本情况为零。然后打印最小数字的最低有效位(num的MSD),下一个最小数字的LSD(num的第二个MSD)等等,导致最终的打印顺序颠倒。%10
这更正了使用和/10
操作的数字提取的相反顺序。
// print integer type literals to display (base-10 representation)
void NewhavenDZW::print(int8_t num) {print(static_cast<int32_t>(num));}
void NewhavenDZW::print(uint8_t num) {print(static_cast<uint32_t>(num));}
void NewhavenDZW::print(int16_t num) {print(static_cast<int32_t>(num));}
void NewhavenDZW::print(uint16_t num) {print(static_cast<uint32_t>(num));}
void NewhavenDZW::print(int32_t num) {
if(num < 0) { // print negative sign if present
send('-', HIGH); // and make num positive
print(static_cast<uint32_t>(-num));
} else
print(static_cast<uint32_t>(num));
}
void NewhavenDZW::print(uint32_t num) {
if(num < 10) { // print single digit numbers directly
send(num + '0', HIGH);
return;
} else // use recursion to print nums with more
recursivePrint(num); // than two digits in the correct order
}
// recursive method for printing a number "backwards"
// used to correct the reversed order of digit extraction
void NewhavenDZW::recursivePrint(uint32_t num) {
if(num) { // true if num>0, false if num==0
recursivePrint(num/10); // maximum of 11 recursive steps
send(num%10 + '0', HIGH); // for a 10 digit number
}
}
迭代
由于数字提取方法从 LSD 而不是 MSD 开始,所以提取的数字不能直接打印,除非我移动光标并告诉显示器从右到左打印。所以我必须在提取数字时存储它们,然后才能以正确的顺序将它们写入显示器。
void NewhavenDZW::print(uint32_t num) {
if(num < 10) {
send(num + '0', HIGH);
return;
}
uint8_t length = 0;
for(uint32_t i=num; i>0; i/=10) // determine number of characters
++length; // needed to represent number
char text[length];
for(uint8_t i=length; num>0; num/=10, --i)
text[i-1] = num%10 + '0'; // map each numerical digit to
for(uint8_t i=0; i<length; i++) // its char value and fix ordering
send(text[i], HIGH); // before printing result
}
更新
最终,递归占用最少的存储空间,但可能使用最多的内存。
在查看了 Igor G 和 darune 提供的代码,以及查看 Godbolt 上列出的指令数量(由 darune 和 old_timer 讨论)后,我相信 Igor G 的解决方案是最好的整体。在测试期间,darune 的函数(使用语句停止前导零并能够打印)编译为2076字节与2096字节。当附加必要的语句时,它还需要比 darune 的 (273) 更少的指令 (88) 。if
0
if
使用指针变量
void NewhavenDZW::print(uint32_t num) {
char buffer[10];
char* p = buffer;
do {
*p++ = num%10 + '0';
num /= 10;
} while (num);
while (p != buffer)
send(*--p, HIGH);
}
使用索引变量
这就是我最初的for
循环试图做的,但是以一种天真的方式。正如 Igor G 所指出的那样,试图最小化缓冲区数组的大小确实没有意义。
void NewhavenDZW::print(uint32_t num) {
char text[10]; // signed/unsigned 32-bit ints are <= 10 digits
uint8_t i = sizeof(text) - 1; // set index to end of char array
do {
text[i--] = num%10 + '0'; // store each numerical digit as
num /= 10; // its associated char value
} while (num);
while (i < sizeof(text))
send(text[i++], HIGH); // print num in the correct order
}
替代方案
这是 darune 的函数,添加了 if 语句,供那些不想筛选评论的人使用。条件pow10 == 100
与 相同pow10 == 1
,但在具有相同编译大小的情况下保存了循环的两次迭代以打印零。
void NewhavenDZW::print(uint32_t num) {
for (uint32_t pow10 = 1000000000; pow10 != 0; pow10 /= 10)
if (num >= pow10 || (num == 0 && pow10 == 100))
send((num/pow10)%10 + '0', HIGH);
}