ダブル様の恥じらい

VB6による以下のコード、「dbl」と「CDbl(CStr(dbl))」は等しくない。小学生でもできる2÷3のあまのじゃくな値をそのままテキストファイルに保存したい!誰かこの素朴な願いを叶えてくれたまえ…っ。

    Dim dbl as Double

    dbl = 2# / 3#

    If dbl <> CDbl(CStr(dbl)) Then
        ' 等しくない
        Stop
    End If

Double様は乙女なので、簡単に体を見せたりはしない。String君を着て違う値に見せかけてしまう今どき珍しい娘なのだ。

デバッガで見る限り、両者は同じ値なのが困る。どうせデバッガもCStr()に媚びを売ってるんだろう。バイナリ版が見たいんだけどなあ。

分かっているさ……分かってる。いつかこれが問題になるときが来ると思っていたさ。

ところが大人の事情により、どうしてもこの「2÷3」の厳密な値を文字列に変換してから、元の値に戻さなけりゃならん。ということで変数をHEXダンプ文字列にでもするVB関数を作ってる人がいるだろう、と思いきや、普段の行いが良すぎるせいか見つからない。

かといって、この程度で余計なモジュール(DLL)を増やすなんてしたくない。そりゃ、値を渡してHEXダンプ作るなんてバイナリ好きの変態言語なら屁でもない。なんのためにセレブ用のVBを貫いてきたんだ。手を抜くためじゃないか。

というわけで関数を作る。変数のアドレスはVarPtr()という奴でとれる。でもポインタの中を覗く方法が分からない……。

ここでmovsbのマシン語コードを作って呼びだすなんてチート行為はゲイツ法違反だ(その気になればできるようだ。http://nienie.com/~masapico/doc_FuncPtr.html)。仕方ないのでAPI「RtlMoveMemory」を使うことにした。サイトを見て思い出した。機能的には初歩というより空気中の二酸化炭素みたいな存在で使わないもんだから忘れてたよこれ……。こいつを使って、DoubleからByteに1個ずつ転送して、それをダンプに変えて連結すれば、16進数の文字列になってくれる。

というか本当に誰も作ってないのか!?メモ程度で良いからソース集積場みたいなところにでもあるかと思ったが、あきらめて作った。なんとも懐かしい響きのあるコードだ。昔は良くこういうの作ったのぉ。ついにDoubleとStringは結ばれました。出産は年末の予定。

' メモリ転送API
Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    ByRef pDst As Any, ByRef pSrc As Any, ByVal dwSize As Long)

' Doubleを16進数ダンプ文字列に変換
Public Function DoubleToHex(ByVal dbl As Double) As String

    Dim str As String, i As Long
    Dim C As Byte
    Dim pDouble As Long, pByte As Long

    ' Double変数とByte変数のポインタを取得
    pByte = VarPtr(C)
    pDouble = VarPtr(dbl)

    ' Double変数のサイズ回、1バイトずつ2文字のHEXダンプに変える
    For i = 0 To 7
        ' Byte変数に1バイトだけ転送
        Call MoveMemory(ByVal pByte, ByVal pDouble + i, 1)
        ' Byte変数を2文字のHEXダンプに変えて連結
        str = str & Format(Hex(C), "00")
    Next

    ' 終了
    DoubleToHex = str

End Function

' 16進数ダンプ文字列をDoubleに変換
Public Function HexToDouble(ByVal str As String) As Double

    Dim i As Long
    Dim dbl As Double, C As Byte
    Dim pDouble As Long, pByte As Long

    ' Double変数とByte変数のポインタを取得
    pByte = VarPtr(C)
    pDouble = VarPtr(dbl)

    For i = 0 To 7
        ' Stringの2文字分を、HEXダンプからByteに変換
        C = CByte("&H" & Mid(str, 1 + i * 2, 2))
        ' 1バイト分をDouble変数に転送
        Call MoveMemory(ByVal pDouble + i, ByVal pByte, 1)
    Next

    ' 終了
    HexToDouble = dbl

End Function

恥じらいのなくなったDoubleは、生まれたままの姿で保存と復帰ができるようになった。「&H」された結果。

どうでもいいことだが、「2÷3」は"555555555555E53F"だそうである。ふーん。スリーサイズは55,55,55、以下同文。


2÷3の値はもう忘れました。で、やばいのはここからかもしれない。Desigeonは現在、ひたすらCStr臭の漂う文字列でDoubleを保存しているので、誤差がでまくりなはずなのだ。これを、なるべく元の値を残しつつ保存するには、全面的にバイナリファイルに移行するか(無謀)、Doubleの保存だけすべて上記の方法に切り替える(無謀)しかない。

何か他の方法を教えてくれる神さまが降臨したら、今日は発泡酒でも飲もうかと思います。

手抜き環境使ってると、こういうところから徐々に苦労が始まる。セレブに明日はないことが分かった。

ところで、上で作った関数名「DoubleToHex」で検索したらきちんとサイトが見つかった後日談は、書いた直後に付け足しておきます。8バイト分をまるごとByte配列に転送しても大丈夫みたいですね。まる。