Source

ocaml-zippy-tutorial-in-japanese / caml_num.rst

OCaml の数値型

OCaml 数値型で標準で使えるものは、 int, float , int32, int64, native_int, nat, big_int, Complex.t の 8つ:

int

標準の整数。 32bit 環境では 31bit, 64bit 環境では 63bit 符号付き整数。

float

倍精度(64bit)浮動小数点数。 IEEE 754 で言うところの double であって、float ではない。

nativeint

OS でサポートする基本整数幅を持つ。 32bit 環境では 32bit, 64bit 環境では 64bit 符号付き整数。

int32

32bit 符号付き整数。

int64

64bit 符号付き整数。

nat

num ライブラリで定義されている無限制精度自然数。

big_int

num ライブラリで定義されている無限制精度符号付数。

Complex.t

複素数。 単に float 二つ組で表現されている。

int

OCaml で「整数」と言うと int を指す。 32bit 環境では 31bit, 64bit 環境では 63bit の符号付き整数。 「失われた 1bit」は GC が int とポインタを識別するために使われる。 この 1bit のおかげで int は box化されず、そのまま扱うことができるので 「1bit」が失われていないが、box化が必要な nativeint と比べると高速に動作する。

定数の書式

通常の10進表記の他に、2進、8進、16進での表記が可能:

# 10;;
- : int = 10
# 0b10;;
- : int = 2
# 0o10;;
- : int = 8
# 0x10;;
- : int = 16

基本的な演算子

Pervasives に基本的は演算子 (+), (-), ( * ), (/) などが 記載されているが、ほとんどがプリミティブ (external 宣言による C関数呼び出し)である。

int 掛け算の二項演算子 * を関数(CR jfuruse:?)として使う場合、 (*) とスペース無しで書くとコメント様の文面と受け取られエラーになる。 必ず括弧と * の間に空白を入れること: ( * )

環境によるサイズの違い

int は環境によってサイズが異なることに注意してほしい。その環境での int の 上限と下限は Pervasives モジュール内の変数 max_int, min_int に格納されている。 64bit アーキテクチャでの int を 32bit アーキテクチャの OCaml プログラムに渡して int と解釈させると問題が発生する。(CR jfuruse: ほんと?)

string_of_int x |> ... |> int_of_string の例

marshal の例

オーバーフロー、アンダーフロー

max_int, min_int を超える演算結果はオーバフローおよびアンダーフローを起こすが、 起こした事実は特にレポートされない。オバーフロー、アンダフローを起こしたか知りたい場合は 自分で何かしら検査コードを書く必要がある:

# max_int + 1 = min_int;;
- : bool = true               (* オーバーフローが起こっている *)

float

倍精度(64bit)浮動小数点数。 IEEE754 や C で言うところの double である。 単精度(32bit)浮動小数点数(いわゆる IEEE754 や C で言うところの float) は OCaml には標準では用意されていない。

定数の書式

OCaml では定数(どころか他の全てもそうだが)にはオーバーローディングがない。 そのため、 float はもし小数点以下が 0 だったとしても、 int との 混乱を避けるため、小数点を付けなければならない:

# 1.23;;
- : float = 1.23
# 1.0;;
- : float = 1.
# 42.;;
- : float = 42.

最後の 42. のように少数点以下を書かない記法は他の言語ではエラーになる場合があるので注意が必要だ。

基本的な演算子

int と同様、Pervasives に基本的は演算子 (+.), (-.), ( *.), (/.) などが宣言されている。 演算子の最後に . が付いていることに注意。 OCaml ではオーバーローディングが無いため、 int の演算子 (+), (-), ( * ), (/) と同じ名前を float 用の演算子名として用いることができない。そのための苦肉の策である。

後述の int 以外の演算子でも普通の四則演算子を使う も参照のこと。 CR jfuruse: link

nan

float の演算は IEEE754 に準拠するため、無限値や nan などが存在する。 浮動小数点数がこれらの特殊な常態かどうかを調べるためには Pervasives.classify_float を使う。

正確な文字列表現

float の内容を文字列に変換するには string_of_float を使うが、 この関数は小数点以下12桁までしか文字列に変換しない:

(* pervasives.ml *)
let string_of_float f = valid_float_lexem (format_float "%.12g" f);;

OCaml では float を完全に正確に文字列表現に変換することは難しいが、 桁数を上げることで誤差を少なくすることは可能である。例えば Sexplib では 20桁まで出力を行なっている:

(* conv.ml of Sexplib *)
let default_string_of_float = ref (fun n -> sprintf "%.20G" n)

正確に float を外部に出力・記録したい場合は、その 64bit 表現をそのまま とり出さなければならない。 Pervasives.output_value や C言語による 補助関数の実装などが必要である(Endianness に注意すること)。

nativeint

int は OS がサポートする基本整数型の幅から 1bit 少ない範囲の整数しか 取ることが出きなかった。OS のシステムコールなどはフルに整数幅を使うことが多いため、 OCaml でシステムプログラミングを行う際には int では不便なことが多い。 (関連 OS 機能が整数の範囲としてフルサイズを使わないと確信できるときは int を 使ってももちろん構わない。)

nativeint は OS の基本整数型と同じ幅を持つ符号付き整数型であり、 int とは違い「失われた1bit」は無い。そのためシステムプログラミングに向いた整数型 といえる。一方、 int が 1bit を犠牲とすることで unbox化された表現を持ち 高速な演算が可能であるのに対し、 nativeint はフルサイズで box化されたデータ表現となり、 GC の対象となるため、 nativeint の計算は int と比べるとメモリ領域を多く使用するし、遅くなってしまう。

CR jfuruse: どれぐらいおそいか

定数の書式

nativeint の定数は整数の後に n をつける。すなはち:

# 1n;;
- : nativeint = 1n
# 0x1234n;;
- : nativeint = 4660n
(* 0oxxxn (8進), 0bxxxn (2進) も可能 *)

Printf 系フォーマット文字列では %nd %nx などやはり n を使う:

# Printf.sprintf "%nd" 42n;; - : string = "42" # Printf.sprintf "%06nx" 123n;; - : string = "00007b"

基本的な演算子

nativeint の関数は Nativeint モジュールに定義されているが、 四則演算が add, sub, mul, div と二項演算子ではなく 普通の関数となっているなど、このままでは、不便である。

後述の int 以外の演算子でも普通の四則演算子を使う も参照のこと。

int32int64

int32int64nativeint と同じく「失われた1bit」の 無い、 box化された符号付き整数型だが、幅はアーキテクチャに関係なく int32 は 32bit、 int64 は 64bit固定である。

定数の書式

int32 は整数の後に l, int64 は後に oL と書く。

# 1l;; - : int32 = 1l # 0x1234L;; - : int64 = 4660L (* 0oxxxl (8進), 0bxxxL (2進) も可能 *)

Printf 系フォーマット文字列では %ld %Lx などやはり lL を使う:

# Printf.sprintf "%ld" 42l;; - : string = "42" # Printf.sprintf "%06Lx" 123L;; - : string = "00007b"

基本的な演算子

int32int64 のための関数はそれぞれ Int32 と Int64 モジュールに 定義されている。四則演算が add, sub, mul, div と二項演算子ではなく 普通の関数となっているなど、このままでは、不便である。

後述の int 以外の演算子でも普通の四則演算子を使う も参照のこと。

natbig_int

Complex.t

OCaml の標準ライブラリには何故か複素数のためのモジュール Complex があり、 Complex.t として複素数の型が定義されている。実装は非常に素直:

type t = { re: float; im: float }

実数(re)と虚数(im)部分を float で表した二つ組。 この t に対して幾つかの基本的な 演算が用意されている。が、文字列への変換も文字列からの変換も存在しない。 何のためにあるのかよくわからないモジュールである。 OCaml の標準ライブラリには、中の人が自分が便利だからと 勢いだけで足してしまったこういうモジュールがいくつか有る。 中の人の特権…とでも言おうか。

int 以外の演算子でも普通の四則演算子を使う

符号なし整数: ocaml-uint