Esta es una pregunta antigua, pero muchas respuestas no funcionan bien o se desbordan para grandes números. Creo que la respuesta de D. Nesterov es la mejor: robusta, simple y rápida. Solo quiero agregar mis dos centavos. Jugué con los decimales y también revisé el código fuente . De la public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)
documentación del constructor .
La representación binaria de un número decimal consta de un signo de 1 bit, un número entero de 96 bits y un factor de escala que se utiliza para dividir el número entero y especificar qué parte del mismo es una fracción decimal. El factor de escala es implícitamente el número 10 elevado a un exponente que va de 0 a 28.
Sabiendo esto, mi primer enfoque fue crear otro decimal
cuya escala corresponda a los decimales que quería descartar, luego truncarlo y finalmente crear un decimal con la escala deseada.
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);
}
Este método no es más rápido que el de D. Nesterov y es más complejo, así que jugué un poco más. Supongo que tener que crear un auxiliar decimal
y recuperar los bits dos veces lo hace más lento. En mi segundo intento, manipulé los componentes devueltos por el método Decimal.GetBits (Decimal d) yo mismo. La idea es dividir los componentes por 10 tantas veces como sea necesario y reducir la escala. El código se basa (en gran medida) en el método 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;
}
No he realizado pruebas de rendimiento rigurosas, pero en un procesador MacOS Sierra 10.12.6, Intel Core i3 de 3,06 GHz y apuntando a .NetCore 2.1, este método parece ser mucho más rápido que el de D. Nesterov (no daré números desde , como he mencionado, mis pruebas no son rigurosas). Depende de quien implemente esto evaluar si las ganancias de rendimiento se compensan o no con la complejidad adicional del código.