Tronca Due cifre decimali senza arrotondamento


108

Diciamo che ho un valore di 3,4679 e voglio 3,46, come posso troncare a due cifre decimali senza arrotondare per eccesso?

Ho provato quanto segue ma tutti e tre mi danno 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));
}

Questo restituisce 3.46, ma sembra solo sporco in qualche modo:

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

Risposte:


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

Attenzione che frazioni come queste non possono essere rappresentate con precisione in virgola mobile.


13
Usa il decimale per i tuoi valori e questa risposta funzionerà. È improbabile che funzioni sempre in qualsiasi rappresentazione in virgola mobile.
— driis

1
Questo mi fa chiedere se dovrebbe essere possibile specificare la direzione di arrotondamento in valori letterali in virgola mobile. Hmmmm.
— Steve314

Ci deve essere un modo per dire al programmatore che il calcolo partendo dal presupposto che un numero può memorizzare più di 308 cifre è decisamente inappropriato. Il doppio può immagazzinarne solo 15. L'overflow è una caratteristica molto importante qui, è andato in overflow piuttosto male.
— Hans Passant

Mi dispiace, pensavo che "valore" fosse decimale.
— nightcoder

54

Sarebbe più utile avere una funzione completa per l'utilizzo nel mondo reale del troncamento di un decimale in C #. Questo potrebbe essere convertito in un metodo di estensione Decimal piuttosto semplice se lo desideri:

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

Se hai bisogno di VB.NET prova questo:

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

Quindi usalo in questo modo:

decimal result = TruncateDecimal(0.275, 2);

o

Dim result As Decimal = TruncateDecimal(0.275, 2)

1
Questo traboccherà su grandi numeri.
— nightcoder

1
Per aggiungere a night coder, il fatto che stai usando Int32 come intermediario nella tua funzione causerà overflow. Dovresti usare Int64 se devi davvero trasmetterlo a un numero intero. La domanda sarebbe perché dovresti comunque sostenere quel sovraccarico extra poiché Truncate restituisce comunque integrali decimali. Basta fare qualcosa come: decimal step = (decimal) Math.Pow (10, precision); return Math.Truncate (passo * valore) / passo;
— Sarel Esterhuizen

Ho lasciato il cast a Integer. Li ho lasciati righe separate per una migliore leggibilità e comprensione di come funziona la funzione.
— Corgalore

27

Usa l'operatore modulo:

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

risultato: 0,54


1
Non capisco (leggi: non ho passato il tempo a verificare) tutte queste altre soluzioni fantasiose, questo fa esattamente quello che stavo cercando. Grazie!
— Isaac Baker

Eseguendolo su .Net Fiddle produce clicky0.5400 ... La risposta di D. Nesterov sotto ha prodotto il previsto 0.54.
— ttugates il

Ti rendi conto, @ttugates, che 0,54 e 0,5400 hanno esattamente lo stesso valore, giusto? Non importa quanti zeri seguono a meno che / fino a quando non arriva il momento di formattare per la visualizzazione - nel qual caso, il risultato sarà lo stesso se formattato correttamente: $"{0.54m:C}"produce "$0.54"e sì, $"{0.5400m:C}"produce "$0.54".
— Leonard Lewis il

25

Metodo universale e veloce (senza Math.Pow()/ moltiplicazione) per 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;
}

4
L'ho eseguito attraverso tutti i test menzionati nelle altre risposte e funziona perfettamente. Sorpreso che non abbia più voti positivi. Vale la pena notare che i decimali possono essere solo compresi tra 0 e 28 (probabilmente OK per la maggior parte delle persone).
— RichardOD

1
Sono d'accordo con questo. Questa è la migliore risposta. +1
— Branko Dimitrijevic

1
Ottima risposta, questo è ciò che io chiamo "pensa fuori dagli schemi"
— bruno.almeida

23

Un problema con gli altri esempi è che moltiplicano il valore di input prima di dividerlo. C'è un caso limite qui che puoi traboccare decimale moltiplicando prima, un caso limite, ma mi sono imbattuto in qualcosa. È più sicuro trattare la parte frazionaria separatamente come segue:

    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;
    }

So che questo è vecchio ma ho notato e problema con questo. Il fattore che hai qui è un int e quindi se stai troncando a un numero elevato di cifre decimali (diciamo 25), il risultato finale avrà un errore di precisione. L'ho risolto cambiando il tipo di fattore in decimale.
— TheKingDave

@TheKingDave: probabilmente è irrilevante ma poiché il fattore non può avere decimali dovrebbe essere meglio modellarlo finché non è vero?
— Ignacio Soler Garcia,

@SoMoS Per me Decimal ha funzionato meglio perché mi ha dato i valori di archiviazione più alti per factor. Ha ancora una limitazione ma è abbastanza grande per la mia applicazione. Long d'altra parte non è stato in grado di memorizzare numeri abbastanza grandi per la mia applicazione. Ad esempio, se esegui un Truncate (25) con long, allora ci sarà qualche imprecisione.
— TheKingDave

Aggiornato per consentire il troncamento a un numero maggiore di posizioni secondo il suggerimento di @TheKingDave, grazie.
— Tim Lloyd,

6

Lascio la soluzione per i numeri decimali.

Alcune delle soluzioni per i decimali qui sono soggette a overflow (se passiamo un numero decimale molto grande e il metodo proverà a moltiplicarlo).

La soluzione di Tim Lloyd è protetta dall'overflow ma non è troppo veloce.

La seguente soluzione è circa 2 volte più veloce e non presenta problemi di overflow:

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));
}

2
Non mi piace aggiungere il suffisso "Ex". C # supporta il sovraccarico, il tuo Truncatemetodo verrà raggruppato insieme a quelli nativi .net, offrendo all'utente un'esperienza senza interruzioni.
— Gqqnbig

1
Il tuo algoritmo produce alcuni risultati errati. La modalità MidpointRounding predefinita è Banker's Rounding, che arrotonda 0,5 al valore pari più vicino. Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));fallisce a causa di questo. Se specifichi l'arrotondamento "normale" (AwayFromZero) nella chiamata Math.Round, Assert.AreEqual(0m, 0m.TruncateEx(1));non riesce
— Jon Senchyna

1
L'unico modo in cui questa soluzione funzionerà è se usi MidpointRounding.AwayFromZeroun codice specifico per gestire il valore 0.
— Jon Senchyna

1
Jon ha ragione: 0m.TruncateEx (0) restituisce -1 a meno che 0 non sia gestito esplicitamente. Allo stesso modo -11m.TruncateEx (0) restituisce -10 a meno che MidpointRounding.AwayFromZero non venga utilizzato all'interno di Math.Round. Sembra però funzionare bene con queste modifiche.
— Ho Ho Ho

1
Anche con le modifiche per AwayFromZero e la gestione esplicita di 0, -999999999999999999999999999999m.TruncateEx (0) restituisce -9999999999999999999999999998, quindi in alcuni casi è ancora fallibile.
— Ho Ho Ho

3

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 decimalcui 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 decimale 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.


Ho dovuto votare a favore di tutto il pensiero e lo sforzo. Hai impostato Nesterov come punto di riferimento e hai continuato - tanto di cappello.
— AndrewBenjamin

2

questo funzionerebbe per te?

Console.Write(((int)(3.4679999999*100))/100.0);

2

Daresti ((long)(3.4679 * 100)) / 100.0quello che vuoi?


1

Ecco un metodo di estensione:

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

0

Se non ti preoccupi troppo delle prestazioni e il risultato finale può essere una stringa, il seguente approccio sarà resiliente ai problemi di precisione fluttuante:

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);
}

6
In realtà, l'hardcoding "." non è una buona idea, meglio usare System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator [0]
— David Airapetyan

0

Ecco la mia implementazione della funzione 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);
}

0

che dire di questo?

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

0

Oltre alle soluzioni di cui sopra, c'è un altro modo che possiamo ottenere.

    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

0

In alcune condizioni questo può essere sufficiente.

Avevo un valore decimale di SubCent = 0.0099999999999999999999999999M che tende a formattare in | SubCent: 0.010000 | attraversostring.Format("{0:N6}", SubCent ); e molte altre scelte di formattazione.

La mia esigenza non era di arrotondare il valore SubCent, ma nemmeno di registrare ogni cifra.

Quanto segue ha soddisfatto il mio requisito:

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

Che restituisce la stringa: | SubCent: 0.0099999 |

Per accogliere il valore che ha una parte intera, quanto segue è un inizio.

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

Che restituisce la stringa:

valFmt4 = "567890.00999999"

0

Sto usando questa funzione per troncare il valore dopo il decimale in una variabile stringa

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;
        }
    }

1
Sebbene questo codice possa rispondere alla domanda, fornire un contesto aggiuntivo su come e / o perché risolve il problema migliorerebbe il valore a lungo termine della risposta.
— Nic3500

0

Questo è quello che ho fatto:

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

sottraendo due numeri immessi senza arrotondare per eccesso o per difetto i decimali. perché le altre soluzioni non funzionano per me. non so se funzionerà per gli altri, voglio solo condividere questo :) Spero che funzioni anche per coloro che stanno trovando una soluzione a un problema simile al mio. Grazie

PS: sono un principiante quindi sentiti libero di indicare qualcosa su questo. : D questo va bene se hai davvero a che fare con i soldi, causa dei centesimi vero? ha solo 2 cifre decimali e arrotondando è un no no.


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);

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);
        }
    }

-2

In realtà vuoi 3,46 da 3,4679. Questa è solo una rappresentazione di caratteri, quindi non ha nulla a che fare con la funzione matematica, la funzione matematica non è concepita per fare questo lavoro. Usa semplicemente il codice seguente.

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

Il codice sopra può essere modificato per qualsiasi numero Metti il ​​codice seguente in un evento di clic del pulsante

Dim str As String 
str= result(3.4679)
 MsgBox("The number is " & str)

-2

che dire

var i = Math.Truncate(number);

var r = i + Math.Truncate((number - i) * 100) / 100;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.