整数と浮動小数点数
整数と浮動小数点数は計算処理の基本要素です。 これらの組み込みの表現は、数値プリミティブと呼ばれますが、 コード中に書かれる具体的なデータの表現は数値リテラルと知られています。 例えば、1は整数リテラルで1.0は浮動小数点数リテラルです。 これらのオブジェクトのメモリ上のバイナリ表現が数値プリミティブです。
Juliaにはさまざまな種類・範囲の数値プリミティブ型がありますが、 それぞれの型に対して算術演算やビット演算がすべて通常の数学関数と同様に定義されています。 こうした型や演算子は今時のコンピュータならネイティブに利用できる型や演算子と直接対応しているので、 Juliaでは計算資源を最大限に活用することができます。 さらに、Juliaではソフトウェアによる任意精度演算が利用可能なので、 ネイティブなハードウェア表現では事実上不可能な値も扱えますが、 パフォーマンスは比較的遅くなってしまいます。
以下にJuliaのプリミティブ数値型を挙げていきます。
- 整数型:
| 型 | 符号付? | ビット数 | 最小値 | 最大値 |
|---|---|---|---|---|
Int8 | ✓ | 8 | -2^7 | 2^7 - 1 |
UInt8 | 8 | 0 | 2^8 - 1 | |
Int16 | ✓ | 16 | -2^15 | 2^15 - 1 |
UInt16 | 16 | 0 | 2^16 - 1 | |
Int32 | ✓ | 32 | -2^31 | 2^31 - 1 |
UInt32 | 32 | 0 | 2^32 - 1 | |
Int64 | ✓ | 64 | -2^63 | 2^63 - 1 |
UInt64 | 64 | 0 | 2^64 - 1 | |
Int128 | ✓ | 128 | -2^127 | 2^127 - 1 |
UInt128 | 128 | 0 | 2^128 - 1 | |
Bool | N/A | 8 | false (0) | true (1) |
- 浮動小数点数型:
| 型 | 精度 | ビット数 |
|---|---|---|
Float16 | 半精度 | 16 |
Float32 | 単精度 | 32 |
Float64 | 倍精度 | 64 |
さらに、Juliaは複素数と有理数に完全対応していますが、こういったプリミティブ数値型の基礎にして構築されています。 すべての数値型はわざわざキャストしなくても自然に変換されます。 これは柔軟でユーザーも拡張可能な型昇格システムのおかげです。
整数
整数リテラルは標準的な方法で表現できます。
julia> 1
1
julia> 1234
1234整数リテラルのデフォルトの型はターゲットシステムのアーキテクチャが32bitか64bitかで変わります。
# 32-bit system:
julia> typeof(1)
Int32
# 64-bit system:
julia> typeof(1)
Int64Juliaの内部変数Sys.WORD_SIZEからターゲットシステムが32bitか64bitかが分かります。
# 32-bit system:
julia> Sys.WORD_SIZE
32
# 64-bit system:
julia> Sys.WORD_SIZE
64JuliaではInt や UInt といった型も定義されていますが、それぞれシステムネイティブの符号付き整数型・符号なし整数型のエイリアスです。
# 32-bit system:
julia> Int
Int32
julia> UInt
UInt32
# 64-bit system:
julia> Int
Int64
julia> UInt
UInt64大きな整数リテラルで、32bitでは表現できなくても64bitなら可能なものは、システムの整数型にかかわらず、常に64bitの整数が 生成されます。
# 32-bit or 64-bit system:
julia> typeof(3000000000)
Int64符号なし整数の入出力には、0xを頭につけた16進数の数字0-9a-f(入力には大文字のA-Fも利用可能)を使います。 符号なし整数値のサイズは、使った16進数の桁数で決まります。
julia> 0x1
0x01
julia> typeof(ans)
UInt8
julia> 0x123
0x0123
julia> typeof(ans)
UInt16
julia> 0x1234567
0x01234567
julia> typeof(ans)
UInt32
julia> 0x123456789abcdef
0x0123456789abcdef
julia> typeof(ans)
UInt64
julia> 0x11112222333344445555666677778888
0x11112222333344445555666677778888
julia> typeof(ans)
UInt128こうした挙動は、符号なし整数の16進リテラルを使うときは、通常、単なる整数値としてよりも長さの決まったバイト列として使うだろう という考察に基づいています。
変数ansには、対話セッションで最後に評価された式の値が代入されることを、思い出してください。 Juliaのコードを別の方法で実行しても、何も代入されません。
2進・8進のリテラルにも対応しています。
julia> 0b10
0x02
julia> typeof(ans)
UInt8
julia> 0o010
0x08
julia> typeof(ans)
UInt8
julia> 0x00000000000000001111222233334444
0x00000000000000001111222233334444
julia> typeof(ans)
UInt1282進・8進・16進リテラルに対しては、符号なし整数型が生成されます。 リテラルの先頭の桁が0ではない場合、バイナリデータのサイズは必要最低限のものになります。 先頭の桁が0の場合のサイズは、その数と同じ長さで先頭が1のリテラルが必要最低限とするサイズになります。 値がUInt128に収まらないものは、リテラルを使って書くことができません。
2進・8進・16進リテラルの直前に-をつけて、符号をつけることができます。 この場合生成されるのは、元の数とサイズの等しい符号なしの整数で、元の数の2の補数表現となるものです。
julia> -0x2
0xfe
julia> -0x0002
0xfffetypemin や typemax といった関数を使うと、 整数などのプリミティブ数値型が表現できる値の下限と上限がわかります。
julia> (typemin(Int32), typemax(Int32))
(-2147483648, 2147483647)
julia> for T in [Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128]
println("$(lpad(T,7)): [$(typemin(T)),$(typemax(T))]")
end
Int8: [-128,127]
Int16: [-32768,32767]
Int32: [-2147483648,2147483647]
Int64: [-9223372036854775808,9223372036854775807]
Int128: [-170141183460469231731687303715884105728,170141183460469231731687303715884105727]
UInt8: [0,255]
UInt16: [0,65535]
UInt32: [0,4294967295]
UInt64: [0,18446744073709551615]
UInt128: [0,340282366920938463463374607431768211455]typemin や typemaxといった関数の返す値は、常に引数の示す型になります。 (上記の式ではまだ紹介していない特徴である for ループ, 文字列,式展開)などを使っていますが、他のプログラミング経験のある人なら簡単に理解できるでしょう。)
オーバーフロー時の挙動
Juliaでは、値がその型の表現可能な上限を超えるとラップアラウンド(循環)がおこります。
julia> x = typemax(Int64)
9223372036854775807
julia> x + 1
-9223372036854775808
julia> x + 1 == typemin(Int64)
trueこのように、Juliaの整数演算は実質的に合同算術を行っています。 これは現代のコンピュータでの整数算術の実装方法の特徴を反映しています。 オーバーフローの起こりうるアプリケーションでは、オーバーフローによってラップアラウンドが起こっていないかの、明示的な検査が不可欠です。 別の方法としてはBigIntを使った任意精度演算がお薦めです。
除算エラー
整数の除算(div関数)では、2つ例外が起こる場合があります。0で割る場合と、負の下限の数(typemin)を-1で割る場合です。 どちらの場合もDivideError例外が投げられます。 剰余演算(remとmod)も第2引数が0の時も 、DivideError例外が投げられます。
浮動小数点数
浮動小数点数のリテラルは標準的なものです。必要に応じてE記法 も使えます。
julia> 1.0
1.0
julia> 1.
1.0
julia> 0.5
0.5
julia> .5
0.5
julia> -1.23
-1.23
julia> 1e10
1.0e10
julia> 2.5e-4
0.00025上記の計算結果はすべてFloat64の値です。 eの代わりにfをつけると、Float32リテラルの値になります。
julia> 0.5f0
0.5f0
SW
julia> typeof(ans)
Float32
julia> 2.5f-4
0.00025f0値をFloat32に変換するのも簡単です。
julia> Float32(-1.5)
-1.5f0
julia> typeof(ans)
Float3216進の浮動小数点数リテラルは、指数(基数は2)の前にpをつけて利用し、値はFloat64 になります。
julia> 0x1p0
1.0
julia> 0x1.8p3
12.0
julia> 0x.4p-1
0.125
julia> typeof(ans)
Float64半精度の浮動小数点数にも対応していますが (Float16)、ソフトウェア上の実装で、演算には Float32を使います。
julia> sizeof(Float16(4.))
2
julia> 2*Float16(4.)
Float16(8.0)桁の区切りとしてアンダースコア_を使うことができます。
julia> 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010
(10000, 5.0e-9, 0xdeadbeef, 0xb2)浮動小数点数の0
浮動小数点数には正と負の2つの0があります。 この2つは値は同じですが2進数の表現が異なり、bitstring関数で見ることができます。
julia> 0.0 == -0.0
true
julia> bitstring(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"
julia> bitstring(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"特殊な浮動小数点数の値
浮動小数点数の値で実数の数直線上に対応する点のないものが3つあります。
Float16 | Float32 | Float64 | 名前 | 説明 |
|---|---|---|---|---|
Inf16 | Inf32 | Inf | 正の無限大 | すべての有限の浮動小数点数より大きい値 |
-Inf16 | -Inf32 | -Inf | 負の無限大 | すべての有限の浮動小数点数より小さい値 |
NaN16 | NaN32 | NaN | 非数 | どんな浮動小数点数とも == の成り立たない値(自身とも) |
こうした非有限の浮動小数点数を、非有限・有限のものに対してどう順序づけるかという更なる議論は、数値の比較を参照してください。 IEEE 754 規格では、 これらの浮動小数点数の値はある種の算術演算の結果として得られます。
julia> 1/Inf
0.0
julia> 1/0
Inf
julia> -5/0
-Inf
julia> 0.000001/0
Inf
julia> 0/0
NaN
julia> 500 + Inf
Inf
julia> 500 - Inf
-Inf
julia> Inf + Inf
Inf
julia> Inf - Inf
NaN
julia> Inf * Inf
Inf
julia> Inf / Inf
NaN
julia> 0 * Inf
NaN関数のtypemin と typemaxは浮動小数点数型に対しても適用できます。
julia> (typemin(Float16),typemax(Float16))
(-Inf16, Inf16)
julia> (typemin(Float32),typemax(Float32))
(-Inf32, Inf32)
julia> (typemin(Float64),typemax(Float64))
(-Inf, Inf)計算機イプシロン
ほとんどの実数は浮動小数点数では、正確に表現できません。 そのため、多くの目的で、隣接する浮動小数点数の距離を知ることが重要です。 これは、計算機イプシロンとして、知られています。
Juliaではepsで、1.0と表現可能な次に大きな浮動小数点値との距離が、分かります。
julia> eps(Float32)
1.1920929f-7
julia> eps(Float64)
2.220446049250313e-16
julia> eps() # same as eps(Float64)
2.220446049250313e-16これら2.0^-23と2.0^-52は、それぞれFloat32とFloat64の値です。 eps関数は、浮動小数点数値を引数にとることもできて、その値と次の表現可能な浮動小数点数値との差の絶対値 が得られます。 つまり、eps(x)はxと同じ型の値を返すので、x + eps(x)はxの次に大きな表現可能な浮動小数点数値です。
julia> eps(1.0)
2.220446049250313e-16
julia> eps(1000.)
1.1368683772161603e-13
julia> eps(1e-27)
1.793662034335766e-43
julia> eps(0.0)
5.0e-324隣接する表現可能な浮動小数点数の距離は一定ではなく、小さいほど小さく、大きいほど大きくなります。 言い換えると、表現可能な浮動小数点数の数直線は、0の近くで一番密で、0から遠ざかるほど指数的に疎になります。 定義により、eps(1.0)はeps(Float64)と同じです。 というのも、 1.0は64bitの浮動小数点数値だからです。
Juliaではnextfloatやprevfloatといった関数も利用可能で、それぞれ表現可能な浮動小数点数で 次に大きなものと小さなものを返します。
julia> x = 1.25f0
1.25f0
julia> nextfloat(x)
1.2500001f0
julia> prevfloat(x)
1.2499999f0
julia> bitstring(prevfloat(x))
"00111111100111111111111111111111"
julia> bitstring(x)
"00111111101000000000000000000000"
julia> bitstring(nextfloat(x))
"00111111101000000000000000000001"この例では隣接する表現可能な浮動小数点数は、2進整数表現も隣接するという一般原則が強調されています。
端数処理
浮動小数点数表現で正確に表現できない数は、適当な表現可能な数に丸める必要があります。 しかしその処理方法は、必要に応じて、IEEE 754 規格に記載されている丸めモードにそって変えることができます。
デフォルトの丸めモードは常に最近接丸めで、これは一番近い表現可能な数に丸める方法で、最近接の表現可能な数が2つある場合は 最下位ビットが偶数になるように丸めます。
背景と参考資料
浮動小数点数の算術には低レベルの実装の詳細なじみのない人が驚くような微妙な点がたくさんあります。 しかしその微妙な点の詳細は、大抵の科学計算に関する本や下記の資料に記載されています。
- 浮動小数点数の演算 に関する最も信頼のおけるガイドは IEEE 754-2008 規格です。 しかし、無料・オンラインでは利用不可。
- 浮動小数点数の表現方法に関する簡潔だが明快な解説として、John D.Cook'の 記事 を参照のこと。 入門 では理想化された抽象的な実数と表現とのの違いからおこる問題が書かれている。
- 同様にお薦めなのが、Bruce Dawsonの浮動小数点数に関する一連のブログ投稿.
- 浮動小数点数に関する秀逸で深遠な議論と、その計算時に遭遇する数的精度の問題は David Goldbergの論文 すべとの計算科学者が浮動小数点数演算について知るべきことを参照のこと。
- 浮動小数点数に関する歴史・理論・問題さらにその他の多くの数値計算に関する話題を集めた広範囲にわたる文書として著作集を参照のこと。 著者 William Kahanは「浮動小数点の父」として広く知られている。 特に興味深いのは 浮動小数点の老人へのインタビュー。
任意精度演算
整数と浮動小数点数に対して任意精度で計算できるように、JuliaはGNU Multiple Precision Arithmetic Library (GMP) と GNU MPFR Libraryに対するラップをそれぞれ行っています。 BigInt と BigFloatの型がそれぞれ任意精度の整数と浮動小数点数として利用可能です。
コンストラクタは、プリミティブ数値型から、これらの型を生成し、parseはAbstractStringからこれらの型を生成します。 生成した値は他の数値型と演算することができ、これはJuliaの 型昇格と型変換のしくみのおかげです。
julia> BigInt(typemax(Int64)) + 1
9223372036854775808
julia> parse(BigInt, "123456789012345678901234567890") + 1
123456789012345678901234567891
julia> parse(BigFloat, "1.23456789012345678901")
1.234567890123456789010000000000000000000000000000000000000000000000000000000004
julia> BigFloat(2.0^66) / 3
2.459565876494606882133333333333333333333333333333333333333333333333333333333344e+19
julia> factorial(BigInt(40))
815915283247897734345611269596115894272000000000しかしプリミティブ型とBigInt/BigFloatとの型昇格は自動では行われず、明示的に記述する必要があります。
julia> x = typemin(Int64)
-9223372036854775808
julia> x = x - 1
9223372036854775807
julia> typeof(x)
Int64
julia> y = BigInt(typemin(Int64))
-9223372036854775808
julia> y = y - 1
-9223372036854775809
julia> typeof(y)
BigIntBigFloat演算のデフォルトの精度(仮数のビット数)や丸めモードは、 setprecisionやsetroundingをグローバル環境で呼び出すことで変更可能で、 以降のすべての計算がこの変更の影響を受けます。 一方、精度や丸めモードの変更を特定のブロック内の実行だけにとどめたい場合は、同じ関数を doブロック内で呼び出せば可能です。
julia> setrounding(BigFloat, RoundUp) do
BigFloat(1) + parse(BigFloat, "0.1")
end
1.100000000000000000000000000000000000000000000000000000000000000000000000000003
julia> setrounding(BigFloat, RoundDown) do
BigFloat(1) + parse(BigFloat, "0.1")
end
1.099999999999999999999999999999999999999999999999999999999999999999999999999986
julia> setprecision(40) do
BigFloat(1) + parse(BigFloat, "0.1")
end
1.1000000000004数値リテラル係数
数式一般を明快にするために、Juliaでは変数の直前に数値リテラルを書いて掛け算を表すことができます。 これを使って多項式を書くとわかりやすくなります。
julia> x = 3
3
julia> 2x^2 - 3x + 1
10
julia> 1.5x^2 - .5x + 1
13.0巾関数もきれいに書けます。
julia> 2^2x
64数値リテラル係数の優先順位は、マイナスなどの単項演算子よりも低いです。 そのため -2xは(-2) * xに解析され、 √2x は (√2) * xと解析されます。 しかし巾関数と結合するときは、単項演算子と同様に解析されます。 例えば、 2^3x は 2^(3x)、 2x^3 は 2*(x^3)というように解析されます。
julia> 2(x-1)^2 - 3(x-1) + 1
3!!! 注意 暗黙的な掛け算に使われる数値リテラル係数は、他の2項演算子、例えば、乗法(*)や除法 (/, \, //)よりも 優先順位は高いです。 例えば、1 / 2imは-0.5imと等しく、6 // 2(2 + 1)は1 // 1と等しいです。
さらに、括弧でくくった式を変数に対する係数として扱い、式と変数の掛け算を行うことができます。
julia> (x-1)x
6しかし、括弧でくくった式を2つ並べたり、括弧でくくった式の前に変数を置いても、掛け算とはみなされません。
julia> (x-1)(x+1)
ERROR: MethodError: objects of type Int64 are not callable
julia> x(x+1)
ERROR: MethodError: objects of type Int64 are not callableどちらの式も関数適用として解釈されます。 数値リテラルではない式に括弧でくくったものを続けると、括弧の中のものに対する関数適用だと解釈されます ( 関数に関する詳細は関数を参照)。 どちらのケースも左側が関数ではないためエラーがおこります。
こうした構文の拡張によって、普通に書いた数式の見た目の煩わしさが大幅に減っています。 掛け算をおこなうには、数値リテラル係数と、識別子や括弧でくくった式の間に、空白を入れてはいけないことに注意してください。
構文の競合
リテラル係数を並べる構文と競合しうる構文が2つあります。 16進整数リテラルと浮動小数点数リテラルの指数表記です。 競合する例を挙げてみると
- 16進整数リテラルの
0xffは、数値リテラルの0と変数xffの掛け算と解釈できる。 - 浮動小数点リテラルの
1e10は数値リテラル1と変数e10の掛け算と解釈できる。E形式も同様。 - 32bit浮動小数点リテラルの
1.5f22数値リテラル1.5と変数f22の掛け算と解釈できる。
すべてのケースで、数値リテラルとしての解釈を好んで、曖昧さを解決しています。
0xで始まる式は、常に16進リテラルである。eかEを伴う数値リテラルで始まる式は、常に浮動小数点リテラルである。fを伴う数値リテラルで始まる式は、常に32bit浮動小数点リテラルである。
Eは歴史的な理由もあって、数値リテラルの中でeと同等に扱われますが、Fの場合はまた別で数値リテラルの中でもfのようには扱われません。 なので、Fを伴う数値リテラルで始まる式は、数値リテラルと変数の掛け算として解釈されます。 例えば、 1.5F22は1.5 * F22と等しくなります。
0と1のリテラル
Juliaには、0や1のリテラルを返す関数で、指定した型や、変数と同じ型となるものがあります。
| 関数 | 説明 |
|---|---|
zero(x) | 型xの0リテラルか、変数xと同じ0リテラル |
one(x) | 型xの1リテラルか、変数xと同じ1リテラル |
これらの関数は 数値の比較を行うときに、 型変換の不要なオーバーヘッドを 避けるために役立ちます。
Examples:
julia> zero(Float32)
0.0f0
julia> zero(1.0)
0.0
julia> one(Int32)
1
julia> one(BigFloat)
1.0