2016年5月16日月曜日

文字列から数値列に変換するプログラムとか

文字列として受け取ったデータを、数値列に変換する処理をどうすればよいか?という問題があったとします。そのプログラムの基本的な考え方です。作り方のルールとして、1文字の場合を作って、そこから文字列(2文字以上)に応用させます。

はじめに、1文字(0から9まで)を受け取って、それに対応する数値に変換する場合を考えます。文字は、ASCIIコードと呼ばれる数値として表現されています。

文字 0 1 2 3 4 5 6 7 8 9
10進数 48 49 50 51 52 53 54 55 56 57
16進数 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39

そのため、例えば、「0」であれば48を引けばよいことになります。プログラムコードであれば、次のように書けます。

  char key = '0'から'9'の文字
  unsigned char digit = key - 48;

もし、16進数であれば、アルファベットAからFまでも加えなければなりません。

文字 A B C D E F
10進数 65 66 67 68 69 70
16進数 0x41 0x42 0x43 0x44 0x45 0x46

このとき、例えば「A」であれば、65を引いて10を足せばよいことになります。プログラムコードであれば、次のようにかけます。

  char key = 'A'から'F'の文字
  unsigned char digit = key - 65 + 10;

ここで、関数を定義したいと思います。関数key_to_digit()は、引数として、char型の数字文字'0'から'F'を受け取って、返値としてunsigned char型の数字を返します。関数の基本として次のように記述できます。

  unsigned char key_to_digit ( char key )  {
    unsigned char digit;
    return digit;
  }

それでは、関数の中身を記述したいと思います。大切なことは、'0'から'9'までと、'A'から'F'までの2つのパターンに分けて処理を書かなければなりません。すなわち、keyの値が60未満のときは'0'から'9'、それ以外は'A'から'F'であることは明白です。従って、以下のようにかけるかと思います。

  unsigned char key_to_digit ( char key )  {
    unsigned char digit;

    if ( key > 60 )  {
      digit = key - 48;
    }
    else  {
      digit = key - 65 + 10;
    }

    return digit;
  }

もう少し見やすくかくと、次のようになります。

  unsigned char key_to_digit ( char key ) {
    return ( key > 60 ) ? key - 48 : key - 65 + 10;
  }


それでは、2文字以上場合を考えます。char型の文字配列から、unsigned char型の数字配列に変換することを考えます。すなわち、下の表の一マスをchar型またはunsigned char型の1バイトだとするとき、上段のように文字として取り扱われている数字列を、下段のように2文字をunsigned char型1つの数字に置き換える処理になります。

:
7
8
8
1
1
5
0
1
8
D
8
1
0
0
¥0
78
81
15
01
8D
81
00


上段の配列をkeys[16]、下段をdigits[7]として、所望の動作をする関数を定義したいと思います。
keys[0]は無関係な':'が常に入っていので、読み飛ばします。すなわち、key[1]とkey[2]を、先ほど定義したkey_to_digit ()関数を使って変換していきます。下のプログラムを読んでください。

  unsigned char ret;
  ret = key_to_digit(key[1]);

unsigned char型の一時変数retに、keys[1]の数字が代入されます。上表では、'7'が7に変換され、retの下位4ビットに7(0111)が入ります。ここで、4ビット左シフトをして、keys[2]の数値を代入します。すなわち、以下の通りになります。

  ret = ret << 4;
  ret = ret | key_to_digit(key[2]);

上の例では、1と2、3と4、5と6番目の要素がペアになっています。すなわち、奇数(2n+1)と偶数(2n)の組み合わせです。従って、for文で回すのであれば、次の通りになります。

  for ( int i = 1; i < 8; ++i ) {
    unsigned char ret;
    ret = key_to_digit(keys[2*i-1]);
    ret = ret << 4;
    ret = ret | key_to_digit(keys[2*i])
  }

最後に、keys[16]を受け取って、digits[7]を返す関数として定義します。

  void convert ( char keys[16], unsigned digits[7] ) {
    for ( int i = 1; i < 8; ++i ) {
      unsigned char ret;
      ret = key_to_digit(keys[2*i-1]);
      ret = ret << 4;
      ret = ret | key_to_digit(keys[2*i])
      digits[i-1] = ret;
    }
}

もう少し見やすくすると、次のようになります。

  void convert ( const char* const keys, unsigned char* const digits )  {
    for ( size_t i = 0; i < 24; ++i )  {
      digits[i] = key_to_digit(keys[2*i+1]) << 4 | key_to_digit ( keys[2*i+2] );
    }
  }

何かの参考にしてください。