Questa è una vecchia domanda, ma molte risposte non funzionano bene o vanno in overflow per i grandi numeri. Penso che la risposta di D. Nesterov sia la migliore: robusta, semplice e veloce. Voglio solo aggiungere i miei due centesimi. Ho giocato con i decimali e ho anche controllato il codice sorgente . Dalla public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)
documentazione del costruttore .
La rappresentazione binaria di un numero decimale è costituita da un segno a 1 bit, un numero intero a 96 bit e un fattore di scala utilizzato per dividere il numero intero e specificare quale parte di esso è una frazione decimale. Il fattore di scala è implicitamente il numero 10 elevato a un esponente compreso tra 0 e 28.
Sapendo questo, il mio primo approccio è stato quello di crearne un altro la decimal
cui scala corrispondesse ai decimali che volevo scartare, quindi troncarlo e infine creare un decimale con la scala desiderata.
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);
}
Questo metodo non è più veloce di quello di D. Nesterov ed è più complesso, quindi ho giocato un po 'di più. La mia ipotesi è che dover creare un ausiliario decimal
e recuperare i bit due volte lo renda più lento. Al secondo tentativo, ho manipolato personalmente i componenti restituiti dal metodo Decimal.GetBits (Decimal d) . L'idea è di dividere i componenti per 10 volte quante sono necessarie e ridurre la scala. Il codice si basa (fortemente) sul metodo 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;
}
Non ho eseguito rigorosi test delle prestazioni, ma su un MacOS Sierra 10.12.6, processore Intel Core i3 da 3,06 GHz e mirato a .NetCore 2.1 questo metodo sembra essere molto più veloce di quello di D.Nesterov (non fornirò numeri da allora , come ho già detto, i miei test non sono rigorosi). Spetta a chi lo implementa valutare se i guadagni in termini di prestazioni ripagano o meno per la complessità del codice aggiunta.