在C中转换为ASCII

使用微控制器(PIC18F4580),我需要收集数据并将其发送到SD卡以供以后分析。 它收集的数据的值介于0和1023之间,或者0x0和0x3FF之间。

所以我需要做的是将1023转换为基本的10个字符串ASCII值(0x31,0x30,0x32,0x33,…)。

我的问题是,我能想到的将数字分开的唯一方法需要进行大量的划分。

char temp[4]; temp[0] = 1023 % 10; temp[1] = (1023 % 100) / 10; temp[2] = (1023 % 1000) / 100; temp[3] = (1023 % 10000) / 1000; 

使用此方法,查找n位十进制数的ASCII值需要2n-1个除法。 有没有更快的方法?

最终目标是结束SD卡上的.csv文件,该文件可以快速插入任何笔记本电脑,以查看Excel中的数据图表。

有一种方法可以使用减法,但我不相信它比在“普通”CPU上使用减法和模数更快(在嵌入式环境中可能不同)。

像这样的东西:

 char makedigit (int *number, int base) { static char map[] = "0123456789"; int ix; for (ix=0; *number >= base; ix++) { *number -= base; } return map[ix]; } char *makestring (int number) { static char tmp[5]; tmp[0] = makedigit(&number, 1000); tmp[1] = makedigit(&number, 100); tmp[2] = makedigit(&number, 10); tmp[3] = makedigit(&number, 1); tmp[5] = '\0'; return tmp; } 

然后,调用makestring()应该导致(静态,所以在覆盖之前复制它)字符串与转换后的数字(零前缀,4个字符宽度,因为原始假设是0-1023范围内的值) 。

显而易见的解决方案是将数据转换为ASCII,而是以二进制格式存储。 这样你需要担心的是数据的字节顺序。 如果执行后面分析的系统比嵌入式目标强大得多,那么让它处理转换和字节顺序是有意义的。

另一方面,与将数据传送到SD卡所花费的时间相比,/和%的执行时间可能是微不足道的。 所以要确保你正在优化正确的事情。

肯定有一种更快的方法:拥有1024个预先计算的字符串数组。 然后你可以做边界检查,然后是数组的索引。

从你的问题不清楚你的代码是否在微控制器上运行。 如果是这种情况,您可能没有足够的内存用于此方法。

我同意Clifford所说的,如果你不需要,你不应该担心优化它,并且你可以将日志清理推送到分析平台,而不是担心嵌入式应用程序中的格式化。

话虽如此,这篇文章可能对您有用。 它使用循环,移位,加法和分支,具有线性/常数复杂性: http : //www.johnloomis.org/ece314/notes/devices/binary_to_BCD/bin_to_bcd.html

另外,我认为制作一些不执行任何除法,乘法或分支的代码会很有趣,但仍会给出正确的答案[0 – 1024]。 没有任何承诺,这比其他选择更快。 这种代码只是一种探索的选择。

我很想知道是否有人可以提供一些技巧来使代码更小,需要更少的内存,或需要更少的操作,同时保持其余的计数相等,或缩小它们:)

统计:

  • 常量中224个字节(不知道代码大小)
  • 5位移位权利
  • 3减去
  • 5位按字节
  • 4位按位
  • 1大于比较

PERF:

在Jonathan Leffler的回答中使用perf比较和itoa例程,这里是我得到的统计数据:

  • 第2.15分部
  • 减法4.87
  • 我的解决方案1.56
  • 蛮力查找0.36

我将迭代计数增加到200000,以确保我没有任何时序分辨率问题,并且必须在函数签名中添加volatile ,以便编译器不会优化循环。 我使用VS2010 express w / vanilla“release”设置,在3ghz双核64位Windows 7机器上(它编译为32位)。

代码:

 #include "stdlib.h" #include "stdio.h" #include "assert.h" void itoa_ten_bits(int n, char s[]) { static const short thousands_digit_subtract_map[2] = { 0, 1000, }; static const char hundreds_digit_map[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, }; static const short hundreds_digit_subtract_map[10] = { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900, }; static const char tens_digit_map[12] = { 0, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9, }; static const char ones_digit_map[44] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3 }; /* Compiler should optimize out appX constants, % operations, and + operations */ /* If not, use this: static const char ones_digit_append_map[16] = { 0, 6, 2, 8, 4, 10, 6, 12, 8, 14, 10, 16, 12, 18, 14, 20, }; */ static const char a1 = 0x10 % 10, a2 = 0x20 % 10, a3 = 0x40 % 10, a4 = 0x80 % 10; static const char ones_digit_append_map[16] = { 0, a1, a2, a1 + a2, a3, a1 + a3, a2 + a3, a1 + a2 + a3, a4, a1 + a4, a2 + a4, a1 + a2 + a4, a3 + a4, a1 + a3 + a4, a2 + a3 + a4, a1 + a2 + a3 + a4, }; char thousands_digit, hundreds_digit, tens_digit, ones_digit; assert(n >= 0 && n < 1024 && "n must be between [0, 1024)"); /* n &= 0x3ff; can use this instead of the assert */ thousands_digit = (n >> 3 & 0x7f) > 0x7c; n -= thousands_digit_subtract_map[thousands_digit]; ones_digit = ones_digit_map[ (n & 0xf) + ones_digit_append_map[n >> 4 & 0xf] + ones_digit_append_map[n >> 8 & 0x3] ]; n -= ones_digit; hundreds_digit = hundreds_digit_map[n >> 3 & 0x7f]; n -= hundreds_digit_subtract_map[hundreds_digit]; tens_digit = tens_digit_map[n >> 3]; s[0] = '0' | thousands_digit; s[1] = '0' | hundreds_digit; s[2] = '0' | tens_digit; s[3] = '0' | ones_digit; s[4] = '\0'; } int main(int argc, char* argv) { int i; for(i = 0; i < 1024; ++i) { char blah[5]; itoa_ten_bits(i, blah); if(atoi(blah) != i) printf("failed %d %s\n", i, blah); } } 

在寻找合适的数字时要小心,你可以乘以基数的倒数而不是除以基数。 Terje的代码适用于x86,但将一般想法移植到PIC应该不是非常困难。

如果值正确地在范围内(0..1023),那么您的最后一次转换在分区上不必要地浪费; 最后一行可以替换为:

 temp[3] = 1023 / 1000; 

甚至:

 temp[3] = 1023 >= 1000; 

由于除法是重复减法,但是你有一个非常特殊的情况(不是一般情况)除法处理,我很想将以下代码的时序与除法版本进行比较。 我注意到你以“逆序”将数字放入字符串 – 最低有效位在temp[0] ,最高在temp[4] 。 此外,在给定存储的情况下,没有机会终止字符串。 此代码使用8字节静态数据表 – 远远少于许多其他解决方案。

 void convert_to_ascii(int value, char *temp) { static const short subtractors[] = { 1000, 100, 10, 1 }; int i; for (i = 0; i < 4; i++) { int n = 0; while (value >= subtractors[i]) { n++; value -= subtractors[i]; } temp[3-i] = n + '0'; } } 

性能测试 – Intel x86_64 Core 2 Duo 3.06 GHz(MacOS X 10.6.4)

这个平台可能不代表您的微控制器,但测试表明,在这个平台上,减法比分区要慢得多。

 void convert_by_division(int value, char *temp) { temp[0] = (value % 10) + '0'; temp[1] = (value % 100) / 10 + '0'; temp[2] = (value % 1000) / 100 + '0'; temp[3] = (value % 10000) / 1000 + '0'; } void convert_by_subtraction(int value, char *temp) { static const short subtractors[] = { 1000, 100, 10, 1 }; int i; for (i = 0; i < 4; i++) { int n = 0; while (value >= subtractors[i]) { n++; value -= subtractors[i]; } temp[3-i] = n + '0'; } } #include  #include  #include  static void time_convertor(const char *tag, void (*function)(void)) { int r; Clock ck; char buffer[32]; clk_init(&ck); clk_start(&ck); for (r = 0; r < 10000; r++) (*function)(); clk_stop(&ck); printf("%s: %12s\n", tag, clk_elapsed_us(&ck, buffer, sizeof(buffer))); } static void using_subtraction(void) { int i; for (i = 0; i < 1024; i++) { char temp1[4]; convert_by_subtraction(i, temp1); } } static void using_division(void) { int i; for (i = 0; i < 1024; i++) { char temp1[4]; convert_by_division(i, temp1); } } int main() { int i; for (i = 0; i < 1024; i++) { char temp1[4]; char temp2[4]; convert_by_subtraction(i, temp1); convert_by_division(i, temp2); if (memcmp(temp1, temp2, 4) != 0) printf("!!DIFFERENCE!! "); printf("%4d: %.4s %.4s\n", i, temp1, temp2); } time_convertor("Using division ", using_division); time_convertor("Using subtraction", using_subtraction); time_convertor("Using division ", using_division); time_convertor("Using subtraction", using_subtraction); time_convertor("Using division ", using_division); time_convertor("Using subtraction", using_subtraction); time_convertor("Using division ", using_division); time_convertor("Using subtraction", using_subtraction); return 0; } 

使用GCC 4.5.1进行编译,并以32位工作,平均时间为(优化' -O '):

  • 使用除法0.13
  • 使用减法0.65

编译和工作在64位,平均时间是:

  • 使用除法0.13
  • 使用减法0.48

显然,在这台机器上,使用减法不是一个成功的命题。 您必须在您的机器上进行测量才能做出决定。 并且移除模10000运算只会使结果倾向于有利于除法(当用比较替换时,它会在划分时减少约0.02秒;这节省了15%并且值得拥有)。

你有什么理由特别担心这个吗?

如果您的编译器和C库提供了itoa()函数,请使用它,然后担心编写此代码(以及相关的测试等等,以确保您做对了!)如果由于某种原因,结果太慢或者不适合RAM或其他东西。

我用更好的答案替换了我以前的答案。 此代码以正确的顺序创建一个4字符的字符串,输出[0]中的最高有效数字在输出[3]中最低有效,输出[4]中有一个零终结符。 我对PIC控制器或C编译器一无所知,但此代码不需要16位整数,加法/减法和移位。

 int x; char output[5]; output[4] = 0; x = 1023; output[3] = '0' + DivideByTenReturnRemainder(&x); output[2] = '0' + DivideByTenReturnRemainder(&x); output[1] = '0' + DivideByTenReturnRemainder(&x); output[0] = '0' + x; 

关键是神奇的functionDivideByTenReturnRemainder 。 如果不明确使用除法,仍然可以通过向右移动除以2的幂; 问题是10不是2的幂。我通过在除以256之前将值乘以25.625来回避这个问题,让整数截断向下舍入到正确的值。 为什么25.625? 因为它很容易用2. 25.625 = 16 + 8 + 1 + 1/2 + 1/8的幂表示。 再次,乘以1/2与右移一位相同,乘以1/8则右移3位。 要获得余数,请将结果乘以10(8 + 2)并将其与原始值相减。

 int DivideByTenReturnRemainder(int * p) { /* This code has been tested for an input range of 0 to 1023. */ int x; x = *p; *p = ((x << 4) + (x << 3) + x + (x >> 1) + (x >> 3)) >> 8; return x - ((*p << 3) + (*p << 1)); } 

您是否需要使用十进制表示的ASCII字符串? 以hex格式存储它会容易得多。 不需要分工,只需(相对便宜)换class操作。 如果您为每个数字添加“0x”,Excel应该能够读取它。