演算精度


2024年6月8日 記

はじめに

スクリプト言語とかの「なんでも入っちゃう変数」を使っているだけだと コンピューター内部でどのように数値が扱われているかを知らないままでいる人もひょっとしたらいるのかもしれません。 それと「コンピューター」ではなくて「リレー回路」をイメージして制御プログラムを書く シーケンサー用のシーケンスプログラムを作っている方々にも読んでもらえると得られるものがあると思います。 ここではちょっとその辺のことを記しておこうと思い立ちました。


目次

 (1)数値表現フォーマット(整数、実数)
 (2)整数値
 (3)数浮動小数点
 (4)BCDコード
 (5)その他、ちょっと関係ないかもしれないウンチク
 (6)最後に




(1)数値表現フォーマット(整数、実数)

数値の表現としては大きく2つになると思います。
整数値実数値です。

これらとは別にBCDコードという数値表現もあるのですが、BCDコードは「数値演算」に使う事を目的としておらず、 演算結果を【表示】したり、計算結果の【誤差を吸収】する目的で使われることはあるかと思います。 ・・・いずれにしてもBCDコードは演算に用いられる整数値や実数値とは分けて考えるべき(扱うべき)ものだと思いますので、 とりあえずここでは除外しておきます。


アセンブリ言語(今これやる人あまりいないと思いますけど・・・)を始めると、最初に出てくる数値表現が「整数値」です。 パソコンの仕様とかを見ると「?ビットCPU」とか書いてあるのを見かけたことはあるかと思いますが、 ここで言っている「?ビット」というのは「整数値」を表現できる【最大ビット数】と考えて頂いて差し支えない※1と思います。

※1 正確にはちょっと正しくないところがあります。 その昔、モトローラ社のMC68000MPU(CPU)は16bitとして謳われていました。 これは外部バス(CPUの外回りの電気信号のデータ部に関する配線をひとまとめに表現したもの)が16bit(16本)でした。 しかしMC68000MPU(CPU)はアセンブリ言語を扱うプログラマ視点で見ると32bitの整数値を扱うことができました。 なので、一部の人からは【内部32bit】と言われていました。 ちなみに MPUとは Micro Processing Unitの略で、CPU(Central Processing Unit)とほぼ同じ意味で使われています。

最近の小型PCやノートPCの仕様には書かれていませんが、これらは小型化するためにバス幅が半分になっている可能性がありそうです。 デスクトップPCに比べて動作が遅く感じられるのはその辺が原因かもしれません。 (増設)メモリのピン数なども少ないですよね。デスクトップPC用と比較して・・・ですが。

現在のWindowsパソコンに載っているCPUは64bitがほとんどだと思います。 つまり64bitの整数値を扱う(演算する)ことができます。 もちろんそれよりも少ないbit数のCPUでもソフトウェア的に工夫すれば64bitの整数値を演算することは可能です。 しかし【工夫】しなければならないので、圧倒的に処理速度が遅くなってしまうということは容易に想像できるかと思います。

64bitで表現できる整数値というのは、 という、現実的な日常生活の中で扱う数値範囲よりもずっと広い範囲(値)を表現できることがお分かり頂けると思います。

ちなみにx64 CPU用のWindowsには32bitと64bitがありました。 32bitのWindowsは(互換性を保つために)32bitモードで走っているか、 または32bit以下の演算命令しか用いていないものと思われます。 ちなみに32bit命令だと32bitアドレスしか扱えなくなりますので、自動的に扱えるメモリ容量(メモリ空間)も32bitになります。

おそらく互換性を保つためCPU自体に【32bitモード】があるんだと思いますが、ちゃんと確認した訳ではないので断定はできません。 ですが、昔、16bit(80286など)から32bit(80386)に変わったとき、パソコンを起動した直後は16bitモードで走っていて、 その後にBIOSレベルで32bitモードに切り替えて走っていたようです(誰かに聞いたか本で読んだ記憶です)。 つまり、当時32bitと謳われていた80386は、起動直後に32bitで動いていたわけではないんです。 この辺についてはハードウェアの設計の仕方によって【選択】できたのかもしれませんが、 私は86系CPUのハードウェアを設計をしたことが無いので正確には分かりません。 インターネットで検索すれば何か(資料が)出てくるかもしれませんので、興味がある方は検索してみてください。

話が少し変わります。

実数値を表現するのにIEEEフォーマットというものがあります。 一般的に見かける実数値を表現するフォーマットは、今はおそらくIEEEフォーマットだと思いますが、 その企業や団体などにより、独自のフォーマットを採用している場合もあるかと思います。




目次へ




(2)整数値

今のパソコンの単位は0または1を格納できるbit(ビット)という単位が最小単位になります。 このbitを8個ひとまとめにして1byte(バイト)という単位になります。 メモリはこの1byte単位に1つのアドレスが割り当てられて、 合計?GB(ギガバイト)とかいうメインメモリ(の容量)が搭載されています。

と、なります。

ついでなのでメモリ容量の単位も書いておきます。
  • KB(キロバイト) 1KB = 1024バイト
  • MB(メガバイト) 1MB = 1024KB
  • GB(ギガバイト) 1GB = 1024MB
  • TB(テラバイト) 1TB = 1024GB
・・・と、(私の頭の中では)なっています。 ですが、いつの間にか世の中では単位の混同を避けるために上のような単位を下のように定義したようです。 私は古い人間なので上の方を今後も使い続けそうですが、この辺については読み替えて頂きたいと思います。
  • KiB(キビバイト) 1KiB = 1024バイト
  • MiB(メビバイト) 1MiB = 1024KiB
  • GiB(ギビバイト) 1GiB = 1024MiB
  • TiB(テビバイト) 1TiB = 1024GiB

「データバス」とはCPUとメモリ間またはI/O間でやり取りするデータを信号として載せる電線(バス)のことです。 これと対になるようなものに「アドレスバス」というものがあります。 「アドレスバス」はどの番地(アドレス)のデータを読み込むor書き込むのかをCPUがメモリやI/Oに対して指定する電線(バス)になります。 CPUはメモリやI/Oにアクセスする際には常にどこの番地(アドレス)のメモリにアクセスするのかを「いちいち指定する」必要があります。 ただし連続するメモリに順にアクセスする場合は手抜きモード(高速アクセスモード)が別途用意されていたりします。 更にちなみになんですが、昔の古いCPU(キャッシュメモリが搭載されていないCPU)はプログラムの命令に従ってメモリアクセスが 実行されていましたが、今の高速CPUは実行されている命令とは関係なく、CPUが必要になるであろうと予測したデータに対して アクセスされる場合が「ほとんど」だろうと思います。CPUはあらかじめ必要となるメモリからデータを取り込んでキャッシュに充填しておいて、 速度が不必要に低下しないようにしています。 「ほとんど」の内に含まれないケースとしては「キャッシュが部分的に無効にされているメモリにアクセスする」とか 「I/O(ハードウェアの信号)に対してアクセスする」とかいうときでしょう。 「キャッシュが無効にされている」場合は当然として、I/Oにアクセスする場合には アクセス速度が低下したとしてもリアルタイムでアクセスする必要があります。 ・・・もう少し正確に言いますと、I/Oはキャッシュメモリにキャッシュしないように「指定」するのが一般的ですかね(メモリマップドI/Oの場合)。 I/OマップドI/Oの場合は・・・どうなっているのか・・・、その辺のハードウェアの設計とか直接叩くようなプログラムを作ったことがないので私には分かりません。 興味があったらご自身で調べてみてくださいね。 ちなみに「メモリマップドI/O」と「I/OマップドI/O」の違いですが、 「メモリマップドI/O」はメインメモリと同じメモリ空間にI/Oがマップされているハードウェア構成を言います。 「I/OマップドI/O」はI/Oアクセスする際にそれ用の信号が出力されていて、通常のメモリアクセス(メモリ空間)とは区別されています。 また使用する命令も「メモリマップドI/O」は通常のメモリアクセス命令でアクセスできますが、 「I/OマップドI/O」の場合は専用の命令が用意されています。

「I/O」について、もう少し掘り下げておきます。 今のCPUは非常に高速で動作していて、それに比べてI/Oは非常に鈍足といえます。 そんな感じなので今のパソコンには「チップセット」とかいう代物が結構以前からですが入っています。 これは鈍足なI/Oアクセスを高速なCPUに変わってアクセスを行い、CPUとI/Oの仲介役を担っているようです。 ただし正確なところは仕様などの情報が入手できない(入手しようとしたことがない)ので分かりません。 パソコンと(自作の)マイコンシステムでは、設計の仕方に違いがありますのでご注意ください。

「ワード境界」とか「4バイト境界」とか「8バイト境界」とかいう言葉を見聞きしたことあるかもしれません。 これはその「境界」に整列していないと「正常にアクセスできない」とか「アクセス速度が低下してしまう」とかいう理由で その境界となるアドレスに整合(整列)させる場合に使われる言葉です。 整合(整列)させる場合には、それ用のコンパイラやアセンブラの専用命令(CPUの命令ではない!ので注意)を使って指定します。


ちょっと話が反れてしまいましたので戻します。
整数値の数値範囲ですが、

1バイトのとき 0~255 (2^8)-1、 符号付きのとき -128~127
2バイトのとき 0~65535 (2^16)-1、 符号付きのとき -32768~32767

です。とりあえずこの程度しか私の頭の中にはありません。 必要であれば電卓とか使って計算すれば良いだけですので。 CPUで演算可能な整数値(数値範囲)はCPUの命令で決まってしまっています。 それ以上でもそれ以下でもありません。

ただし「数値表現」についてはあくまでプログラマ視点になります。 メモリに格納されたデータを「どのような数値としてみなすのか」は、 あくまでプログラマが決定して表示(出力)に反映させることになります。 「整数値」は小数点以下の数をバッサリと切り捨てた「固定小数点数」と言えます。 なので、小数点位置をちょこっとずらして、計算なども少し工夫することで小数点数も正数値で表現できます。 例えば32ビット(4バイト)の正数値の小数点位置を16ビット左にシフトすることで小数点以下の数値を表現すると、

1234567812345678.1234567812345678(小数点位置前後の'1'の位置に注目!)
0000000000000010.0000000000000000 = 2.0
0000000000000001.0000000000000000 = 1.0
0000000000000000.1000000000000000 = 0.5
0000000000000000.0100000000000000 = 0.25

という感じで数値を表現できます。 ちなみに最上位ビットは符号ビットになります。 この数値表現は、実数値を扱いたいけど都合によりC言語の実数(float,double)を使えないような状況のときに 私はよくこの表現方法を使っていました。 この表現方法だと加減算は普通の整数値の命令でOKですし、乗除算も正数値の乗除算命令を工夫して使えば使うことができます。

「固定小数点数」に対して「浮動小数点数(浮動小数点)」というものがあります。 浮動小数点数は実数値を表すのに使用されるものです。




目次へ




(3)浮動小数点

通常、手計算とかでちょっとした計算をするには整数値(固定小数点数)だけでもそれなりに使えると思いますが、 もっと広範囲の天文学的な数値を扱うにはまったくの不十分です。 そこで出てくるのが「浮動小数点数」です。

浮動小数点数は仮数部と指数部の2つの値を用いて数値を表します。 浮動小数点数は単精度と倍精度、FPUによっては拡張精度というものがあり、 単精度は32ビット(4バイト)、倍精度は64bit(8バイト)で表され、 拡張精度は80bit(10バイト)くらいのものがあったかと思います。

仮数部、指数部共に有限のビット数しかありません。 有限のビット数で表すことができる数値は当然ですが「有限」です。 通常の四則演算程度では演算結果が真値に対して違ってくることはほとんどないと思いますが、 指数関数や対数関数みたいなものを使うと下の方の桁の値が違ってくるのはよくありますし、 演算をさせる順番によってはとんでもなく違ってしまうなんてこともざらにあります。

ちょっと具体的な例を挙げてみます。 私が初めて購入した(購入してもらった)パソコンは小数点以下1桁しか表現できませんでした。 言語はBASICというやつです。 小数点以下1桁しか表現できないので、演算させる順番によって結果が明らかに違ってしまいました。

例えば、

(10 ÷ 3) × 3
の答えは、普通に計算すれば 10 ですよね。10を3で割って同じ数の3を掛けているだけなので。 ですが演算精度が小数点以下1桁しか表現できないとき、この結果は、
10 ÷ 3 = 3.333・・・ ≒ 3.3
3.3 × 3 = 9.9
・・・ということで、本来欲しい結果である10ではなくて、0.1少ない9.9になってしまいました。 これを回避するには演算する順番をプログラム的に変えて、
(10 × 3) = 30
30 ÷ 3 = 10
という感じで、 演算精度を低下させる割り算はできるだけ演算の最後の方で行うようにすることで演算結果の精度が低下することを防ぐ必要がありました。

昔のパソコンに比べれば高精度な「浮動小数点数」ですが、 有限のビット数でしか表現できないのは変わりませんので高精度な演算結果を得たいのであれば、 同様な感じで演算結果の精度を低下させるような演算はできるだけ後回しにするように注意する必要があるということです。

「浮動小数点数」の具体的なフォーマットを知りたいのであれば 「IEEE」とか「IEEEフォーマット」とかのキーワードをインターネットで検索すれば出てくるのではないかと思いますので、ここでは割愛します。




目次へ




(4)BCDコード

一般的なBCDコードは次のようなものになります。 10進数の 1234 を16進数で 1234H と表現します。 最下部に'H'を付けた数値は16進数を表します。 このような表現を特に「パックドデシマル」と呼びます。 「パックドデシマル」は、FPU(フローティングポイントユニット)でも対応しているものもあるかもしれません・・・というか、 ありました、過去に。

これと同じような表現になりますが、ASCIIコードの数字コードで表現することもあります。 ただしこの場合は「文字」としてモニターに出力するときなど、 人に文字として認識できるようにするときに用いられるだけです。 ちなみにASCIIコードの半角数字は、

0 = 30H
1 = 31H
2 = 32H
・・・・・・・
9 = 39H


という感じで割り当てられています。

それとパソコンではないのですが、 世の中には「シーケンサー」と呼ばれる機械を制御するための、 ちょっと鈍くさい制御システムがありましてリレー回路を構成するような感じで制御プログラムを表現できるので、 電気屋さんとかもやっている人が居ます。 ・・・というか、電気屋さんにもできるように作られたものが「シーケンサー」という代物です。 電気屋さんでこの辺まで知っている人はほとんど居ないと思うのですが、 シーケンサーもマイコンシステムで、この辺の数値表現を知っている人であれば数値演算などもできるように作られています。

ちょっと具体的なメーカー名を挙げてしまいますが、三菱とかキーエンスとかだと基本的にバイナリ表現(2進数での表現)なのですが、 オムロンだとBCDコードがデフォルトになっていました(過去形で書いていますが、現在も変わっていないのではないかと思います)。 BCDコードと2進数の「違い」を知らない人同士が組んでシステムを構成したときに、 BCDコード用と2進数用の命令を区別していない(区別できない)ため、 演算関係がぐちゃぐちゃになってしまったことがありました。

ちなみに私が過去に使用したことがあるシーケンサーは各メーカー共に、 リトルエンディアン(x86 CPUと同じ)でありました。 この辺も理解していないとシーケンサーで演算を含むプログラムは組めませんのでご注意ください。 エンディアンの違いは特に「通信コマンド」のやりとりをするときに必要になります。 文字列の格納順と、転送命令、演算命令でデータの並びが逆になることがある(経験者はきっとこれだけで分かってくれるだろうと思います)ためです。




目次へ




(5)その他、ちょっと関係ないかもしれないウンチク

・MMX SSE CPU演算とマルチコア

CPUが1コア時代には活躍していたであろうMMXですが、今のマルチコアCPUでMMX機能を使うと「発熱」がすごいことになってしまいます。 ・・・というか、すごいことになってしまいました。 アッという間に高温になってしまいますし、 4コア以上のCPU上で実行するのであればMMXを使うより、 普通にCPU用のコードとして記述してマルチコアの威力をふんだんに使った方が労力も少なくてラクだと気付いたのは結構前の話です。

SSE(3D Now!)は改まって使ったこと、私はありません。 せいぜいCコンパイラのコンパイルオプションで機能を有効にする程度です。


・画像処理(拡大、縮小)

画像の拡大縮小処理を「適当」にやると綺麗な画像になりません。 この辺の処理は演算精度とかではなくて、何らかの「理論」があるようです。 その理論が「何か」を私は知りません。

画像の拡大縮小処理についてよくわからない人は、例えば画像を1ピクセル幅だけ縮めたい場合を想像してみてください。 単純にどこか1ラインをごそっと抜いてしまったときには、 継ぎ目が見えてしまって綺麗な画像にならないのは容易に想像できると思います。

画像編集ソフトとかで画像の拡大縮小を具体的にどうやっているか想像できますか?
「画像処理について未経験だけど想像できる」という人は、その手の処理に関してはきっと天才なんだと思いますよ。 私にはよくわかりません。具体的な処理方法が思い浮かびません。 「あまり綺麗にはならない(できない)けど・・・」という条件が付けば、私でもなんとかできるかもしれませんが・・・。 ちなみにこの場合、他の人が作ったその手のライブラリなどを利用するとかいうのは反則ですよ!。 当たり前ですが・・・。




目次へ




(6)最後に

とりあえず一通り、最低限は書けたかな?と思います。 分からない部分とかありましたらインターネットで更に検索して調べてみてくださいね。




ページのTOPへ






メニュー