这是一个古老的问题,但是许多选民的表现并不出色,或者由于人数众多而溢出。我认为D. Nesterov的答案是最好的答案:健壮,简单且快速。我只想加两分钱。我玩弄小数,还检查了源代码。来自public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)
构造函数文档。
小数的二进制表示形式由1位符号,96位整数和比例因子组成,该比例因子用于对整数进行除法并指定整数的哪一部分为小数。比例因子隐式地是数字10升到0到28范围内的指数。
知道这一点后,我的第一种方法是创建一个decimal
小数位数,该小数位数与我要舍弃的小数位相对应,然后截断它,最后创建具有所需小数位的小数位。
private const int ScaleMask = 0x00FF0000;
public static Decimal Truncate(decimal target, byte decimalPlaces)
{
var bits = Decimal.GetBits(target);
var scale = (byte)((bits[3] & (ScaleMask)) >> 16);
if (scale <= decimalPlaces)
return target;
var temporalDecimal = new Decimal(bits[0], bits[1], bits[2], target < 0, (byte)(scale - decimalPlaces));
temporalDecimal = Math.Truncate(temporalDecimal);
bits = Decimal.GetBits(temporalDecimal);
return new Decimal(bits[0], bits[1], bits[2], target < 0, decimalPlaces);
}
这种方法并不比D. Nesterov的方法快,并且更复杂,所以我玩的更多。我的猜测是,必须创建一个辅助工具decimal
并两次检索这些位,这会使它变慢。第二次尝试时,我自己操作了Decimal.GetBits(Decimal d)方法返回的组件。想法是将组件按需要除以10倍,并缩小规模。该代码(大量)基于Decimal.InternalRoundFromZero(ref Decimal d,int decimalCount)方法。
private const Int32 MaxInt32Scale = 9;
private const int ScaleMask = 0x00FF0000;
private const int SignMask = unchecked((int)0x80000000);
// Fast access for 10^n where n is 0-9
private static UInt32[] Powers10 = new UInt32[] {
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000
};
public static Decimal Truncate(decimal target, byte decimalPlaces)
{
var bits = Decimal.GetBits(target);
int lo = bits[0];
int mid = bits[1];
int hi = bits[2];
int flags = bits[3];
var scale = (byte)((flags & (ScaleMask)) >> 16);
int scaleDifference = scale - decimalPlaces;
if (scaleDifference <= 0)
return target;
// Divide the value by 10^scaleDifference
UInt32 lastDivisor;
do
{
Int32 diffChunk = (scaleDifference > MaxInt32Scale) ? MaxInt32Scale : scaleDifference;
lastDivisor = Powers10[diffChunk];
InternalDivRemUInt32(ref lo, ref mid, ref hi, lastDivisor);
scaleDifference -= diffChunk;
} while (scaleDifference > 0);
return new Decimal(lo, mid, hi, (flags & SignMask)!=0, decimalPlaces);
}
private static UInt32 InternalDivRemUInt32(ref int lo, ref int mid, ref int hi, UInt32 divisor)
{
UInt32 remainder = 0;
UInt64 n;
if (hi != 0)
{
n = ((UInt32)hi);
hi = (Int32)((UInt32)(n / divisor));
remainder = (UInt32)(n % divisor);
}
if (mid != 0 || remainder != 0)
{
n = ((UInt64)remainder << 32) | (UInt32)mid;
mid = (Int32)((UInt32)(n / divisor));
remainder = (UInt32)(n % divisor);
}
if (lo != 0 || remainder != 0)
{
n = ((UInt64)remainder << 32) | (UInt32)lo;
lo = (Int32)((UInt32)(n / divisor));
remainder = (UInt32)(n % divisor);
}
return remainder;
}
我尚未进行严格的性能测试,但是在MacOS Sierra 10.12.6、3.06 GHz Intel Core i3处理器上,并且针对.NetCore 2.1,此方法似乎比D快得多。Nesterov的方法(我不会给出数字,因为,正如我已经提到的,我的测试并不严格。实施此方法的人员取决于评估是否为增加的代码复杂性而获得了性能提升。