C'est une vieille question, mais de nombreuses réponses ne fonctionnent pas bien ou débordent pour de grands nombres. Je pense que la réponse de D. Nesterov est la meilleure: robuste, simple et rapide. Je veux juste ajouter mes deux cents. J'ai joué avec les décimales et j'ai également vérifié le code source . De la public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)
documentation du constructeur .
La représentation binaire d'un nombre décimal se compose d'un signe de 1 bit, d'un nombre entier de 96 bits et d'un facteur d'échelle utilisé pour diviser le nombre entier et spécifier quelle partie de celui-ci est une fraction décimale. Le facteur d'échelle est implicitement le nombre 10 élevé à un exposant compris entre 0 et 28.
Sachant cela, ma première approche a été d'en créer une autre decimal
dont l'échelle correspond aux décimales que je voulais supprimer, puis de la tronquer et enfin de créer une décimale avec l'échelle souhaitée.
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);
}
Cette méthode n'est pas plus rapide que celle de D. Nesterov et elle est plus complexe, alors j'ai joué un peu plus. Je suppose que le fait de devoir créer un auxiliaire decimal
et de récupérer les bits deux fois le ralentit. Lors de ma deuxième tentative, j'ai manipulé moi-même les composants retournés par la méthode Decimal.GetBits (Decimal d) . L'idée est de diviser les composants par 10 autant de fois que nécessaire et de réduire l'échelle. Le code est basé (fortement) sur la méthode 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;
}
Je n'ai pas effectué de tests de performances rigoureux, mais sur un processeur MacOS Sierra 10.12.6, Intel Core i3 3,06 GHz et ciblant .NetCore 2.1, cette méthode semble être beaucoup plus rapide que celle de D.Nesterov (je ne donnerai pas de chiffres depuis , comme je l'ai mentionné, mes tests ne sont pas rigoureux). Il appartient à quiconque implémente cela d'évaluer si les gains de performances sont récompensés par la complexité du code supplémentaire.