Tronquer Deux décimales sans arrondi

108

Disons que j'ai une valeur de 3,4679 et que je veux 3,46, comment puis-je tronquer à deux décimales sans arrondir?

J'ai essayé ce qui suit mais les trois me donnent 3.47:

void Main()
{
    Console.Write(Math.Round(3.4679, 2,MidpointRounding.ToEven));
    Console.Write(Math.Round(3.4679, 2,MidpointRounding.AwayFromZero));
    Console.Write(Math.Round(3.4679, 2));
}

Cela renvoie 3.46, mais semble juste sale comment:

void Main()
{
    Console.Write(Math.Round(3.46799999999 -.005 , 2));
}

la source

Réponses:

152
value = Math.Truncate(100 * value) / 100;

Attention, de telles fractions ne peuvent pas être représentées avec précision en virgule flottante.

Hans Passant
la source
13
Utilisez décimal pour vos valeurs et cette réponse fonctionnera. Il est peu probable qu'il fonctionne toujours dans une représentation en virgule flottante.
driis
1
Cela me fait me demander s'il devrait être possible de spécifier la direction d'arrondi dans les littéraux à virgule flottante. Hmmmm.
Steve314 le
Il doit y avoir une façon de dire au programmeur que le calcul de l'hypothèse qu'un nombre peut stocker plus de 308 chiffres est grossièrement inappropriée. Double ne peut stocker que 15. Le débordement est vraiment une caractéristique ici, il a plutôt mal débordé.
Hans Passant
Je suis désolé, je pensais que la "valeur" était décimale.
nightcoder
54

Il serait plus utile d'avoir une fonction complète pour une utilisation dans le monde réel de la troncature d'une décimale en C #. Cela pourrait être converti en une méthode d'extension Decimal assez facilement si vous le souhaitez:

public decimal TruncateDecimal(decimal value, int precision)
{
    decimal step = (decimal)Math.Pow(10, precision);
    decimal tmp = Math.Truncate(step * value);
    return tmp / step;
}

Si vous avez besoin de VB.NET, essayez ceci:

Function TruncateDecimal(value As Decimal, precision As Integer) As Decimal
    Dim stepper As Decimal = Math.Pow(10, precision)
    Dim tmp As Decimal = Math.Truncate(stepper * value)
    Return tmp / stepper
End Function

Ensuite, utilisez-le comme ceci:

decimal result = TruncateDecimal(0.275, 2);

ou

Dim result As Decimal = TruncateDecimal(0.275, 2)
Corgalore
la source
1
Cela débordera sur les grands nombres.
nightcoder
1
Pour ajouter au codeur de nuit, le fait que vous utilisiez Int32 comme intermédiaire dans votre fonction provoquera des débordements. Vous devez utiliser Int64 si vous devez vraiment le convertir en Integer. La question serait de savoir pourquoi vous voudriez de toute façon encourir cette surcharge puisque Truncate renvoie de toute façon des intégrales décimales. Faites quelque chose comme: decimal step = (decimal) Math.Pow (10, precision); return Math.Truncate (valeur d'étape *) / étape;
Sarel Esterhuizen
J'ai laissé tomber le casting sur Integer. Je leur ai laissé des lignes séparées pour une meilleure lisibilité et une meilleure compréhension du fonctionnement de la fonction.
Corgalore
27

Utilisez l'opérateur module:

var fourPlaces = 0.5485M;
var twoPlaces = fourPlaces - (fourPlaces % 0.01M);

résultat: 0,54

Leonard Lewis
la source
1
Je ne comprends pas (lire: je n'ai pas passé le temps à vérifier) ​​toutes ces autres solutions sophistiquées, cela fait exactement ce que je recherchais. Je vous remercie!
Isaac Baker
L'exécution sur .Net Fiddle clicky produit 0.5400... La réponse de D. Nesterov ci-dessous a produit le attendu 0.54.
ttugates
Vous réalisez, @ttugates, que 0,54 et 0,5400 sont exactement la même valeur, n'est-ce pas? Peu importe le nombre de zéros qui suivent, à moins que / jusqu'à ce qu'il vienne le temps de formater pour l'affichage - auquel cas, le résultat sera le même s'il est correctement formaté: $"{0.54m:C}"produit "$0.54"et oui, $"{0.5400m:C}"produit "$0.54".
Leonard Lewis
25

Méthode universelle et rapide (sans Math.Pow()/ multiplication) pour System.Decimal:

decimal Truncate(decimal d, byte decimals)
{
    decimal r = Math.Round(d, decimals);

    if (d > 0 && r > d)
    {
        return r - new decimal(1, 0, 0, false, decimals);
    }
    else if (d < 0 && r < d)
    {
        return r + new decimal(1, 0, 0, false, decimals);
    }

    return r;
}
D. Nesterov
la source
4
J'ai parcouru tous les tests mentionnés dans les autres réponses et cela fonctionne parfaitement. Surpris qu'il n'ait pas plus de votes positifs. Il est à noter que les décimales ne peuvent être qu'entre 0 et 28 (probablement OK pour la plupart des gens).
RichardOD
1
Je suis d'accord. C'est la meilleure réponse. +1
Branko Dimitrijevic
1
Excellente réponse, c'est ce que j'appelle "sortir des sentiers battus"
bruno.almeida
23

Un problème avec les autres exemples est qu'ils multiplient la valeur d'entrée avant de la diviser. Il y a un cas de bord ici où vous pouvez déborder de décimal en multipliant d'abord, un cas de bord, mais quelque chose que j'ai rencontré. Il est plus sûr de traiter la partie fractionnaire séparément comme suit:

    public static decimal TruncateDecimal(this decimal value, int decimalPlaces)
    {
        decimal integralValue = Math.Truncate(value);

        decimal fraction = value - integralValue;

        decimal factor = (decimal)Math.Pow(10, decimalPlaces);

        decimal truncatedFraction = Math.Truncate(fraction * factor) / factor;

        decimal result = integralValue + truncatedFraction;

        return result;
    }
Tim Lloyd
la source
Je sais que c'est vieux mais j'ai remarqué et j'ai un problème avec cela. Le facteur que vous avez ici est un entier et donc si vous tronquez à un grand nombre de décimales (disons 25), le résultat final aura une erreur de précision. Je l'ai corrigé en changeant le type de facteur en décimal.
TheKingDave
@TheKingDave: ce n'est probablement pas pertinent, mais comme le facteur ne peut pas avoir de décimales, il vaut mieux le modéliser aussi longtemps, non?
Ignacio Soler Garcia
@SoMoS Pour moi, Decimal fonctionnait mieux car il me donnait les valeurs de stockage les plus élevées pour le facteur. Il a toujours une limitation mais il est assez grand pour mon application. Long d'un autre côté n'a pas été en mesure de stocker des nombres assez grands pour mon application. Par exemple, si vous effectuez un Truncate (25) avec long, il y aura une certaine imprécision.
TheKingDave
Mis à jour pour permettre la troncature à un plus grand nombre d'endroits selon la suggestion @TheKingDave, merci.
Tim Lloyd
6

Je laisserai la solution pour les nombres décimaux.

Certaines des solutions pour les décimales ici sont sujettes au débordement (si nous passons un très grand nombre décimal et que la méthode essaiera de le multiplier).

La solution de Tim Lloyd est protégée contre les débordements mais ce n'est pas trop rapide.

La solution suivante est environ 2 fois plus rapide et ne présente pas de problème de débordement:

public static class DecimalExtensions
{
    public static decimal TruncateEx(this decimal value, int decimalPlaces)
    {
        if (decimalPlaces < 0)
            throw new ArgumentException("decimalPlaces must be greater than or equal to 0.");

        var modifier = Convert.ToDecimal(0.5 / Math.Pow(10, decimalPlaces));
        return Math.Round(value >= 0 ? value - modifier : value + modifier, decimalPlaces);
    }
}

[Test]
public void FastDecimalTruncateTest()
{
    Assert.AreEqual(-1.12m, -1.129m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.120m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.125m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.1255m.TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.1254m.TruncateEx(2));
    Assert.AreEqual(0m,      0.0001m.TruncateEx(3));
    Assert.AreEqual(0m,     -0.0001m.TruncateEx(3));
    Assert.AreEqual(0m,     -0.0000m.TruncateEx(3));
    Assert.AreEqual(0m,      0.0000m.TruncateEx(3));
    Assert.AreEqual(1.1m,    1.12m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.15m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.19m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.111m. TruncateEx(1));
    Assert.AreEqual(1.1m,    1.199m. TruncateEx(1));
    Assert.AreEqual(1.2m,    1.2m.   TruncateEx(1));
    Assert.AreEqual(0.1m,    0.14m.  TruncateEx(1));
    Assert.AreEqual(0,      -0.05m.  TruncateEx(1));
    Assert.AreEqual(0,      -0.049m. TruncateEx(1));
    Assert.AreEqual(0,      -0.051m. TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.14m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.15m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.16m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.19m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.199m. TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.101m. TruncateEx(1));
    Assert.AreEqual(0m,     -0.099m. TruncateEx(1));
    Assert.AreEqual(0m,     -0.001m. TruncateEx(1));
    Assert.AreEqual(1m,      1.99m.  TruncateEx(0));
    Assert.AreEqual(1m,      1.01m.  TruncateEx(0));
    Assert.AreEqual(-1m,    -1.99m.  TruncateEx(0));
    Assert.AreEqual(-1m,    -1.01m.  TruncateEx(0));
}
codeur de nuit
la source
2
Je n'aime pas suffixer "Ex". C # prend en charge la surcharge, votre Truncateméthode sera regroupée avec les méthodes natives .net, offrant à l'utilisateur une expérience transparente.
Gqqnbig
1
Votre algorithme donne des résultats incorrects. Le mode MidpointRounding par défaut est Banker's Rounding, qui arrondit 0,5 à la valeur paire la plus proche. Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));échoue à cause de cela. Si vous spécifiez un arrondi "normal" (AwayFromZero) dans l'appel Math.Round, puis Assert.AreEqual(0m, 0m.TruncateEx(1));échoue
Jon Senchyna
1
La seule façon dont cette solution fonctionnera est si vous utilisez MidpointRounding.AwayFromZeroet spécifiquement du code pour gérer la valeur 0.
Jon Senchyna
1
Jon a raison: 0m.TruncateEx (0) donne -1 sauf si 0 est explicitement géré De même -11m.TruncateEx (0) donne -10 sauf si MidpointRounding.AwayFromZero est utilisé dans Math.Round. Cela semble bien fonctionner avec ces modifications.
Ho Ho Ho
1
Même avec des changements pour AwayFromZero et une gestion explicite de 0, -9999999999999999999999999999m.TruncateEx (0) donne -9999999999999999999999999998, il est donc toujours faillible dans certains cas.
Ho Ho Ho
3

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 decimaldont 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 decimalet 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.

Muscicapa Striata
la source
J'ai dû voter à cause de toute la réflexion et des efforts. Vous avez défini Nesterov comme une référence et avez continué - chapeau bas.
AndrewBenjamin
2

cela fonctionnerait-il pour vous?

Console.Write(((int)(3.4679999999*100))/100.0);
John Boker
la source
2

Donnerait ((long)(3.4679 * 100)) / 100.0ce que vous voulez?

Franc
la source
1

Voici une méthode d'extension:

public static decimal? TruncateDecimalPlaces(this decimal? value, int places)
    {
        if (value == null)
        {
            return null;
        }

        return Math.Floor((decimal)value * (decimal)Math.Pow(10, places)) / (decimal)Math.Pow(10, places);

    } // end
John Meyer
la source
0

Si vous ne vous inquiétez pas trop des performances et que votre résultat final peut être une chaîne, l'approche suivante sera résiliente aux problèmes de précision flottante:

string Truncate(double value, int precision)
{
    if (precision < 0)
    {
        throw new ArgumentOutOfRangeException("Precision cannot be less than zero");
    }

    string result = value.ToString();

    int dot = result.IndexOf('.');
    if (dot < 0)
    {
        return result;
    }

    int newLength = dot + precision + 1;

    if (newLength == dot + 1)
    {
        newLength--;
    }

    if (newLength > result.Length)
    {
        newLength = result.Length;
    }

    return result.Substring(0, newLength);
}
David Airapetyan
la source
6
En fait, codage en dur '.' n'est pas une bonne idée, mieux utiliser System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator [0]
David Airapetyan
0

Voici mon implémentation de la fonction TRUNC

private static object Tranc(List<Expression.Expression> p)
{
    var target = (decimal)p[0].Evaluate();

    // check if formula contains only one argument
    var digits = p.Count > 1
        ? (decimal) p[1].Evaluate()
        : 0;

    return Math.Truncate((double)target * Math.Pow(10, (int)digits)) / Math.Pow(10, (int)digits);
}
ladeangel
la source
0

Et ça?

Function TruncateDecimal2(MyValue As Decimal) As Decimal
        Try
            Return Math.Truncate(100 * MyValue) / 100
        Catch ex As Exception
            Return Math.Round(MyValue, 2)
        End Try
End Function
user2241289
la source
0

Outre les solutions ci-dessus, il existe un autre moyen de parvenir à ce résultat.

    decimal val=23.5678m,finalValue;

    //take the decimal part    
     int decimalPos = val.ToString().IndexOf('.');
     string decimalPart = val.ToString().Substring(decimalPosition+1,val.ToString().Length);
    //will result.56
   string wholePart=val.ToString().Substring(0,decimalPos-1);
   //concantinate and parse for decimal.
  string truncatedValue=wholePart+decimalPart;//"23.56"
  bool isDecimal=Decimal.tryParse(truncatedValue,out finalValue);//finalValue=23.56
Hameed Syed
la source
0

Dans certaines conditions, cela peut suffire.

J'ai eu une valeur décimale de SubCent = 0,0099999999999999999999999999M qui a tendance à se formater à | SubCent: 0.010000 | viastring.Format("{0:N6}", SubCent ); et de nombreux autres choix de formatage.

Mon exigence n'était pas d'arrondir la valeur SubCent, mais pas non plus de consigner chaque chiffre.

Les éléments suivants répondaient à mes exigences:

string.Format("SubCent:{0}|", 
    SubCent.ToString("N10", CultureInfo.InvariantCulture).Substring(0, 9));

Qui renvoie la chaîne: | SubCent: 0,0099999 |

Pour accueillir la valeur ayant une partie entière, ce qui suit est un début.

tmpValFmt = 567890.0099999933999229999999M.ToString("0.0000000000000000000000000000");
decPt = tmpValFmt.LastIndexOf(".");
if (decPt < 0) decPt = 0;
valFmt4 = string.Format("{0}", tmpValFmt.Substring(0, decPt + 9));

Ce qui renvoie la chaîne:

valFmt4 = "567890.00999999"
kevinwaite
la source
0

J'utilise cette fonction pour tronquer la valeur après la virgule dans une variable de chaîne

public static string TruncateFunction(string value)
    {
        if (string.IsNullOrEmpty(value)) return "";
        else
        {
            string[] split = value.Split('.');
            if (split.Length > 0)
            {
                string predecimal = split[0];
                string postdecimal = split[1];
                postdecimal = postdecimal.Length > 6 ? postdecimal.Substring(0, 6) : postdecimal;
                return predecimal + "." + postdecimal;

            }
            else return value;
        }
    }
Arun Kumar
la source
1
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire sur la façon et / ou pourquoi il résout le problème améliorerait la valeur à long terme de la réponse.
Nic3500
0

C'est ce que j'ai fait:

        c1 = a1 - b1;
        d1 = Math.Ceiling(c1 * 100) / 100;

soustraire deux nombres entrés sans arrondir les décimales vers le haut ou vers le bas. parce que les autres solutions ne fonctionnent pas pour moi. Je ne sais pas si cela fonctionnera pour les autres, je veux juste partager ceci :) J'espère que cela fonctionne pour ceux qui trouvent une solution à un problème similaire au mien. Merci

PS: je suis un débutant, alors n'hésitez pas à signaler quelque chose à ce sujet. : D c'est bien si vous traitez réellement avec de l'argent, à cause des cents non? il n'a que 2 décimales et son arrondi est un non non.

Nooj
la source
0
        public static void ReminderDigints(decimal? number, out decimal? Value,  out decimal? Reminder)
        {
            Reminder = null;
            Value = null;
            if (number.HasValue)
            {
                Value = Math.Floor(number.Value);
                Reminder = (number - Math.Truncate(number.Value));
            }
        }



        decimal? number= 50.55m;             
        ReminderDigints(number, out decimal? Value, out decimal? Reminder);
Zoyeb Shaikh
la source
0
public static decimal TruncateDecimalPlaces(this decimal value, int precision)
    {
        try
        {
            step = (decimal)Math.Pow(10, precision);
            decimal tmp = Math.Truncate(step * value);
            return tmp / step;
        }
        catch (OverflowException)
        {
            step = (decimal)Math.Pow(10, -1 * precision);
            return value - (value % step);
        }
    }
Monsieur Wang de Next Door
la source
-2

En fait, vous voulez 3.46 de 3.4679. Il ne s'agit que d'une représentation de caractères. Il n'y a donc rien à voir avec la fonction mathématique. La fonction Math n'est pas destinée à faire ce travail. Utilisez simplement le code suivant.

Dim str1 As String
str1=""
str1 ="3.4679" 
  Dim substring As String = str1.Substring(0, 3)

    ' Write the results to the screen.
    Console.WriteLine("Substring: {0}", substring)

Or 
    Please use the following code.
Public function result(ByVal x1 As Double) As String 
  Dim i as  Int32
  i=0
  Dim y as String
  y = ""
  For Each ch as Char In x1.ToString
    If i>3 then
     Exit For
    Else
    y + y +ch
    End if
    i=i+1
  Next
  return y
End Function

Le code ci-dessus peut être modifié pour n'importe quel nombre Mettez le code suivant dans un événement de clic de bouton

Dim str As String 
str= result(3.4679)
 MsgBox("The number is " & str)
Antony Thomas
la source
-2

qu'en est-il de

var i = Math.Truncate(number);var r = i + Math.Truncate((number - i) * 100) / 100;
Jacky
la source