轉載文章請標明原文地址:https://segmentfault.com/a/1190000046149521
一、表示規則
1.內存排布
C#中的decimal類型佔16字節,內存排布如下:
flags(32位 符號1位+縮放因子8位)|high(32位)|low + mid(64位)
S0000000 CCCCCCCC 00000000 00000000 | HHHHHHHH HHHHHHHH HHHHHHHH HHHHHHHH | LLLLLLLL LLLLLLLL LLLLLLLL LLLLLLLL | MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM
2.關鍵源碼
Decimal源碼
private const int SignMask = unchecked((int)0x80000000);
// NOTE: Do not change the order and types of these fields. The layout has to
// match Win32 DECIMAL type.
private readonly int _flags;
private readonly uint _hi32;
private readonly ulong _lo64;
public Decimal(int lo, int mid, int hi, bool isNegative, byte scale)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(scale, 28);
_lo64 = (uint)lo + ((ulong)(uint)mid << 32);
_hi32 = (uint)hi;
_flags = ((int)scale) << 16;
if (isNegative)
_flags |= SignMask;
}
二、代碼實現
1.decimal轉二進制
private static byte[] WriteDecimal(decimal value) {
/*
// 推薦用decimal.GetBits(value) 方法獲取decimal的四個部分 這裏為了性能更好 就用了指針直接讀內存
int[] parts = decimal.GetBits(value);
int low = parts[0];
int mid = parts[1];
int high = parts[2];
int flags = parts[3];
*/
uint flags;
uint high;
uint low;
uint mid;
unsafe {
// Decimal源碼:int _flags; uint _hi32; ulong _lo64; _lo64 = (uint)lo + ((ulong)(uint)mid << 32);
uint* ptr = (uint*)&value;
flags = ptr[0]; // int _flags
high = ptr[1]; // uint _hi32
low = ptr[2]; // ulong _lo64 低32位
mid = ptr[3]; // ulong _lo64 高32位
}
bool isNegative = (flags & 0x80000000) != 0; // 取flags的最高位(0為非負值 1為負值)
byte scale = (byte)((flags >> 16) & 0x1F); // 取flags第17位開始的低8位(最大數值28)
byte[] bytes = new byte[14];
bytes[0] = isNegative ? (byte)1 : (byte)0;
bytes[1] = scale;
bytes[2] = (byte)low;
bytes[3] = (byte)(low >> 8);
bytes[4] = (byte)(low >> 16);
bytes[5] = (byte)(low >> 24);
bytes[6] = (byte)mid;
bytes[7] = (byte)(mid >> 8);
bytes[8] = (byte)(mid >> 16);
bytes[9] = (byte)(mid >> 24);
bytes[10] = (byte)high;
bytes[11] = (byte)(high >> 8);
bytes[12] = (byte)(high >> 16);
bytes[13] = (byte)(high >> 24);
return bytes;
}
2.二進制轉decimal
private static decimal ReadDecimal(byte[] bytes) {
bool isNegative = bytes[0] != 0;
byte scale = bytes[1];
int low = bytes[2] | bytes[3] << 8 | bytes[4] << 16 | bytes[5] << 24;
int mid = bytes[6] | bytes[7] << 8 | bytes[8] << 16 | bytes[9] << 24;
int high = bytes[10] | bytes[11] << 8 | bytes[12] << 16 | bytes[13] << 24;
return new decimal(low, mid, high, isNegative, scale);
}
三、總結
1.精度丟失問題
decimal不會產生精度丟失問題。因為decimal是基於十進制的科學計數法表示的、與float和double的基於二進制科學計數法表示的規則不同,所以不會產生精度丟失問題,在對小數計算敏感的場景裏,建議用decimal代替float和double。
2.內存排布問題
decimal內存排布由於實現方式的原因,並不是flags、high、mid、low這種排布方法,用指針獲取值時需要注意。
3.序列化和網絡傳輸問題
由於不同編程語言中,類似decimal類型的定義標準和實現原理差異巨大,所以decimal的序列化和網絡傳輸建議轉為字符串,字符串可以在不同語言之間轉為decimal類型。