Đây là một câu hỏi cũ, nhưng nhiều nhà cảm xạ không thực hiện tốt hoặc tràn cho những con số lớn. Tôi nghĩ câu trả lời của D. Nesterov là câu trả lời hay nhất: mạnh mẽ, đơn giản và nhanh chóng. Tôi chỉ muốn thêm hai xu của mình. Tôi đã thử với các số thập phân và cũng kiểm tra mã nguồn . Từ public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)
tài liệu xây dựng .
Biểu diễn nhị phân của số Thập phân bao gồm dấu 1 bit, số nguyên 96 bit và hệ số tỷ lệ được sử dụng để chia số nguyên và chỉ định phần nào của nó là phân số thập phân. Hệ số tỷ lệ ngầm hiểu là số 10 được nâng lên thành số mũ nằm trong khoảng từ 0 đến 28.
Biết được điều này, cách tiếp cận đầu tiên của tôi là tạo một cái khác decimal
có tỷ lệ tương ứng với các số thập phân mà tôi muốn loại bỏ, sau đó cắt bớt nó và cuối cùng tạo một số thập phân với tỷ lệ mong muốn.
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);
}
Phương pháp này không nhanh hơn D. Nesterov và nó phức tạp hơn, vì vậy tôi đã chơi xung quanh nhiều hơn một chút. Tôi đoán rằng việc phải tạo một auxiliar decimal
và lấy các bit hai lần sẽ làm cho nó chậm hơn. Trong lần thử thứ hai, tôi đã tự mình thao tác các thành phần được trả về bởi phương thức Decimal.GetBits (Decimal d) . Ý tưởng là chia các thành phần cho 10 lần nếu cần và giảm tỷ lệ. Mã dựa trên (nhiều) phương thức 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;
}
Tôi chưa thực hiện các bài kiểm tra hiệu suất nghiêm ngặt, nhưng trên bộ vi xử lý Intel Core i3 MacOS Sierra 10.12.6, 3.06 GHz và nhắm mục tiêu .NetCore 2.1, phương pháp này có vẻ nhanh hơn nhiều so với D. Nesterov (tôi sẽ không đưa ra con số vì , như tôi đã đề cập, các bài kiểm tra của tôi không nghiêm ngặt). Ai thực hiện điều này là tùy thuộc vào việc đánh giá xem liệu hiệu suất có được đền đáp cho sự phức tạp của mã thêm vào hay không.