代码之家  ›  专栏  ›  技术社区  ›  John Moffitt

在C中转换为ASCII

  •  7
  • John Moffitt  · 技术社区  · 14 年前

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

    我的问题是,我能想到的唯一办法是把数字分开,需要很多除法。

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

    这样做的最终目的是在SD卡上生成一个.csv文件,可以快速插入任何笔记本电脑,在Excel中查看数据的图形。

    9 回复  |  直到 14 年前
        1
  •  2
  •   Adrian Mole Chris    4 年前

    有一种使用减法的方法,但我不认为它比在“普通”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[4] = '\0';
    
      return tmp;
    }
    

    makestring() 应该产生一个(静态的,所以在重写之前复制它)字符串,带有转换后的数字(前缀为零,宽度为4个字符,因为最初的假设是0-1023范围内的值)。

        2
  •  15
  •   Clifford    14 年前

    显而易见的解决办法是

    另一方面,与将数据传输到SD卡所用的时间相比,/和%的执行时间可能微不足道;因此,请确保您优化了正确的设置。

        3
  •  4
  •   Jon Skeet    14 年前

    当然还有一种更快的方法:使用1024个预先计算的字符串数组。然后,只需执行边界检查,然后在数组中创建索引。

        4
  •  3
  •   Merlyn Morgan-Graham    14 年前

    尽管如此,这里有一篇文章可能对你有用。它使用循环、移位、加法和分支,具有线性/恒定复杂性: http://www.johnloomis.org/ece314/notes/devices/binary_to_BCD/bin_to_bcd.html

    另外,我认为编写一些不执行任何除法、乘法或分支,但仍然给出正确答案的代码会很有趣[0-1024]。没有人保证这比其他选择更快。这类代码只是探索的一种选择。

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

    • 常量为224字节(不知道代码大小)
    • 5位移位权
    • 3减法
    • 4位运算符

    性能:

    使用Jonathan Leffler答案中的性能比较和itoa例程,以下是我得到的数据:

    • 第2.15部分
    • 减法4.87
    • 暴力查找0.36

    我将迭代计数增加到200000,以确保在时间分辨率方面没有任何问题,并且必须添加 volatile

    代码:

    #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);
      }
    }
    
        5
  •  2
  •   Jerry Coffin    14 年前

    multiply by the reciprocal 而不是除以基数。Terje的代码是针对x86的,但是将总体思想移植到PIC中应该不会非常困难。

        6
  •  2
  •   Jonathan Leffler    14 年前

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

    temp[3] = 1023 / 1000;
    

    甚至:

    temp[3] = 1023 >= 1000;
    

    temp[0] 最重要的是 temp[4]

    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核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 <stdio.h>
    #include <timer.h>
    #include <string.h>
    
    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;
    }
    

    使用GCC4.5.1编译,32位工作,平均计时为(优化) -O '):

    • 0.13 秒使用除法
    • 0.65

    在64位编译和工作时,平均计时为:

    • 秒使用除法
    • 0.48

    显然,在这台机器上,使用减法并不是一个成功的提议。你必须在你的机器上测量才能做出决定。去掉模10000运算只会使结果偏向于除法(当用比较代替除法时,它减少了大约0.02秒的时间;这节省了15%,值得拥有)。

        7
  •  1
  •   Brooks Moses    14 年前

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

    如果编译器和C库提供 itoa()

        8
  •  1
  •   Mark Ransom    14 年前

    我已经用一个更好的答案代替了以前的答案。此代码按正确顺序创建一个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;
    

    DivideByTenReturnRemainder . 如果不明确地使用除法,通过右移仍然可以除以2的幂;问题是10不是2的幂。我避开了这个问题,先将值乘以25.625,然后再除以256,让整数截断向下舍入到正确的值。为什么是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));
    }
    
        9
  •  1
  •   bta    14 年前

    是否需要使用