这是一个老问题,但许多 anwsers 表现不佳或因大数字而溢出。我认为 D. Nesterov 的答案是最好的:健壮、简单和快速。我只想加两分钱。
我玩过decimals,还查看了source code。来自public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)constructor documentation。
十进制数的二进制表示由一个 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) method 返回的组件。这个想法是根据需要将组件除以 10 倍并减小规模。该代码(大量)基于Decimal.InternalRoundFromZero(ref Decimal d, int decimalCount) method。
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 的快得多(我不会给出数字,因为正如我所提到的,我的测试并不严格)。由谁来实现它来评估性能提升是否会因增加的代码复杂性而得到回报。