整数と浮動小数点数

`

整数と浮動小数点数

整数と浮動小数点数は計算処理の基本要素です。 これらの組み込みの表現は、数値プリミティブと呼ばれますが、 コード中に書かれる具体的なデータの表現は数値リテラルと知られています。 例えば、1は整数リテラルで1.0は浮動小数点数リテラルです。 これらのオブジェクトのメモリ上のバイナリ表現が数値プリミティブです。

Juliaにはさまざまな種類・範囲の数値プリミティブ型がありますが、 それぞれの型に対して算術演算やビット演算がすべて通常の数学関数と同様に定義されています。 こうした型や演算子は今時のコンピュータならネイティブに利用できる型や演算子と直接対応しているので、 Juliaでは計算資源を最大限に活用することができます。 さらに、Juliaではソフトウェアによる任意精度演算が利用可能なので、 ネイティブなハードウェア表現では事実上不可能な値も扱えますが、 パフォーマンスは比較的遅くなってしまいます。

以下にJuliaのプリミティブ数値型を挙げていきます。

`

符号付?ビット数最小値最大値
Int88-2^72^7 - 1
UInt8802^8 - 1
Int1616-2^152^15 - 1
UInt161602^16 - 1
Int3232-2^312^31 - 1
UInt323202^32 - 1
Int6464-2^632^63 - 1
UInt646402^64 - 1
Int128128-2^1272^127 - 1
UInt12812802^128 - 1
BoolN/A8false (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)
Int64

Juliaの内部変数Sys.WORD_SIZEからターゲットシステムが32bitか64bitかが分かります。

# 32-bit system:
julia> Sys.WORD_SIZE
32

# 64-bit system:
julia> Sys.WORD_SIZE
64

JuliaではIntUInt といった型も定義されていますが、それぞれシステムネイティブの符号付き整数型・符号なし整数型のエイリアスです。

# 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)
UInt128

2進・8進・16進リテラルに対しては、符号なし整数型が生成されます。 リテラルの先頭の桁が0ではない場合、バイナリデータのサイズは必要最低限のものになります。 先頭の桁が0の場合のサイズは、その数と同じ長さで先頭が1のリテラルが必要最低限とするサイズになります。 値がUInt128に収まらないものは、リテラルを使って書くことができません。

2進・8進・16進リテラルの直前に-をつけて、符号をつけることができます。 この場合生成されるのは、元の数とサイズの等しい符号なしの整数で、元の数の2の補数表現となるものです。

julia> -0x2
0xfe

julia> -0x0002
0xfffe

typemintypemax といった関数を使うと、 整数などのプリミティブ数値型が表現できる値の下限と上限がわかります。

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]

typemintypemaxといった関数の返す値は、常に引数の示す型になります。 (上記の式ではまだ紹介していない特徴である 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例外が投げられます。 剰余演算(remmod)も第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)
Float32

16進の浮動小数点数リテラルは、指数(基数は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つあります。

Float16Float32Float64名前説明
Inf16Inf32Inf正の無限大すべての有限の浮動小数点数より大きい値
-Inf16-Inf32-Inf負の無限大すべての有限の浮動小数点数より小さい値
NaN16NaN32NaN非数どんな浮動小数点数とも == の成り立たない値(自身とも)

こうした非有限の浮動小数点数を、非有限・有限のものに対してどう順序づけるかという更なる議論は、数値の比較を参照してください。 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

関数のtypemintypemaxは浮動小数点数型に対しても適用できます。

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^-232.0^-52は、それぞれFloat32Float64の値です。 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ではnextfloatprevfloatといった関数も利用可能で、それぞれ表現可能な浮動小数点数で 次に大きなものと小さなものを返します。

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つある場合は 最下位ビットが偶数になるように丸めます。

`

背景と参考資料

浮動小数点数の算術には低レベルの実装の詳細なじみのない人が驚くような微妙な点がたくさんあります。 しかしその微妙な点の詳細は、大抵の科学計算に関する本や下記の資料に記載されています。

`

任意精度演算

整数と浮動小数点数に対して任意精度で計算できるように、JuliaはGNU Multiple Precision Arithmetic Library (GMP)GNU MPFR Libraryに対するラップをそれぞれ行っています。 BigIntBigFloatの型がそれぞれ任意精度の整数と浮動小数点数として利用可能です。

コンストラクタは、プリミティブ数値型から、これらの型を生成し、parseAbstractStringからこれらの型を生成します。 生成した値は他の数値型と演算することができ、これは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)
BigInt

BigFloat演算のデフォルトの精度(仮数のビット数)や丸めモードは、 setprecisionsetroundingをグローバル環境で呼び出すことで変更可能で、 以降のすべての計算がこの変更の影響を受けます。 一方、精度や丸めモードの変更を特定のブロック内の実行だけにとどめたい場合は、同じ関数を 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^3x2^(3x)2x^32*(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進整数リテラルと浮動小数点数リテラルの指数表記です。 競合する例を挙げてみると

すべてのケースで、数値リテラルとしての解釈を好んで、曖昧さを解決しています。

Eは歴史的な理由もあって、数値リテラルの中でeと同等に扱われますが、Fの場合はまた別で数値リテラルの中でもfのようには扱われません。 なので、Fを伴う数値リテラルで始まる式は、数値リテラルと変数の掛け算として解釈されます。 例えば、 1.5F221.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