マイナス計算が値を変化させないとき §
文字列-1234567890を整数に変換することは可能です。int.TryParseメソッドなどを使います。正しい数値になります。これの符号を反転すると1234567890になります。期待通りです。
しかし、-2147483648の場合は違います。期待通りの結果にならず値が変化しません。マイナスは付いたママです。
なぜでしょうか。
実は起きているオーバーフロー §
C#などで使用されている2の補数表現の負数の有効範囲は、実はマイナス側で一つ多いという特徴があります。つまり、-2147483648は扱えても+2147483648は扱えないのです。
ですから、実はマイナス計算を行った時点でオーバーフローが起きています。
オーバーフローが起きると正しい結果になりません。
対策はなんだ? §
対策は2種類あります。
一つはint型ではなくlong型を使って桁数を増やす方法。
もう1つはcheckedコンテキストで実行させ、オーバーフローの存在を検出して実行を止め
る方法です。
サンプルソースでは後者の方法でオーバーフローを検出する例が最後にあります。
罠の数々 §
- 一般的にその型の表現できる境界上の値は避けた方が安全である。オーバーフロー、アンダーフローが起こりやすいし、判定を誤る可能性もある
- そうは言っても符号なしの数値型で0は使いたい。符号なしの数値型では0から1を引いても-1にならないので注意して扱おう
- checkedコンテキスト/uncheckedコンテキストは知っておく価値がある。意図しない計算結果が出るときは、checkedコンテキストで実行するとどこで間違ったか例外が教えてくれることがある
- メモリを節約するために、よりビット幅の小さい型を使いたいのはよくあることだが、それをやると意図しないオーバーフローが発生しがちである
参考リンク §
整数数値型 (C# リファレンス)
Int32.TryParse メソッド
Int32.MaxValue フィールド
Int32.MinValue フィールド
checked (C# リファレンス)
unchecked (C# リファレンス)
int型でこれ以上先に行ったら正常に処理できない限界値はInt32.MaxValue フィールドとInt32.MinValue フィールドに明示されている。かなり大きな値なのでそう簡単にはそこまでいかないと思うかも知れないが、計算中に値が膨れあがって限界を超えることもある。どの値まで有効なのか確認しておこう。また自動チェックさせたいときはchecked文を使う。合わせてチェックしておこう。しかし、わざとオーバーフローさせてオーバーフロー分を無視するテクニックもある。一時的にチェック機能を停止させるにはuncheckedだ。これも確認しておこう。
リポジトリ §
https://github.com/autumn009/cshowto
Negative §
using System;
class Program
{
private static void parseAndNegative(string s)
{
int.TryParse(s, out int v);
Console.Write(v);
Console.Write(">>>>");
v = -v;
Console.WriteLine(v);
}
private static void parseAndNegativeChecked(string s)
{
try
{
int.TryParse(s, out int v);
Console.Write(v);
Console.Write(">>>>");
checked
{
v = -v;
}
Console.WriteLine(v);
}
catch (OverflowException)
{
Console.WriteLine("Overflow");
}
}
static void Main()
{
parseAndNegative("-1234567890");
parseAndNegative("-2147483648");
parseAndNegativeChecked("-2147483648");
}
}
実行結果
-1234567890>>>>1234567890
-2147483648>>>>-2147483648
-2147483648>>>>Overflow