Ini adalah pertanyaan lama, tetapi banyak pengguna tidak berkinerja baik atau berlebihan untuk jumlah besar. Saya pikir jawaban D. Nesterov adalah yang terbaik: kuat, sederhana dan cepat. Saya hanya ingin menambahkan dua sen saya. Saya bermain-main dengan desimal dan juga memeriksa kode sumbernya . Dari public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)
dokumentasi konstruktor .
Representasi biner dari bilangan desimal terdiri dari tanda 1-bit, bilangan bulat 96-bit, dan faktor skala yang digunakan untuk membagi bilangan bulat dan menentukan bagian mana yang merupakan pecahan desimal. Faktor skala secara implisit adalah angka 10 yang dipangkatkan menjadi eksponen yang berkisar dari 0 hingga 28.
Mengetahui hal ini, pendekatan pertama saya adalah membuat yang lain decimal
yang skalanya sesuai dengan desimal yang ingin saya buang, lalu potong dan akhirnya buat desimal dengan skala yang diinginkan.
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);
}
Metode ini tidak lebih cepat dari metode D. Nesterov dan lebih kompleks, jadi saya bermain-main sedikit lebih banyak. Dugaan saya adalah bahwa harus membuat auxiliar decimal
dan mengambil bit dua kali membuatnya lebih lambat. Pada upaya kedua saya, saya memanipulasi komponen yang dikembalikan oleh metode Decimal.GetBits (Decimal d) sendiri. Idenya adalah membagi komponen dengan 10 kali sebanyak yang dibutuhkan dan mengurangi skala. Kode ini didasarkan (sangat) pada metode 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;
}
Saya belum melakukan tes kinerja yang ketat, tetapi pada MacOS Sierra 10.12.6, prosesor Intel Core i3 3,06 GHz dan menargetkan .NetCore 2.1 metode ini tampaknya jauh lebih cepat daripada D. Nesterov (saya tidak akan memberikan angka sejak , seperti yang telah saya sebutkan, pengujian saya tidak ketat). Terserah siapa pun yang menerapkan ini untuk mengevaluasi apakah kinerja yang diperoleh terbayar untuk kompleksitas kode yang ditambahkan.