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.