符号付きか符号無しか文字列か、はたまた浮動小数か。
プログラム上で値を扱う場合には当然のように意識することですが、ある種あたりまえのように身に付いていても説明するとなると面倒ですね。。今さらながらというか今だからなのか、ひょんなことからこのあたりを説明する機会があったのですが伝わったのかどうかが微妙な気がしたもので。
と、いうわけで今回は数値表現について記事にします。
2進10進16進(BIN/DEC/HEX)
よく”CPU内部では数値を2進数で扱う”ということは知られていますが、10進数・16進数とはどう使い分けるのかを知らない方もいます。(新人エンジニアとか。)
2進数はコンピュータが扱いやすいため、という理由ですが電気的なことを考えれば分かりやすいですね。
⇒電圧が高い=Hi=1、電圧が低い=Lo=0というビット基準でしかコンピュータは扱えない。
10進数は人間が扱いやすい数値であることはすぐに分かると思います。16進数は2進数表現だと桁数が多くなってしまうため2進数4桁=16進数1桁となるように考えられたものです。早い話が”長いから良い感じに短くしろ”という人間都合で生まれたものが16進数ですね。(知らんけどw。まあ支障ないでしょう。)
2進数(Binary)、10進数(Decimal)、16進数(Hexadecimal)を表現すると下表のようになります。
2進数 BIN | 10進数 DEC | 16進数 HEX |
0000 | 0 | 0 |
0001 | 1 | 1 |
0010 | 2 | 2 |
0011 | 3 | 3 |
0100 | 4 | 4 |
0101 | 5 | 5 |
0110 | 6 | 6 |
0111 | 7 | 7 |
1000 | 8 | 8 |
1001 | 9 | 9 |
1010 | 10 | A |
1011 | 11 | B |
1100 | 12 | C |
1101 | 13 | D |
1110 | 14 | E |
1111 | 15 | F |
よくある基本的なヤツですね。PLCを扱うような制御系の職種の人は上表のパターンをほぼほぼ暗記していると思います。特に2進数/16進数は上表のパターンさえ覚えてしまえば桁数が増えるだけの話になるので。(逆にこのパターン以外に覚える必要がない、と言えます)
符号付きと符号無しの数値
符号付きの値は最上位ビットを符号ビットとして扱う(0=正、1=負)、というものになり、符号無しの値は負数は扱えず0以上の正数のみを扱うものになります。
よくあるPLCではワードデバイス1点(D0など)は1ワード=16ビットなので2の16乗=65536通りの表現が出来ます。なので符号付きなら-32768~+32767、符号無しなら0~65535の範囲で扱えます。
ここまではわりと知られていますが2進数や16進数の表現と一緒になると?が浮かんでくる方もいます。(新人エンジニアとか。)
とりあえず省略しつつ表現すると下表のようになります。(1ワード・2バイト・16ビットの場合)
2進数 | 16進数 | 10進数 符号付き | 10進数 符号無し |
0000 0000 0000 0000 | 0000 | 0 | 0 |
0000 0000 0000 0001 | 0001 | 1 | 1 |
0000 0000 0000 0010 | 0002 | 2 | 2 |
… | … | … | … |
0111 1111 1111 1101 | 7FFD | 32765 | 32765 |
0111 1111 1111 1110 | 7FFE | 32766 | 32766 |
0111 1111 1111 1111 | 7FFF | 32767 | 32767 |
1000 0000 0000 0000 | 8000 | -32768 | 32768 |
1000 0000 0000 0001 | 8001 | -32767 | 32769 |
1000 0000 0000 0010 | 8002 | -32766 | 32770 |
… | … | … | … |
1111 1111 1111 1101 | FFFD | -3 | 65533 |
1111 1111 1111 1110 | FFFE | -2 | 65534 |
1111 1111 1111 1111 | FFFF | -1 | 65535 |
ポイントとなるのは最上位ビットと2進/16進数では同じ(=CPU内部では同じビットパターン)なのに符号付きと符号無しで違う数値に見えてしまう部分です。符号の有無はどう扱うか(人間から見てどうなのか)というだけでCPUの値(内部表現)としては同じになる、ということです。ちなみにCPU内部で負数は2の補数を使って表現します。2の補数とは全ビットを反転して1を足すということになり(2の補数の定義とは少し違うが結果的にこうなる)、基本的に±の符号を逆転した値になります。(ただし0と負数の最小値は別。。2バイトの場合0と-32768が2の補数の関係となる。)
当然ながら1バイトや4バイトなどバイトサイズが違っても10進数で表現できる範囲が変わるだけで考え方自体は同じです。
文字・ASCII
数値を扱うのはイメージがついても文字を扱う場合にどうなるのかが分からない方もいます。(新人エンジニアとか。)
CPU内で文字を扱う場合は文字コードを使います。ある文字に対してコード(=数値)が割り当てられているのでCPU内で文字を扱う=文字コードを扱うという意味になります。
文字コードは規格化されているので必要なものを使うことになります。
良く知られているのは
・ASCII
・Shift-JIS
・Unicode
あたりと思います。
ちなみにアスキーコード(ASCII CODE)は1バイト(正確には7ビット=0~127)で表現可能な文字や記号が割り当てられており、さらに文字や記号として不可視な制御コード(制御文字)も含まれています。(128~255の領域は拡張アスキーなどと呼ばれ各国で割り当てが異なり、日本では半角カタカナなどが割り当てられています)
Shift-JISやUnicodeは日本語(ひらがなやカタカナ、漢字など)を使う場合によく使われる文字コードになります。日本語版のWindowsでは日本語を扱う場合、基本的にShift-JISが多いのですが最近はUnicodeも増えています。(Windows XPあたりから多言語対応がよく言われるようになり、Unicodeはどの言語の文字にも対応するように規格化されてきた経緯があるが、、、XPの頃はUnicodeに対応するアプリが少なかった)
PLCなどの制御系で文字が必要となるのはたいてい通信処理ですが、基本アスキーコードの範囲で事足りるのでアスキーコードをおさえておけばほぼ問題ありません。アスキーコード表はググるとすぐに見つかるので割愛しますが、PLC内のデバイスにどのような値が格納されるかは把握しておく必要があります。
下表は例としてD100に”ABCDE 123″という文字列(間にスペースが入る)が格納された場合です。
デバイス | 三菱PLC 16進数 (文字) | OMRON, KEYENCE 16進数 (文字) |
D100 | 0x4241 (BA) | 0x4142 (AB) |
D101 | 0x4443 (DC) | 0x4344 (CD) |
D102 | 0x2045 ( E) | 0x4520 (E ) |
D103 | 0x3231 (21) | 0x3132 (12) |
D104 | 0x0033 ([NULL]3) | 0x3300 (3[NULL]) |
ポイントは
下位→上位バイトで格納されるか、上位→下位バイトで格納されるかの違い
最後に[NULL](0x00)で終わる
という2点です。
上位⇔下位のどちらから文字列が格納されるかはPLCメーカーの仕様によって異なります。あくまで文字列処理に関する命令を実行する場合に順序が重要になるだけで、PLC内で文字列処理を使わず外部から文字列相当の値を格納するだけならユーザ側で取り決めれば良いだけとなります。
NULL文字(ヌルもじ、と読む)で終わるのはどのPLCでも同じです。(文字列の終わりがNULLというのはPLCだけでなく一般的な定義です。終端を決めないと文字列の終わりが判別できないので。)上記の例では文字列の長さを奇数にしましたが文字列の長さが偶数の場合は次のデバイスにNULL文字が必要になります。
⇒例えば文字列が10文字の場合はNULLを含めてワードデバイス6点を見込んでおく必要がある
余談ですが、、
PLCで文字列を扱うのは通信処理の場合が多いですが、初めて通信処理を設計・実装するさいに多少混乱することがあります。通信データはコマンドや応答、必要に応じて数値をアスキー文字列で送受信するパターンが多く、10進数の数値文字列なのか16進数の数値文字列なのか、はたまたアスキー文字を表す数値なのかを使い分ける場面に出くわします。(チェックサムのようなデータがあると特に。)
このような場合、文字データか数値データかデバイスにどんな値が格納されるか?を明確にイメージできないと混乱しやすくなります。
メモリのイメージ
メモリはアドレス(番地)で示される部分に値が格納できる箱(のようなモノ)、などと説明されます。
(これだけで分かる人には分かるし、分からない人には分からないヤツですね。)
メモリは通常アドレスの番地1つに1バイトの値が格納でき、連続したアドレス空間が割り当てられます。アドレスはOSなどの環境によって左右されるので具体的な絶対値に意味はありませんがアドレス値が基準とすべきところからどれだけ差異があるか、という相対値は場合によって注意しておく必要があります。
(ただしマイコンを相手にする場合はアドレスの絶対値が重要な場合もあります)
アドレス (値に特に意味は無い) | 値 (1バイト) |
0x00200000 | 0x12 |
0x00200001 | 0x34 |
0x00200002 | 0x56 |
0x00200003 | 0x78 |
0x00200004 | 0x9A |
… | … |
上表は1バイトで扱うのメモリのイメージです。ですが実際に1バイトだけ並べても処理的にあまり良くない場面が多いため2 or 4 or 8バイトでまとめておくパターンが多いです。PLCの場合は2バイトでまとめておくと都合が良いためイメージ的には下表が近いハズです。
アドレス (値に特に意味は無くウソの値) | デバイス | 値 (2バイト) |
0x00200000 | D0 | 0x3412 |
0x00200002 | D1 | 0x7856 |
0x00200004 | D2 | 0xBC9A |
0x00200006 | D3 | 0xF0DE |
… | … | … |
PLCとしてはデバイスでアクセスするので具体的なアドレス値に意味はありません。(三菱PLCではADRSET命令でポインタとして扱うことも可能です。他社でも似たようなヤツがあったかと。)
PLC内だけで処理する場合はあまり関係ありませんが、、PC⇔PLC間で通信する場合はバイトオーダー(エンディアンともいう)に注意する必要があります。
バイトオーダー または エンディアン
マルチバイトのデータを下位バイト側から配置するか上位バイトから配置するか、の違いを表すものでOSなどの環境依存となります。
アドレスの若番側から順にデータの下位バイトから配置するものをリトルエンディアンといい、逆に上位バイトから配置するものをビッグエンディアンと言います。
ちなみにWindowsや三菱PLCはリトルエンディアン、UNIX系OSやOMRON PLCはビッグエンディアンが採用されています。
BCD
PC上ではあまり使う機会はありませんが、PLCでは時々BCDコードを使うことがあります。BCD=Binary Coded Decimal、2進化10進コードとも言われ4ビットで10進数1桁を表現する値のことで16進数表記をした場合に10進数に見える値になります。
2進数 BIN | 16進数 HEX | 2進化10進 BCD |
0000 0000 | 00 | 0 |
0000 0001 | 01 | 1 |
0000 0010 | 02 | 2 |
0000 0011 | 03 | 3 |
0000 0100 | 04 | 4 |
0000 0101 | 05 | 5 |
0000 0110 | 06 | 6 |
0000 0111 | 07 | 7 |
0000 1000 | 08 | 8 |
0000 1001 | 09 | 9 |
0000 1010 | 0A | ×(エラー) |
0000 1011 | 0B | ×(エラー) |
0000 1100 | 0C | ×(エラー) |
0000 1101 | 0D | ×(エラー) |
0000 1110 | 0E | ×(エラー) |
0000 1111 | 0F | ×(エラー) |
0001 0000 | 10 | 10 |
0001 0001 | 11 | 11 |
0001 0010 | 12 | 12 |
0001 0011 | 13 | 13 |
0001 0100 | 14 | 14 |
16進数表記でA~Fを使うビットパターンはBCDコードでは使えなくなります。2バイトでは通常65536とおりの値を表現出来ますが、2バイトBCDコードの場合0~9999の1万とおりしか表現出来ません。デジタルSWなどのハードウェアを使う場合にBCDコードのほうが都合が良く、たいていはハード絡みで使う場面に限られるハズなのですが、、
なぜかOMRONだと昔からの流れ(内部仕様による制約?)なのか無駄にBCDコードを強いられる場合が多いです。。
(初めてOMRON CJシリーズを使ったときにタイマの設定値で何故16進?と思ったことが懐かしい。)
浮動小数・FLOAT
PLCでも浮動小数を扱うことが出来ます。符号ビットと仮数部・指数部に分かれてデータが格納されます。
ただあまり詳細に覚えておく必要もないのでこの程度だけ知っておけば事足ります。
単精度浮動小数: 2ワード(4バイト)、FloatやSingleと呼ぶことが多い
倍精度浮動小数: 4ワード(8バイト)、Doubleと呼ぶことが多い(Double FloatやLong Floatとも)
ちなみにPLCで浮動小数は使えますが多用は禁物です。スキャンタイムが大幅に伸びることもありえますので。。
最近のCPUはだいぶ速くなりましたが20年くらい前のCPUだと結構遅く、、浮動小数計算を1つ2つ追加しただけでスキャンタイムが1ms程度長くなるとか良くあった。。。
まとめ?
BCDや浮動小数は余談ですが、、
それ以外の内容は(特に)通信処理を設計・実装する場合にキッチリと理解しておかないと作業が進まなかったり混乱したりします。そこそこ経験してきた今となっては当たり前すぎて何とも思いませんが昔を思い返すと理解が浅くて通信処理で四苦八苦したような記憶もあります。。
なんであれ基本的なイメージ・概念を理解しておくのは大事ですね。
コメント