Help us understand the problem. What is going on with this article?

[C#] decimal型の数値はメモリ上での並び方がちょっと独特です

More than 1 year has passed since last update.

こんにちはー!ニアです。

先ほど書いた「浮動小数点型の数値はメモリ上でどのように格納されているのか」では、浮動小数点型(float型など)の数値がメモリ上でどのように格納されているかを調べていきました。

では、.NET Frameworkのdecimal型の場合はどうでしょう・・・

decimal型の内部表現

1. メモリ上での並び順を調べる

C#で数値のメモリ上での並び順を調べる時は、BitConverterクラスのGetBytesメソッドを使用します。取得したbyte型配列の要素の並び方は、メモリ上での並び方と同じです。

byte-order.cs
using System;
using System.Linq;

class Program {
    static void Main( string[] args ) {

        Console.WriteLine( "バイトオーダー : {0}方式\n", BitConverter.IsLittleEndian ? "リトルエンディアン" : "ビッグエンディアン" );

        int i = 0x01020304;
        byte[] ib = BitConverter.GetBytes( i );
        Console.WriteLine( "int型" );
        Console.WriteLine( "元の値 : {0:X8}", i );
        Console.WriteLine( "メモリ上の値 : {0}\n", string.Join( " ", ib.Select( _ => _.ToString( "X2" ) ) ) );

        float f = 1.024f;
        byte[] fb = BitConverter.GetBytes( f );
        Console.WriteLine( "float型" );
        Console.WriteLine( "元の値 : {0}", f );
        Console.WriteLine( "内部表現の値 : {0:X8}", BitConverter.ToInt32( fb, 0 ) );
        Console.WriteLine( "メモリ上の値 : {0}", string.Join( " ", fb.Select( _ => _.ToString( "X2" ) ) ) );
    }
}

◆ 実行結果

バイトオーダー : リトルエンディアン方式

int型
元の値 : 01020304
メモリ上の値 : 04 03 02 01

float型
元の値 : 1.024
内部表現の値 : 3F83126F
メモリ上の値 : 6F 12 83 3F

decimalの場合

しかし、BitConverterクラスのGetBytesメソッドには、decimal型用のオーバーロードがありません。代わりに、ポインタを使って直接変数にアクセスしていきます。

内部表現はdecimalのGetBitsメソッドを使って取得します。

byte-order-decimal.cs
using System;
using System.Linq;

class Program {
    static unsafe void Main( string[] args ) {

        decimal d = -0.4972397250606885993375668763m;
        Console.WriteLine( "元の値 : {0}", d );
        Console.WriteLine( "内部表現の値 : {0}", string.Join( " ", decimal.GetBits( d ).Reverse().Select( _ => _.ToString( "X8" ) ) ) );

        // byte型ポインタにdecimal型変数のアドレスを代入します。
        byte* pdb = ( byte* )&d;
        // byte型ポインタからiバイト分だけオフセットして、byte型の値を取得します。
        byte[] db = new byte[16];
        for( int i = 0; i < 16; i++ ) db[i] = *( pdb + i );
        Console.WriteLine( "メモリ上での並び順 : {0}", string.Join( " ", db.Select( _ => _.ToString( "X2" ) ) ) );

    }
}

※アンセーフコードを扱うので、コンパイル時に「/unsafe」オプションを付けます。

◆ 実行結果
※使用したコンピューターのCPUのバイトオーダーはリトルエンディアン方式です。

元の値 : -0.4972397250606885993375668763
内部表現の値 : 801C0000 10111213 14151617 18191A1B
メモリ上での並び順 : 00 00 1C 80 13 12 11 10 1B 1A 19 18 17 16 15 14

2. decimal型の数値における、メモリ上での並び方

実行結果にあるdecimal型の数値の内部表現とメモリ上での並び順を見比べると、内部表現の値を4バイトずつ分割し、各要素の中はバイト単位で逆順に並んでいることが分かります。

しかし、4バイトずつ分割したものについて、内部表現での並び順を上位から3→2→1→0(※GetBitsメソッドで取得した時の要素のインデックスに合わせています)とすると、メモリ上では3→2→0→1となっています。

littleE-decimal.png

decimal型の謎は深い・・・

それでは、See you next!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away