実数でもキャスト
データ変換は、整数だけでなく、実数でも必要とされる。C#には、桁数の多いdouble型と桁数の少ないfloat型という2種類の浮動小数点実数のデータ型がある。桁数の少ないfloat型の値を桁数の多いdouble型に代入するのは問題ないが、これは誰でもすぐ分かるのでサンプルは割愛する。逆に、桁数の多いdouble型の値を桁数の少ないfloat型に代入する場合は、整数と同じくキャストを必要とする。実際にキャストしてみた例を以下に示す。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: double d = 0.123456789012345;
9: float f = (float)d;
10: Console.WriteLine("{0},{1}",d,f);
11: return 0;
12: }
13: }
14: } |
|
double型変数をキャストしてfloat型変数に代入するプログラム |
これを実行すると以下のようになる。
|
プログラムの実行結果 |
double型をfloat型にキャストすると、数値の下の桁が切り捨てられていることが分かる。前出の整数の場合のように、とんでもない値になってしまうわけではない。
|
これを見ると分かるとおり、桁数の少ないデータ型に変換されたときに切り捨てられるのは下の桁である。つまり、値のおおまかな値は変化しない。精度が落ちるだけである。その意味で、整数をキャストする場合に比べれば比較的被害は少ない。ちなみに、上記画像の最後が“91”で終わっているが、変換元の値を見れば、ここは“89”になるべき場所である。このような食い違いが生じるのは、2進数の浮動小数点演算が、10進数の表記を正確に表記できないことによって起こる誤差であり、原理的に不可避である。これを避けるには、2進数ではなく10進数で処理するdecimal型を用いるが、処理速度や効率はfloat型やdouble型の方が勝る。
定数値にキャスト
誤差の出ない計算を行うなら、decimal型が有利である。それでは、と言うことでdecimal型の簡単なプログラムを書いてみよう。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: decimal e = 0.123456789;
9: Console.WriteLine("{0}",e);
10: return 0;
11: }
12: }
13: } |
|
decimal型変数を使用するプログラム |
ところが、このソースコードは、コンパイルを通らない。以下のようなエラーが発生する。
d:\w\test\consoleapplication11\class1.cs(8、16): エラー CS0029: 型 'double' を型 'decimal' に暗黙的に変換できません。 |
これは、何気なく書いた“0.123456789”という値が、実はコンパイラ内部でdouble型の値として認識されていることにより発生したエラーである。つまりコンパイラは、8行目の代入を、double型の値をdecimal型に代入すると認識して、エラー扱いしたのである。もちろん、キャストを用いて“(decimal)0.123456789”と書いてエラーを避けることはできるが、せっかく誤差を避けるためにdouble型を使わないようにしているのに、ここでdouble型が入り込むのは美しくない。そこで、以下のように記述する。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: decimal e = 0.123456789m;
9: Console.WriteLine("{0}",e);
10: return 0;
11: }
12: }
13: } |
|
定数に続けて“m”を記述し、明示的に定数の型を指定したプログラム |
ここで重要なポイントは、“0.123456789”の後ろに“m”を付けて、“0.123456789m”と記述したことである。この“m”は、「その値がdecimal型だよ」と言うことをコンパイラに伝える機能を持った文字である。このほかに、“u”は符号なしであること、“l”はlong型であること、“f”はfloat型、“d”はdouble型、といった機能をもった文字を数値の後ろに付けることができる。無意味なキャストを減らすためにも、これらの文字は有効である。これを実行した結果は以下のようになる。
|
プログラムの実行結果 |
定数に型指定文字を追加したら、コンパイルも問題なく通過し、実行結果も期待どおりのものになった。
|
符号の有無は要注意
キャストを用いる際には、符号ありのデータ型と、符号なしのデータ型の違いに注意を払う必要がある。例えば、同等のビット数で表現されるデータ型の間で変換を行う場合、確かに情報が欠落せずに変換はできる。しかし、それが指し示す値が同じとは限らない。以下に一例を示す。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: short i = -1;
9: ushort u = (ushort)i;
10: Console.WriteLine("{0},{1}",i,u);
11: return 0;
12: }
13: }
14: } |
|
負の数を符号なしのデータ型に代入するプログラム |
これを実行した結果は以下のようになる。
|
プログラムの実行結果 |
ビットとしての情報は失われていないものの、符号付き変数では値の解釈が異なるため、このような結果となる。
|
変数iと変数uは、機械語レベルで格納されるビットの値としてはまったく同一であり、ビット単位で処理する演算子を用いるとまったく同じ結果になる。しかし、数値としての値は同じではないことを、肝に銘じておく必要がある。言い換えれば、符号付きと符号なしのデータ型は違うものだと認識して、混用はなるべく避ける方がよい。
小数の切り捨て
言うまでもなく、整数型には小数を表現する能力はない。キャストによって実数型から整数型に変換させることはできるが、その際、小数点以下の値はすべて失われる。では、小数点以下が消えてなくなるときに、その値は変換結果にどんな影響を及ぼすのだろうか。切り捨てだろうか、切り上げだろうか、四捨五入だろうか?
実際に試してみよう。以下のサンプル・ソースを実行すると分かる。
1: namespace ConsoleApplication11
2: {
3: using System;
4: public class Class1
5: {
6: public static int Main(string[] args)
7: {
8: for( float f=-2; f<2; f += 0.25f )
9: {
10: int i = (int)f;
11: Console.WriteLine("{0},{1}",f,i);
12: }
13: return 0;
14: }
15: }
16: } |
|
浮動小数点値を整数型変数にキャストするプログラム |
これを実行すると以下のようになる。
|
プログラムの実行結果 |
浮動小数点数値を整数型変数にキャストした場合には、同一符号で絶対値が本来の値(浮動小数点値)を超えない最大の整数値に変換される。
|
見て分かるとおり、実数から整数にキャストで変換する場合は、「同一符号で絶対値がその値を超えない最大の整数に変換される」と言うことになる。四捨五入に慣れたVisual BASICプログラマなどは要注意である。
Insider.NET 記事ランキング
本日
月間