Bu eski bir sorudur, ancak çoğu cevap iyi performans göstermez veya büyük sayılar için taşmaz. Bence D. Nesterov cevabı en iyisi: sağlam, basit ve hızlı. Sadece iki sentimi eklemek istiyorum. Ondalık sayılarla oynadım ve kaynak kodunu da kontrol ettim . Gönderen public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)
yapıcı belgelerinde .
Bir Ondalık sayının ikili gösterimi 1 bitlik bir işaret, 96 bitlik bir tam sayı ve tamsayı sayısını bölmek ve bunun hangi kısmının ondalık kesir olduğunu belirtmek için kullanılan bir ölçekleme faktöründen oluşur. Ölçekleme faktörü örtük olarak 0 ile 28 arasında bir üsse yükseltilen 10 sayısıdır.
Bunu bilerek, ilk yaklaşımım decimal
ölçeği atmak istediğim ondalık sayıya karşılık gelen başka bir tane yaratmak , sonra onu kesmek ve sonunda istenen ölçekte bir ondalık oluşturmaktı.
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);
}
Bu yöntem D. Nesterov'unkinden daha hızlı değil ve daha karmaşık, bu yüzden biraz daha fazla oynadım. Tahminim, bir yardımcı oluşturmak decimal
ve bitleri iki kez almak zorunda kalmanın onu daha yavaş hale getirdiği. İkinci denememde Decimal.GetBits (Decimal d) yöntemiyle döndürülen bileşenleri kendim değiştirdim. Buradaki fikir, bileşenleri gerektiği kadar 10'a bölmek ve ölçeği küçültmektir. Kod, (büyük ölçüde) Decimal.InternalRoundFromZero (ref Decimal d, int decimalCount) yöntemine dayanır .
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;
}
Sıkı performans testleri yapmadım, ancak bir MacOS Sierra 10.12.6, 3.06 GHz Intel Core i3 işlemcide ve .NetCore 2.1'i hedefleyen bu yöntem, D.Nesterov'unkinden çok daha hızlı görünüyor. , bahsettiğim gibi, testlerim titiz değil). Performans kazanımlarının eklenen kod karmaşıklığı için işe yarayıp yaramadığını değerlendirmek, bunu uygulayan kişiye bağlıdır.