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.2 ≠ 0.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.0 と 1.00 と 1 は同一の値になる(parse("1.0") == parse("1.00")・コレクションで同一キー)。10 進の負のゼロ (-0) は 0 へ正準化する。これにより == / compare / std:map・std:set のキー同一性が表現に依存しない。小数の「桁数(scale)」は値に保持されず、表示桁は §5 の format / 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)で行う。単項 - も同じで、Decimal が neg メソッドを持っていても -dec は書けない(単項 - が引くのは受け手が宣言したスロットだけで、ランタイム層が実装する組込メソッドは引かない。../language-spec.md §8.6)。符号反転は dec.neg! と書く。
異型比較は panic(../language-spec.md §9.4)。Decimal は Int / Float と別型であり、dec.compare(1) / dec < 2.0 は panic する(暗黙変換しない。§6)。
Decimal は std: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)。末尾ゼロは付かず、整数値には小数点を付けない。
Decimal はCopyable である(../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 のタグ、other は Decimal。
| メソッド | 形 | 戻り | 意味 |
|---|---|---|---|
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.5はSome(2.5)、1 ÷ 3はNone)。otherが 0 のときはError。roundは値(正準形のDecimal)を返し、formatは表示用の文字列を返す。round(2, mode)が1.00を正準化して1にするのに対し、format(2, mode)は"1.00"を返す(固定桁の表示はこちらを使う)。to_int(mode)はround(0, mode)相当の整数値を Int で返す。compareはDecimal同士のみ。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:decimal は 3 種の結果を使い分ける(math.md §6・json.md §5 と同じ規律)。
- 値を直接返す:
of・算術(add/sub/mul)・round/format/to_int/to_string/neg/abs/sign/compare。型が合えば結果を返す。 Optionを返す:parse(解析不能でNone)・div_exact(割り切れないでNone)・to_float(範囲超でNone)。いずれも ../language-spec.md の「不在は Option」。Error(バグ層、../language-spec.md §16): 回復可能エラーではなく呼び出し位置で停止する。
compare(および導出される < <= > >=)に Decimal と Int / Float を混在させると異型比較で panic する(../language-spec.md §9.4)。== は全域で、Decimal と別型は false(../language-spec.md §9.1)。