CvsMks : N88-BASIC←→IEEE 浮動小数点表現形式の変換/還元
概要
ANSI-C 言語や Quick-BASIC あるいは Visual Basic で用いられる浮動小数点表現形式は、IEEE
形式と呼ばれます。これは、かつてMS-DOSの時代によく使われていたN88-日本語BASIC(86)というプログラミング言語(*1)における表現形式とは異なるため、過去のデータファイルを
Microsoft Windows で取り扱えるよう変換する場合、問題が生じます。
本ソースは、Windows から N88-BASIC のランダムファイルへのアクセスを主眼に、N88-BASIC
における関数 CVS、CVD、MKS$、MKD$ と同等の機能を提供する DLL を生成するためのものです(*2)。生成されたDLLは、Windows
95/98/NT/XP 上で動作します。
*1. N88-日本語BASIC(86)は、当時、国内で最大のシェアを得ていたNEC製16ビットパソコンで動作しました。東芝にはT-BASIC、三菱にはM-BASICなどと、かなり似通った仕様のものがありましたが、PCメーカを替えると動作しません。現在のようにOSには依存するが、機種メーカを選ばないプログラミング言語ではありません。
*2. ちなみにN88-日本語BASIC(86)における CVI や MKI$、すなわち整数については、Cにおける
signed short、Visual Basic における Integer と、同形式(同ビット列)ゆえ、いずれも2バイト単位で読み書きすればよいことになります。
所収ソース
CvsMks.C |
変換/還元ルーチンの本体です。
なお DllMain 関数は、これ以上ないほど簡単な最小限のものです。マルチスレッドなど一切、考慮していませんのでご注意ください。 |
CvsMksX.H |
CvsMks.DLL 生成のためのヘッダファイル(プロトタイプ宣言)です。 |
CvsMks.Def |
DLL 生成用の関数名修飾定義です。 |
生成(コンパイル)法
上記3ファイルで、Win32プロジェクトとして DLL を生成してください(*3)。
*3. Microsoft Visual C++ 6.0 / Microsoft Visual C++ .NET のいずれでも可。VC5などでも可と思われますが、未確認です。
生成後DLL所収関数
<n88CVS>
用途 |
N88BASIC(86) における MKS$ 文字列の数値への還元 (CVS 関数同等機能) |
プロトタイプ |
(VC) float n88CVS( unsigned char *bytes );
(VB) Declare Function n88CVS Lib "CvsMks.Dll" (ByRef A As Any)
As Single |
引数 |
MKS$ 文字列(32ビット列)
<例> 10.5(D) = "\x00""\x00""\x28""\x84" |
戻り値 |
還元した浮動小数点数(単精度実数値) |
注記 |
IEEE では単精度浮動小数点数(float)を次のように表現する。
(Upper) SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM (Lower)
S: 符号1ビット (0 = 正、1 = 負)
E: 指数8ビット (Bias 127 = 0x7F を加算)
M: 仮数23ビット(+隠れビット、先頭に1)
仮数部の先頭ビットは1を表す。
一方 N88-BASIC では、次の形式である。
EEEE EEEE SMMM MMMM MMMM MMMM MMMM MMMM
S: 符号1ビット (0=正、1=負)
E: 指数8ビット (Bias 128 = 0x80 を加算)
M: 仮数23ビット(+隠れビット、先頭に1)
仮数部の先頭ビットは 0.5 ( = 2^-1 ) を表す。 |
<n88CVD>
用途 |
N88BASIC(86) における MKD$ 文字列の数値への還元 (CVD 関数同等機能) |
プロトタイプ |
(VC) double n88CVD( unsigned char *bytes );
(VB) Declare Function n88CVD Lib "CvsMks.Dll" (ByRef B As Any)
As Double |
引数 |
MKD$ 文字列(64ビット列) |
戻り値 |
還元した浮動小数点数(倍精度実数値) |
注記 |
IEEE では倍精度浮動小数点数(double)を次のように表現する。
(Upper) SEEE EEEE EEEE MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM
MMMM MMMM MMMM (Lower)
S: 符号1ビット (0 = 正、1 = 負)
E: 指数11ビット (Bias 1023 = 0x3FF を加算)
M: 仮数52ビット(+隠れビット、先頭に1)
仮数部の先頭ビットは1を表す。
一方 N88-BASIC では、次の形式である。
EEEE EEEE SMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM MMMM
MMMM MMMM
S: 符号1ビット (0 = 正、1 = 負)
E: 指数8ビット (Bias 128 = 0x80 を加算)
M: 仮数55ビット(+隠れビット、先頭に1)
仮数部の先頭ビットは 0.0625 ( = 2^-4 ) を表す。
N88-BASIC の倍精度は、指数部は単精度と変わらないため、IEEE のように扱える範囲が広大になるわけではない。よって
MKD$ → double 数値化還元で桁あふれすることは無い。 |
<n88MKS>
用途 |
float 型数値の N88BASIC(86) 形式への変換 (N88-BASIC における MKS$ 関数相当機能) |
プロトタイプ |
(VC) unsigned char * n88MKS( float fValue, unsigned char *buf );
(VB) Declare Function n88MKS Lib "CvsMks.Dll" (ByVal C As Single,
ByRef D As Any) As Long |
引数 |
fValue (C)・・・変換元の float 型実数 (下注1)
buf (D)・・・変換後のビット列を書き込むバッファ |
戻り値 |
変換後ビット列へのポインタ (下注2) |
注記 |
1) 単精度実数の値範囲は次の通り。
(IEEE) -3.402823466E+38 〜 3.402823466E+38
(N88) -1.70141E +38 〜 1.70141E +38
より厳密な手法を使えば N88 の限界に近づけることはできるだろうが、実用上無意味と考え、絶対値で
1.7014E+38 より大きい数値はすべて FF FF FF EB(負)、FF 7F FF EB(正)に変換することにした。
2) ビット列格納バッファへのポインタを (unsigned char *) と: したが、もちろん
(char *) で、かまわない。
3) N88-BASIC では0に対しての MKS$ で結果が異なることがある。
<例> MKS$(0) → 00 00 7A 00 (7A が E2 とかも有り)
MKS$(0!)→ 00 00 00 00
本プログラムでは「00 00 00 00」に固定する。 |
<n88MKD>
用途 |
double 型数値の N88BASIC(86) 形式への変換 (N88-BASIC における MKD$ 関数相当機能) |
プロトタイプ |
(VC) unsigned char * n88MKD( double dValue, unsigned char *buf );
(VB) Declare Function n88MKD Lib "CvsMks.Dll" (ByVal E As Double,
ByRef F As Any) As Long |
引数 |
dValue (E)・・・変換元の double 型実数 (下注1)
buf (F)・・・変換後のビット列を書き込むバッファ |
戻り値 |
変換後ビット列へのポインタ (下注2) |
注記 |
1) 倍精度実数の値範囲は次の通り。
(IEEE) -1.797693134862316D+308〜+1.797693134862316D+308
(N88) -1.701411834604692D+38 〜+1.701411834604692D+38
単精度の場合(n88MKS関数)と同様の考えで、
-1.701411834604691D+38 未満 ・・・ FFFFFFFF FFFFFFF0
+1.701411834604691D+38 より大 ・・・ FF7FFFFF FFFFFFF0
に変換することにした。
2) (char *) 型、可。 |
「使用法(1) Visual Basic から」へ進む
「使用法(2) Visual C から」へ進む
「役立たない?/プログラム等」へ戻る
このページの先頭に戻る
補注、参考
(限界値)
N88-BASIC(86) の純正マニュアルには、単精度実数の範囲を次のように記しています。
(最小値) -1.070141E+38 〜 +1.70141E+38 (最大値)
この最小・最大値を実際に N88-BASIC で MKS$ すると、次のビット列(MSB←→LSB)が得られます。
最小値 MKS$( -1.70141E+38 ) → FF FF FF EB (H)
最大値 MKS$( +1.70141E+38 ) → FF 7F FF EB (H)
n88MKS 関数では、これらのビット列を、絶対値で 1.7014E+38 より大きい数値に対し採用しました。しかし(実験してみればわかるように)、ビット列を次のように設定しても、N88-BASIC
は障害(Overflow エラー等)を起こすことなく、期待通りの値に読みとってくれます。
最小値設定 FF FF FF FF (H) → CVS → -1.70141E+38
最大値設定 FF 7F FF FF (H) → CVS → +1.70141E+38
すなわち、
FF FF FF EB も FF FF FF FF も同じ -1.70141E+38 (最小値)
FF 7F FF EB も FF FF FF FF も同じ +1.70141E+38 (最大値)
とみなしてくれるわけです。
このエラーが生じないことを逆手にとり、変換前の float 値が N88-BASIC 単精度範囲を超えるなら
FF とし、範囲内最大・最小(EB)と区別するようなこともできるでしょう。が、実用的にはまったく無意味と判断しましたので(1.7014E+38
でも十分、無駄な範囲と思えたので)、ここでは採用していません。
ただし、所収関数を用いる前に取り扱う値が Overflow 等、障害を起こさないようアプリケーション側で考慮しておく必要があります。
(誤差)
ここで示したプログラムは、しょせんビット列変換に過ぎません。ゆえに変換元と変換先の値精度あるいは値範囲を越えない限り、二進法で見た場合の誤差は生じません。しかし本来的にある二進法・十進法間の変換に伴う誤差や、各処理系での丸め処理の相違に伴う誤差は逃れようがありません(別途、十進演算ルーチンを開発する以外、解決策はありません)。例えば次のような表示誤差は、必ず生じ得ます。
N88-BASICでの書き出し |
C(VC6.0)による読み取り
- printf - |
VB6.0による読み取り
- Debug.Print - |
MKD$(0.1) |
0.1000000015 |
0.100000001490116 |
MKD$(0.2) |
0.2000000030 |
0.200000002980232 |
 |
darokugawa@master.email.ne.jp |
このページ最終更新日 : October 31, 2003 |
|