-
Notifications
You must be signed in to change notification settings - Fork 0
MS_NumericCalculation
- 戻る(.NET開発)
特に小数点を持つ数値の計算を行う場合は、以下の点に考慮する。
- 扱える数値の最大値を超える
- オーバーフロー時:System.OverflowException 例外、
- 指数部が小さくなり過ぎる
- アンダーフロー時:System.UnderflowException 例外が発生する。
移行メモ(正誤): .NET に
System.UnderflowExceptionは存在しない。
System.OverflowExceptionはあるが、アンダーフローに対応する例外型はない。実際の挙動は型によって異なる。
型 アンダーフロー時の挙動 float/double例外は出ない。非正規化数を経て、静かに 0になるdecimal例外は出ない。有効桁を超えた分は丸められて 0になる整数型 そもそもアンダーフローの概念がない(最小値を下回れば OverflowException または折り返し) double d = 1e-320; // 非正規化数(精度は落ちるが値は残る) Console.WriteLine(d / 1e10); // 0 ← 例外は出ない decimal m = 0.0000000000000000000000000001m; // 1e-28 Console.WriteLine(m / 10m); // 0 ← 例外は出ない「静かに 0 になる」ことが、オーバーフローより厄介である。
例外で気付けないため、除算の分母にすると 0 除算になる、
累積すると結果が丸ごと消える、といった形で後から表面化する。
.NET はコンパイラ オプションでオーバーフロー、アンダーフローのチェックが可能である。
-
Visual Basicコンパイラ オプション一覧 > /optionstrict
https://learn.microsoft.com/ja-jp/dotnet/visual-basic/reference/command-line-compiler/optionstrict- C#コンパイラ オプション一覧 > /checked
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/compiler-options/language#checkforoverflowunderflow - Visual Basicコンパイラ オプション一覧 > /removeintchecks
https://learn.microsoft.com/ja-jp/dotnet/visual-basic/reference/command-line-compiler/removeintchecks
- C#コンパイラ オプション一覧 > /checked
移行メモ(チェックの対象は整数だけ): コンパイラ オプションの
/checked(C#)や/removeintchecks(VB)が効くのは、
整数型の演算だけである。【チェックされる】 int, long, short, byte, uint, ulong … の +, -, *, ++, --, 明示的な数値変換 【チェックされない】float, double ← 仕様上、例外を出さない decimal ← 常にチェックされる(オプション不問)つまり、原文が言う「アンダーフローのチェック」は
コンパイラ オプションでは実現できない。
浮動小数点のアンダーフローは、自分で値域を検査するしかない。現在の書き方(
.csproj):<PropertyGroup> <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> </PropertyGroup>既定は
false(チェックしない = 折り返す)。
金額や数量を扱うならtrueにしておく方が安全である
(性能への影響はごく小さい)。
コンパイラ オプションで設定したオーバーフロー、アンダーフローのチェックの既定の動作を変更したい場合は、
-
C# では、checked、unchecked、というキーワードを使用する。
- ステートメントのキーワード (C#リファレンス) > checked
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/statements/checked-and-unchecked - ステートメントのキーワード (C#リファレンス) > unchecked
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/statements/checked-and-unchecked
- ステートメントのキーワード (C#リファレンス) > checked
-
VB ではこのステートメントに合致するものが存在しない
- VB は暗黙の型変換を許容している。
- Option Strict をオンにすることで暗黙の型変換(による桁溢れ)を抑止できる。
- ステートメント (Visual Basic) > Option Strictステートメント
https://learn.microsoft.com/ja-jp/dotnet/visual-basic/language-reference/statements/option-strict-statement
- ステートメント (Visual Basic) > Option Strictステートメント
補足(
checked/uncheckedの使い分け): 既定(unchecked)では
静かに折り返すため、バグが見えなくなる。int max = int.MaxValue; unchecked { Console.WriteLine(max + 1); } // -2147483648 ← 折り返す checked { Console.WriteLine(max + 1); } // OverflowException
場面 選択 業務計算(金額・数量・件数) checked。誤った値が流れる方が危険ハッシュ計算、CRC、ビット演算 unchecked。折り返しが仕様の一部性能が極限まで要る内側のループ unchecked(ただし測ってから)
Option Strict Onは VB では必須と考えてよい。
暗黙の型変換を止めることで、桁溢れ以前に
意図しない型変換そのものを防げる。
- 切り捨て
- 四捨五入
- 切り上げ
補足(.NET の既定は「銀行丸め」):
Math.Roundの既定は
四捨五入ではない。ここは実務でよく事故る。Math.Round(0.5) // 0 ← 1 ではなく 0(偶数側へ丸める) Math.Round(1.5) // 2 Math.Round(2.5) // 2 ← 3 ではない // 四捨五入したいなら明示する Math.Round(0.5, MidpointRounding.AwayFromZero) // 1 Math.Round(2.5, MidpointRounding.AwayFromZero) // 3既定の
MidpointRounding.ToEven(銀行丸め・偶数丸め)は、
大量の丸めを行った際に誤差が一方向に偏らない利点がある。
一方、日本の商習慣や税計算は四捨五入(あるいは切り捨て)を前提
にしていることが多い。
メソッド 動作 Math.Round(x)銀行丸め(既定) Math.Round(x, MidpointRounding.AwayFromZero)四捨五入 Math.Floor(x)/Math.Truncate(x)切り捨て(負数で挙動が違う) Math.Ceiling(x)切り上げ
Floorは「小さい方へ」、Truncateは「0 の方へ」。
Math.Floor(-1.5)は-2、Math.Truncate(-1.5)は-1である。仕様書に丸め方を明記させ、コードでも明示する——
これが唯一の対策である。
0.3333....(無限級数)を有限項で打ち切った場合。
ほぼ等しい 2 つの数値の差を求める場合、
計算結果が 0 に極端に近くなり有効桁数が減る。
- 有効数字 - Wikipedia
https://ja.wikipedia.org/wiki/%E6%9C%89%E5%8A%B9%E6%95%B0%E5%AD%97 - 桁落ちとは - IT用語辞典 e-Words
https://e-words.jp/w/%E6%A1%81%E8%90%BD%E3%81%A1.html
非常に大きな数値と小さな数値の加減算で、
小さな数値が結果に反映されない。
補足(誤差の実例): 挙げられた 4 つは、どれも実際に見える。
// ① 丸め誤差(2 進数で 0.1 を表現できない) Console.WriteLine(0.1 + 0.2 == 0.3); // False Console.WriteLine((0.1 + 0.2).ToString("R")); // 0.30000000000000004 // ② 桁落ち double a = 1.0000000001, b = 1.0000000000; Console.WriteLine((a - b).ToString("R")); // 1.000000082740371E-10(有効桁が激減) // ③ 情報落ち double big = 1e16, small = 1.0; Console.WriteLine(big + small == big); // True ← small が消えた // ④ 加算順序で結果が変わる Console.WriteLine(1e16 + 1 + 1 - 1e16); // 0 Console.WriteLine(1 + 1 + 1e16 - 1e16); // 2④ は特に重要で、浮動小数点の加算には結合則が成り立たない。
大量のデータを合計する際は、小さい値から足す、あるいは
Kahan の補正加算を使うと誤差を抑えられる。
==で浮動小数点を比較しないのが鉄則である。// ✗ if (x == y) { } // ○ 許容誤差を決めて比較する if (Math.Abs(x - y) < 1e-9) { }
特に小数点を持つ金額の計算には、Decimal を用いる。
Decimal の特徴を以下に示す。
- DOBON.NET > プログラミング道 > .NET Tips > 基本
小数(浮動小数点数型)の計算が思った結果にならない理由と解決法
https://dobon.net/vb/dotnet/beginner/floatingpointerror.html
-
ほとんどの 10 進数の小数は 2 進数(Single、Double 型などの浮動小数点型)で表現することができない。
- このような値は 2 進数では近似値でしか表現できず、その誤差が計算結果として現れる(打ち切り誤差)。
- ただし、k/(2^n)(k と n は整数)で表すこともできる小数は 2 進数でも表現できる。
-
Decimal は浮動小数点型と比べ、金額計算の慣例に適合する。
- 10 進数を表現する。
- 有効桁数が多く、範囲が狭い(28 ~ 29)。
- 格納できる値の大きさの範囲が狭いは、金額の計算には十分。
-
問題点は、計算速度が遅いこと。
補足(型の選択): 原文の主張——
金額計算はdecimal——は現在も完全に正しい。数値で裏付けておく。
floatdoubledecimal内部表現 2 進浮動小数点 2 進浮動小数点 10 進浮動小数点 サイズ 4 バイト 8 バイト 16 バイト 有効桁数 約 7 桁 約 15~17 桁 28~29 桁 範囲 ±1.5e−45 ~ ±3.4e38 ±5.0e−324 ~ ±1.7e308 ±1.0e−28 ~ ±7.9e28 0.1の表現不可(近似) 不可(近似) 可(正確) 速度 速い(CPU が直接扱う) 速い 遅い(10~20 倍程度) 使い分け:
用途 型 金額、税、数量、率(10 進で定義される値) decimal科学技術計算、統計、座標、物理量 double大量データ・機械学習・グラフィックス float(精度より量)通貨を整数で持つ(最小単位で保持) long(最速かつ正確。円なら丸め不要)
decimalも万能ではない点に注意する。decimal d = 1m / 3m; // 0.3333333333333333333333333333(打ち切り誤差は残る) Console.WriteLine(d * 3m); // 0.9999999999999999999999999999 ← 1 にならない除算がある限り誤差はゼロにならない。
「どこで丸めるか」を業務ルールとして決めることが本質である。DB との対応(SQL Server):
.NET SQL Server decimaldecimal(p,s)/numeric(p,s)/moneydoublefloatfloatreal**
decimalの精度は SQL Server(38 桁)より狭い(28~29 桁)**ため、
decimal(38,10)のような列を読むとOverflowExceptionになり得る。
- Microsoft Learn > C#リファレンス > C#のキーワード > 型
- decimal
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types - float
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types - double
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types
- decimal
補足(現在追加された型): 原文の時代にはなかった型・機能がある。
型 / 機能 内容 System.Half2 バイトの浮動小数点(.NET 5~)。機械学習向け System.Int128/UInt128128 ビット整数(.NET 7~) System.Numerics.BigInteger桁数無制限の整数。正確だが遅い System.Numerics.Complex複素数 ジェネリック数値演算 INumber<T>等(.NET 7~)。型を跨いだ数値アルゴリズムが書ける// .NET 7 以降:型に依らない合計 static T Sum<T>(IEnumerable<T> values) where T : INumber<T> => values.Aggregate(T.Zero, (a, b) => a + b);
- 浮動小数点数値型(C# リファレンス)
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types - 整数数値型(C# リファレンス)
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/integral-numeric-types - System.Math.Round メソッド
https://learn.microsoft.com/ja-jp/dotnet/api/system.math.round - .NET での数値の書式指定
https://learn.microsoft.com/ja-jp/dotnet/standard/base-types/standard-numeric-format-strings
Tags: 移行, プログラミング, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。