Це давнє запитання, але багато шанувальників не спрацьовують добре або переповнюються для великої кількості. Я думаю, що відповідь Д. Нестерова найкраща: надійний, простий і швидкий. Я просто хочу додати свої два центи. Я пограв із десятковими знаками, а також перевірив вихідний код . З 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);
}
Цей метод не швидший, ніж у Д. Нестерова, і він складніший, тому я пограв трохи більше. Я здогадуюсь, що те, що створити допоміжний decimal
засіб та двічі отримати біти, це робить це повільніше. Під час моєї другої спроби я сам маніпулював компонентами, поверненими методом Decimal.GetBits (Десяткове d) . Ідея полягає в тому, щоб розділити компоненти на 10 стільки разів, скільки потрібно, і зменшити масштаб. Код заснований (значно) на методі 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;
}
Я не проводив суворих тестів на працездатність, але на MacOS Sierra 10.12.6, процесор Intel Core i3 3,06 ГГц та орієнтацію. як я вже згадував, мої тести не суворі). Той, хто реалізує це, залежить від того, чи окупність продуктивності окупиться за додаткову складність коду.