12
4

More than 1 year has passed since last update.

Haskellの数値変換(wiki.haskell.orgからの邦訳)

Last updated at Posted at 2021-12-18

訳者まえがき

Haskellには初心者殺しの関所が幾つもありますが、その一つに

  • 簡単な四則演算すら型が合わずコンパイルできない

があります。
この問題を解決するためにアチコチの本やサイトを読みましたが、結局一番分かりやすかったのはwiki.haskell.orgの記事でした。
自分のために翻訳したものを公開します(最後に独自に数値型の階層と変換関数の一覧もまとめています)。
日本語版公開にあたってはHaskellWiki:Copyrightsを参照し、問題ないとの判断をしていますが、万が一権利侵害等がありましたら、ご指摘ください。許諾等の処理が完了するまで公開を中止します。

本文

以下はConverting numbersの日本語訳です。

Haskellにおける数値型間の変換は明示的に行う必要があります。
これは他の多くの伝統的言語(CやJavaなど)で暗黙にキャストが行われるのとは異なります。

1. 整数型との間の変換

整数(Integral)型には整数だけが含まれます(分数は含まれません)
最も一般的に使われるのは次の二つの型です。

  • Integerは他の言語ではしばしば「bignum」または「big-integer」と呼ばれる任意精度の整数です。
  • Intは固定幅のマシン固有の整数で、最小保証範囲は-2^29〜2^29-1ですが、実際にはもっと大きくなることが多いです。GHCコンパイラのx86-64バージョンでは、符号付64ビット整数を扱うことができます。

整数型からの変換に主に使われるのはfromIntegralです。
任意の整数型を任意の数字(Num)型(Int,Integer,Rational,Double)に変換します。

整数型数値の変換.hs
fromIntegral :: (Num b, Integral a) => a -> b

例えばIntである値nのルートをとる場合、単純にsqrt nとは書けません。なぜならsqrtは浮動小数点数にのみ適用可能だからです。
したがって、代わりにsqrt (fromIntegral n)と明示的にnNum型に変換し、浮動小数点の値に降りていけるようにする必要があります。

fromIntegerはIntegerから任意の数字型への変換の特殊ケース.hs
fromInteger :: Num a => Integer -> a
同じように整数型からIntegerへの変換の特殊ケース.hs
toInteger:: Integral a => a -> Integer

2. 実数型と実数分数型(有理数型)間の変換

実数分数(RealFractional)型は、整数と分数のどちらも属することができます。
最も一般的に使われる型は次のとおり。

  • Rationalは任意精度の分数
  • Doubleは倍精度の浮動小数点数

実数(Real)型は整数(Integral)型と実数分数(RealFrac)型の両方を含みます。
型名の"real"は複素数が除外されていることを示しています。
実数(Real)型からの変換に使われるのはrealToFracで、任意の実数型を任意の分数型(Fractional)型(Rational,Doubleの属する型)に変換します。

realToFrac.hs
realToFrac:: (Real a, Fractional b) => a -> b

realToFracRealFrac型間の変換にも使うことができます。
(後述のとおり、realToFracを浮動小数点型間の変換に使うことは避けてください)

Rationalから分数への変換.hs
fromRational :: Fractional a => Rational -> a
実数から有理数(Rational)への変換?.hs
toRational :: Real a => a -> Rational

3. 実数分数から整数への変換

整数型は非整数を表すことができないので、これは本質的に損失のある変換です。 変換方法に応じて、次のいずれかを選択することができます:

非整数→整数?.hs
ceiling  :: (RealFrac a, Integral b) => a -> b
floor    :: (RealFrac a, Integral b) => a -> b
truncate :: (RealFrac a, Integral b) => a -> b
round    :: (RealFrac a, Integral b) => a -> b

4. 異なる精度の浮動小数点間の変換

FloatDoubleの間の変換は、GHC.FloatモジュールのGHC固有の関数を使用します。

GHC.Float.hs
float2Double :: Float -> Double
double2Float :: Double -> Float

RealToFracを使用して浮動小数点型同志の変換は避けてください。中間型であるRationalは、無限大またはNaNなどの例外的な値を表すことができません。 GHCチケット#3676を参照してください。

5. 自動変換 

人々は何度も数値間の自動変換を要求しますが、これは通常良くない考えとされています。詳細は一般的な数値型に関する考えを参照してください。

6. 例

2つの座標系の間で変換するいくつかの関数を記述しようとしています。
coord1を呼び出す最初の座標系は、左上が(0、0)で始まり、右下が(500,500)で終わります。
coord1の座標は(Int、Int)型です。
coord2と呼ばれる第2の座標系は、左下が(0.0、0.0)で始まり、右上の(1.0,1.0)で終わります。
coord2の座標は、(Float、Float)型です。
型チェックが通るように以下の2つの関数を書き換えます。

このままだとコンパイルは通らない.hs
coord1ToCoord2 :: (Int, Int) -> (Float, Float)
coord1ToCoord2 (x, y) = (x/500, (500-y)/500)

coord2ToCoord1 :: (Float, Float) -> (Int, Int)
coord2ToCoord1 (x, y) = (500/(1/x), 500 - 500/(1/y))
こうなって欲しい.hs
 coord1ToCoord2 (0, 0) -> (0.0, 1.0)
 coord1ToCoord2 (250, 250) -> (0.5, 0.5)
 coord1ToCoord2 (350, 350) -> (0.7, 0.3)
 coord1ToCoord2 (500, 500) -> (1.0, 0.0)

 coord2ToCoord1 (0.0, 0.0) -> (0, 500)
 coord2ToCoord1 (0.5, 0.5) -> (250, 250)
 coord2ToCoord1 (0.7, 0.7) -> (350, 150)
 coord2ToCoord1 (1.0, 1.0) -> (500, 0)

解法はfromIntegralroundを使うことです。

コンパイル通る例.hs
coord1ToCoord2 :: (Int, Int) -> (Float, Float)
coord1ToCoord2 (x, y) = (fromIntegral x/500, (500 - fromIntegral y)/500)

coord2ToCoord1 :: (Float, Float) -> (Int, Int)
coord2ToCoord1 (x, y) = (round (500 * x), round (500 - 500 * y))

訳者あとがき

特にクラス名の訳語は最適なものが見つからないので、標準的な訳語があるものは教えていただきたいです。

この記事の理解を深めるため、私の書いた図を二つ付けておきます。(かなり昔に作ったため、陳腐化や間違いがあればご指摘くださると助かります)

a. 数値クラスの階層関係

image.png

参考:hackage , Haskell Language Report 2010

b. 数値型間の変換表

image.png

参考:「Real World Haskell」6章 6.4.4数値型 表6-4

12
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4