Single型あるいはDouble型の変数に格納された浮動小数点数について、小数部だけを単純に切り落とす方法として、Math.Truncate関数があるが、メソッドの呼び出しのためのオーバーヘッドが大きい。また、C、C++、C#では、キャストにより整数型の変数に変換する際には、少なくとも正の値であれば、単純に小数部が切り落とされるが、Visual BasicのCIntやCLngによる変換では、複雑な丸め操作を伴う。
ここでは、浮動小数点数から整数へ小数部を切り飛ばすことによって変換する方法で、可読性には欠けるがインラインで記述・展開できる方法を示す。
Dim i as Long Dim d as Double i = CLng(Math.Truncate(d))
に代わる方法である。以下のような構造体を用いたトリッキーな方法であるが、要するに、Double型変数とLong型あるいはInteger型の変数をメモリ上で同じアドレスを共有するように工夫し、Double型変数の内容を整数型変数を通じてbit単位で操作する。
'構造体のメンバー変数としてDouble型とLong型(共に同じ '8Byte長)をメモリ上の同じ位置を共有させることができる。 '同様に、メモリ上の一部を4Byte長のInteger型で共有させる。 'CやC++で言うところの共用体(union)と同等である。 'この機能は.Net Frameworkの以下の名前空間で提供されている。 Imports System.Runtime.InteropServices '属性を用いてメンバー変数のメモリ上の位置を細かく指定できる。 '詳しくは、.Net Frameworkのドキュメントを参照のこと。 <StructLayout(LayoutKind.Explicit)> _ Private Structure DU <FieldOffset(0)> Public D As Double <FieldOffset(0)> Public L As Long <FieldOffset(4)> Public I4 As Integer Public Const FracBitLength As Integer = 52I 'ビットシフトの方向が逆であったものを修正(2008/2/19) Public Const ImplicitBit As Long = 1L << FracBitLength Public Const ExplicitTopBit As Long = ImplicitBit \ 2 Public Const GetFrac As Long = ImplicitBit - 1 Public Const GetSgn As Long = &H8000000000000000L Public Const GetSgnFrac As Long = GetSgn Or GetFrac End Structure
まず、Long型の変数へ変換する方法を示す。
'変換操作を行うDUの変数を宣言する。
Dim f As DU
'変換結果を代入する変数を宣言する。
Dim i As Long
'DUのメンバー変数Dに変換したい値を代入する。
f.D = ...
'操作の基本的な考え方は、浮動小数点数の指数部を読み取って仮数部の
'中で小数点の位置を求め、仮数部のデータを右にシフトして小数点以下
'の数値を追い出してしまうことである。
'指数部の読み取り。
'f.I4 >> 20 により、指数部のデータを取り出す。
'これを1023から引いて 仮数部の中での小数点の位置をbit単位で得る。
' (最上位bitから数えてマイナスを付した値)
'仮数部のbit長52を足して 必要なシフト量sdを得る。
Dim sd As Integer = 1075 - (f.I4 >> 20)
If sd > DU.FracBitLength Then
'sdが仮数部のbit長より大きければ、1未満の小数であり、ゼロを返す。
i = 0
ElseIf sd < 0 Then
'sdが負の場合、すべての桁の値を仮数部の精度では表せないような
'大きな数値である。そのまま、CLngで変換して代入すればよい。
'sd < 0を考慮する必要がなければ、このElseIfブロックは不要。
i = CLng(f.D)
Else
'仮数部のみを取り出してシフト操作を行う。
'f.L And DU.GetSgnFrac により、符号と仮数部をすべて取り出し、
'Or DU.ImplicitBit により、浮動小数点数の場合は「暗黙」に存在が
' 仮定されているbitを明示的にセットし、
'>> sd により1の位が最下位bitになるようにシフトする。
' Long型であれば、符号部は保存される。
i = ((f.L And DU.GetSgnFrac) Or DU.ImplicitBit) >> sd
End If
Integer型の変数へ変換する場合は、上述のElseブロック内の代入文の右辺全体をCIntで囲めばよい。
ここではコメントを付して示したので行数が多くなっているが、構造体DUの宣言、作業用と代入用の値の宣言を除けば、シフト量sdの計算に2つの演算、sdの比較と分岐、及び仮数部の取り出しとシフト操作に3つの演算しか行っておらず、Math.Truncate関数の呼び出しのオーバーヘッドと実際の演算、及びCLngの変換操作に費やす演算量(命令数)に比べればはるかにコンパクトなオブジェクトコードになり、高速に実行される。
整数型の変数に変換せず、Double型のままで小数部を切りとばす、すなわち、Math.Truncateと全く同じ機能をインラインで記述し、f.Dの値を上書きするには、上述のsdの算出までは同様で、引き続いて、以下のとおり処理すればよい。
If sd > DU.FracBitLength Then f.D = 0.0# ElseIf sd >= 0 then '1L << sd により、1に0がsd個続く2進数を用意し、 '-1 により、1がsd個続く値にして、 'Not で反転して、下位のsd bitが0で、残りの上位bitが ' 1であるマスク用のデータを生成する。 f.L = f.L And (Not ((1L << sd) - 1)) End If 'sd < 0 の時は、小数点以下の位はない大きな値なので、何もしない。