นี่เป็นคำถามเก่า แต่ anwsers จำนวนมากทำงานได้ไม่ดีหรือมากเกินไปสำหรับตัวเลขจำนวนมาก ฉันคิดว่าคำตอบของ 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 (ทศนิยม d) ด้วยตัวเอง แนวคิดคือการแบ่งส่วนประกอบ 10 เท่าหลาย ๆ ครั้งตามต้องการและลดขนาด รหัสจะขึ้น (หนัก) ในDecimal.InternalRoundFromZero (อ้างอิงสิบวัน decimalCount int) วิธีการ
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 โปรเซสเซอร์ Intel Core i3 3,06 GHz และการกำหนดเป้าหมาย NetCore 2.1 วิธีนี้ดูเหมือนจะเร็วกว่าของ D.Nesterov มาก (ฉันจะไม่ให้ตัวเลขตั้งแต่ ดังที่ฉันได้กล่าวไปแล้วการทดสอบของฉันไม่เข้มงวด) ขึ้นอยู่กับว่าใครก็ตามที่ใช้สิ่งนี้ในการประเมินว่าประสิทธิภาพที่ได้รับนั้นคุ้มค่าหรือไม่สำหรับความซับซ้อนของโค้ดที่เพิ่มเข้ามา