转载文章请标明原文地址: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 _flagshigh = ptr[1]; // uint _hi32low = 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类型。