Bitcasting float to uint in Haskell

by syoyo

MUDA の LLVM IR のバックエンドを書いていて float の即値のあつかいでハマりました.

float の値によってはアセンブラ(llvm-as)がエラーを出す.
なぜかと思って、LLVM IR のドキュメント(LangLef.html)を見たことろ、

Floating point constants use standard decimal notation (e.g. 123.421), exponential notation (e.g. 1.23421e+2), or a more precise hexadecimal notation (see below). The assembler requires the exact decimal value of a floating-point constant. For example, the assembler accepts 1.25 but rejects 1.3 because 1.3 is a repeating decimal in binary. Floating point constants must have a floating point type.

とあり、123.45 とかの表記はできるけど、
1.3 みたいに正確に表現できない値は NG だよ、
その場合は Hex 値で正確に与えてねとかかれています.

うーむややこしい.

これは文字列表現の数値のリード/ライトで
数値誤差がでないようにするためにするためなんだろうけど、
不正確な数値はアセンブルで跳ねてしまうのはちょっと厳格ですね。

さすが LLVM, ポータブルな浮動小数点表現を実現するために
APFloat を作ってしまうほどの漢気のあるライブラリだ。

—-

さて、MUDA は Haskell で書かれているので、
Haskell で float 値を hex 値にビットキャスト、
つまりメモリ上の値を変更せずに型だけ変える、
をする必要が出てくるのですが、
これがなかなか調べてもうまいやりかたが見つかりません。
結局半日くらいかけて Foreign モジュールの with, peek を使う方法が
一番簡潔にできることが分かりました。


import Numeric
import Foreign
import Foreign.Marshal.Alloc
import Foreign.C.Types

floatAsUInt :: Ptr CFloat -> IO CUInt
floatAsUInt p = do
  ui  IO (CUInt)
bitcastFloatToUInt f = do
  r <- with f floatAsUInt
  return r

main = do
  u  3fa66666

やっていることはこんな感じ。

– float の値を with を使って内部でメモリを確保して float 値を書き込む
– floatAsUInt を使って UInt 値でそのメモリの値を読み戻す

bitcastFloatToUInt はもう少し簡略化(難読化)することができて、


bitcastFloatToUInt :: CFloat -> IO (CUInt)
bitcastFloatToUInt f = with f $ \\p -> peek (castPtr p)

とできます.

これで float -> uint のビットキャストができました.
ただ、型のキャストなのに IO がからむのがちょっと嫌ですね.
unsafePerformIO で消してしまうのも手ですが.

Advertisements