本文へ移動
Hikari 仕様

std:decimal — 10 進小数 (Decimal)

標準ライブラリモジュール (index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../language-spec.md を参照。

import d := "std:decimal" は 10 進小数の不透明値型 Decimal の生成関数と丸めモードタグを slot に持つ namespace object を d に束縛する。Float (../language-spec.md §7) が IEEE 754 の 2 進浮動小数で 0.1 + 0.20.3 の丸め誤差を持つのに対し、Decimal は 10 進で正確な小数(金額・会計・割合など)を扱う。

std:fs / std:term と違い I/O を伴わず、純粋・同期 (Future を返さない)。std:math と同じく、与えられた値だけを見て結果を直接返す。

import d := "std:decimal"

price := d.parse("19.99").unwrap!
qty   := d.of(3)
sub   := price.mul(qty)                       # 59.97 (正確)
tax   := sub.mul(d.parse("0.10").unwrap!)     # 5.997 (正確)
total := sub.add(tax)                          # 65.967 (正確)

total.format(2, d.HalfUp)                       #> "65.97"   固定 2 桁で表示
d.parse("0.1").unwrap!.add(d.parse("0.2").unwrap!) == d.parse("0.3").unwrap!   #> true

以下では import d := "std:decimal" で束縛したものとして d.of 等で記す。

名前 意味
of d.of(n) Int n から Decimal(正確・全域)
parse d.parse(s) String s を解析し Option(Decimal)(不能は None
HalfEven / HalfUp / Floor / Ceiling / TowardZero d.HalfEven 丸めモードの Variant タグ(§4

本モジュールは効果を1 つも持たない../language-spec.md §17.9)。

1. 不透明型 Decimal

Decimal は内部表現を露出しない不透明値型で、10 進の正確な有理数のうち有限小数(係数 × 10^指数)を表す。生成は §3 のコンストラクター経由のみ。

正準形: 同じ値は 1 つの表現に固定される。末尾ゼロは落とされ、1.01.001同一の値になる(parse("1.0") == parse("1.00")・コレクションで同一キー)。10 進の負のゼロ (-0) は 0 へ正準化する。これにより == / compare / std:mapstd:set のキー同一性が表現に依存しない。小数の「桁数(scale)」は値に保持されず、表示桁は §5format / round で境界指定する。

指数の範囲: 係数の桁数に上限は無いが、指数は絶対値 10^15 までである(1e1000000000000000 は表せ、1e1000000000000001 は表せない)。範囲を超える綴りの解析は None§3)、演算の結果が範囲を超えるときは Error§6)になる。金額・会計・割合の桁はこの範囲に遠く収まる。

Decimal値等価 (==) と順序 (< <= > >=../language-spec.md §9.4) を持つ。順序は compare メソッド(§5)の全順序で、等価は正準形の一致による。正準形ゆえ「値の一致」「compare == Equal」「内部表現の一致」の三者が常に一致し、compare を持つ object の ==compare から導出する規則(../language-spec.md §9.1)と構造的に整合する。+ / - / * / / 演算子はオーバーロードしない — 算術は add / sub / mul / div メソッド(§5)で行う。単項 - も同じでDecimalneg メソッドを持っていても -dec は書けない(単項 - が引くのは受け手が宣言したスロットだけで、ランタイム層が実装する組込メソッドは引かない。../language-spec.md §8.6)。符号反転は dec.neg! と書く。

異型比較は panic(../language-spec.md §9.4)。DecimalInt / Float と別型であり、dec.compare(1) / dec < 2.0 は panic する(暗黙変換しない。§6)。

Decimalstd:decimal型メンバーとして export され、型注釈に書ける(../language-spec.md §13 のモジュール型 export と対称)。import d := "std:decimal" のもとで x: d.Decimal と修飾参照するか、import { Decimal } := "std:decimal" で名前を取り出して x: Decimal と書く。不透明性は保たれ、注釈に書けても内部表現は露出しない(生成は §3 経由のみ・パターン分解は不可)。

Inspect(補間・診断表示)と to_string§5)は正準形の 10 進文字列を返す(例: 1.5 / 1500 / 0.001 / -2.5)。末尾ゼロは付かず、整数値には小数点を付けない。

DecimalCopyable である(../language-spec.md の複製の節)。不透明型だがホストの可変状態を持たない不変な値なので、複製は Int を複製するのと同じ意味を持つ。

2. 取得方法

import d := "std:decimal"

返るのは閉じた namespace object。slot は §3 の生成関数 2 個と §4 の丸めモードタグ 5 個。型メソッド(§5)は Decimal の値に対して登録され、dec.add(other) のように呼ぶ。

3. 生成

名前 戻り 意味
of d.of(n) Decimal Int n を正確に Decimal 化(全域)
parse d.parse(s) Option(Decimal) String s を 10 進表記として解析
  • of(n) は任意精度 Int(../language-spec.md §7.1)を正確に受ける。非 Int を渡すと呼び出し位置で Error(panic 型、§6)。
  • parse(s) は符号(任意 +/-)・整数部・小数部(任意)・指数部(任意 e/E に続く整数)から成る 10 進表記を解析する(例: "19.99" / "-0.001" / "1.5e3" / "42")。整数部と小数部は片方を省いてよいが、数字が 1 つも無い綴りは受けない(".5""1." は可・".""e3"None)。解析できない文字列・空文字列は None../language-spec.md の「不在は Option」)。指数が §1 の範囲を超える綴りも None — 指数部そのものが大きい "1e1000000000000001" も、小数部と指数部を合わせた結果が範囲外になる "0.00001e-9223372036854775807" も同じである。非 String は Error§6)。
  • 小数の生成は parse によるd.parse("0.1"))。Float からの生成口は設けない — Float の 2 進誤差を持ち込まないため、正確な小数はリテラル文字列から作る。
d.of(1500)                 #> 1500
d.parse("0.1")             #> Some(0.1)
d.parse("1.50") == d.parse("1.5")   #> Some 同士が等しい (正準形)
d.parse("abc")             #> None

4. 丸めモード (Variant タグ)

丸めモードは 5 個の Variant 値として namespace に export される(d.HalfEven 等)。div / round / format / to_int§5)の引数に渡す。隠れた既定は持たない — 丸めが起きうる操作では常にモードを明示する。

タグ 丸め方
HalfEven 最近接。半数は偶数側へ(banker's)。統計・既定的な選択
HalfUp 最近接。半数は 0 から遠い側へ(四捨五入)
Floor −∞方向(負数では絶対値が増える)
Ceiling +∞方向
TowardZero 0 方向(小数部を捨てる・切り捨て)

日本の税端数(切り捨て = TowardZero/四捨五入 = HalfUp/切り上げ = Ceiling)を含む実務要件を満たす。

half := d.parse("2.5").unwrap!
half.to_int(d.HalfEven)    #> 2     (偶数側)
half.to_int(d.HalfUp)      #> 3     (四捨五入)

5. メソッド

無引数メソッドは postfix ! で呼ぶ(v!v()../language-spec.md)。引数の型不一致は呼び出し位置で Error(panic 型、§6)。以下 places は非負の Int(小数点以下の桁数。§1 の指数と同じく 10^15 まで)、mode§4 のタグ、otherDecimal

メソッド 戻り 意味
add dec.add(other) Decimal 和(正確)
sub dec.sub(other) Decimal 差(正確)
mul dec.mul(other) Decimal 積(正確)
div dec.div(other, places, mode) Decimal 商を places 桁に mode で丸める
div_exact dec.div_exact(other) Option(Decimal) 有限小数で割り切れれば Some、循環等で不能なら None
neg dec.neg! Decimal 符号反転
abs dec.abs! Decimal 絶対値
sign dec.sign! Int 符号(負 -1・零 0・正 1
round dec.round(places, mode) Decimal places 桁に丸めた値(正準形なので末尾ゼロは残らない)
format dec.format(places, mode) String places 桁ちょうどの固定桁文字列
to_int dec.to_int(mode) Int 整数へ丸める
to_float dec.to_float! Option(Float) 最近接 Float。float64 範囲超は None
to_string dec.to_string! String 正準形の 10 進文字列(Inspect と同じ)
compare dec.compare(other) Ordering 値の全順序
  • add / sub / mul は丸めを要さず常に正確
  • div(other, places, mode) は任意精度 10 進では商が循環しうるため、places 桁への丸めを必須にする。other が 0 の Decimal のときはゼロ除算で Error(Int の除算と一貫、../language-spec.md §7.1)。
  • div_exact(other) は「正確に割り切れなければ失敗」を Option で表す(10 ÷ 4 = 2.5Some(2.5)1 ÷ 3None)。other が 0 のときは Error
  • round は値(正準形の Decimal)を返し、format は表示用の文字列を返す。round(2, mode)1.00 を正準化して 1 にするのに対し、format(2, mode)"1.00" を返す(固定桁の表示はこちらを使う)。
  • to_int(mode)round(0, mode) 相当の整数値を Int で返す。
  • compareDecimal 同士のみ。Int / Float を渡すと異型比較で panic(§6)。
a := d.parse("10").unwrap!
b := d.parse("3").unwrap!

a.div(b, 4, d.HalfEven)    #> 3.3333
a.div_exact(b)             #> None
a.div_exact(d.of(4))       #> Some(2.5)

money := d.parse("1").unwrap!
money.format(2, d.HalfUp)  #> "1.00"
money.round(2, d.HalfUp) == money   #> true   (round は正準化で 1 のまま)

d.parse("-2.5").unwrap!.abs!    #> 2.5
d.parse("-2.5").unwrap!.sign!   #> -1

6. エラーモデル

std:decimal3 種の結果を使い分ける(math.md §6json.md §5 と同じ規律)。

  1. 値を直接返す: of・算術(add/sub/mul)・round/format/to_int/to_string/neg/abs/sign/compare。型が合えば結果を返す。
  2. Option を返す: parse(解析不能で None)・div_exact(割り切れないで None)・to_float(範囲超で None)。いずれも ../language-spec.md の「不在は Option」。
  3. Error(バグ層、../language-spec.md §16: 回復可能エラーではなく呼び出し位置で停止する。
    • 型が合わない呼び出し(of に非 Int・算術/比較メソッドに Decimal 以外・places に非 Int・mode に丸めタグ以外を渡す)
    • div / div_exact の除数が 0 の Decimal(ゼロ除算)
    • places が負、または §1 の範囲を超える
    • 演算(add / sub / mul / div / div_exact / round)の結果の指数が §1 の範囲を超える

compare(および導出される < <= > >=)に DecimalInt / Float を混在させると異型比較で panic する(../language-spec.md §9.4)。== は全域で、Decimal と別型は false../language-spec.md §9.1)。