Zwei Dezimalstellen ohne Rundung abschneiden


106

Nehmen wir an, ich habe einen Wert von 3,4679 und möchte 3,46. Wie kann ich das auf zwei Dezimalstellen kürzen, ohne aufzurunden?

Ich habe folgendes versucht, aber alle drei geben mir 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));
}

Dies gibt 3,46 zurück, scheint aber nur ein bisschen schmutzig zu sein wie:

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

Antworten:


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

Beachten Sie, dass solche Brüche im Gleitkomma nicht genau dargestellt werden können.


12
Verwenden Sie für Ihre Werte eine Dezimalzahl, und diese Antwort funktioniert. Es ist unwahrscheinlich, dass in einer Gleitkommadarstellung immer funktioniert.
Driis

1
Ich frage mich daher, ob es möglich sein sollte, die Rundungsrichtung in Gleitkomma-Literalen anzugeben. Hmmmm.
Steve314

Es muss sein , einiger Weg , um den Programmierer zu sagen , dass mit der Annahme , die Berechnung , dass eine Reihe mehr als 308 Stellen speichern kann , ist grob unangemessen. Double kann nur 15 speichern. Überlauf ist hier ein Merkmal, es ist ziemlich stark übergelaufen.
Hans Passant

Es tut mir leid, ich dachte, "Wert" ist dezimal.
Nightcoder

52

Es wäre nützlicher, eine vollständige Funktion für die reale Verwendung des Abschneidens einer Dezimalstelle in C # zu haben. Dies könnte ziemlich einfach in eine Dezimalerweiterungsmethode konvertiert werden, wenn Sie möchten:

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

Wenn Sie VB.NET benötigen, versuchen Sie Folgendes:

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

Dann benutze es so:

decimal result = TruncateDecimal(0.275, 2);

oder

Dim result As Decimal = TruncateDecimal(0.275, 2)

1
Dies wird bei großen Zahlen überlaufen.
Nightcoder

1
Um den Nachtcodierer zu erweitern, führt die Tatsache, dass Sie Int32 als Vermittler in Ihrer Funktion verwenden, zu Überläufen. Sie sollten Int64 verwenden, wenn Sie es wirklich in eine Ganzzahl umwandeln müssen. Die Frage wäre, warum Sie diesen zusätzlichen Aufwand sowieso verursachen möchten, da Truncate ohnehin Dezimalintegrale zurückgibt. Machen Sie einfach etwas wie: Dezimalschritt = (Dezimal) Math.Pow (10, Genauigkeit); return Math.Truncate (Schritt * Wert) / Schritt;
Sarel Esterhuizen

Ich habe die Besetzung auf Integer fallen lassen. Ich habe sie getrennt gelassen, um die Lesbarkeit und das Verständnis der Funktionsweise zu verbessern.
Corgalore

27

Verwenden Sie den Moduloperator:

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

Ergebnis: 0,54


1
Ich verstehe all diese anderen ausgefallenen Lösungen nicht (lese: habe nicht die Zeit damit verbracht, sie zu überprüfen), das macht genau das , wonach ich gesucht habe. Danke dir!
Isaac Baker

24

Universelle und schnelle Methode (ohne Math.Pow()/ Multiplikation) für 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
Ich habe alle in den anderen Antworten genannten Tests durchlaufen und es funktioniert perfekt. Überrascht hat es nicht mehr positive Stimmen. Es ist erwähnenswert, dass Dezimalstellen nur zwischen 0 und 28 liegen können (wahrscheinlich in Ordnung für die meisten Menschen).
RichardOD

1
Ich stimme dem zu. Dies ist die beste Antwort. +1
Branko Dimitrijevic

1
Tolle Antwort, das nenne ich "über den Tellerrand hinaus denken"
bruno.almeida

23

Ein Problem bei den anderen Beispielen ist, dass sie den Eingabewert multiplizieren, bevor sie geteilt werden. Hier gibt es einen Kantenfall, bei dem Sie die Dezimalzahl überlaufen können, indem Sie zuerst einen Kantenfall multiplizieren, aber etwas, auf das ich gestoßen bin. Es ist sicherer, den Bruchteil wie folgt separat zu behandeln:

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

Ich weiß, dass dies alt ist, aber ich habe es bemerkt und habe Probleme damit. Der Faktor, den Sie hier haben, ist ein int. Wenn Sie also auf eine große Anzahl von Dezimalstellen (z. B. 25) kürzen, führt dies dazu, dass das Endergebnis einen Genauigkeitsfehler aufweist. Ich habe es behoben, indem ich den Faktortyp auf dezimal geändert habe.
TheKingDave

@TheKingDave: Wahrscheinlich ist es irrelevant, aber da der Faktor keine Dezimalstellen haben kann, sollte es besser sein, ihn so lange zu modellieren, oder?
Ignacio Soler Garcia

@SoMoS Für mich hat Decimal besser funktioniert, weil es mir die höchsten Speicherwerte für Faktor gegeben hat. Es gibt immer noch eine Einschränkung, aber es ist groß genug für meine Anwendung. Long hingegen konnte nicht genügend Zahlen für meine Bewerbung speichern. Wenn Sie beispielsweise eine Kürzung (25) mit long ausführen, tritt eine gewisse Ungenauigkeit auf.
TheKingDave

Aktualisiert, um das Abschneiden auf eine größere Anzahl von Stellen gemäß dem Vorschlag von @TheKingDave zu ermöglichen, danke.
Tim Lloyd

6

Ich werde die Lösung für Dezimalzahlen verlassen.

Einige der Lösungen für Dezimalstellen hier neigen zum Überlaufen (wenn wir eine sehr große Dezimalzahl übergeben und die Methode versucht, sie zu multiplizieren).

Die Lösung von Tim Lloyd ist vor Überlauf geschützt, aber nicht zu schnell.

Die folgende Lösung ist ungefähr zweimal schneller und hat kein Überlaufproblem:

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
Ich mag es nicht, "Ex" daran zu setzen. C # unterstützt das Überladen. Ihre TruncateMethode wird zusammen mit nativen .net-Methoden unterstützt , sodass der Benutzer eine nahtlose Benutzererfahrung erhält.
Gqqnbig

1
Ihr Algorithmus führt zu falschen Ergebnissen. Der Standard-MidpointRounding-Modus ist Banker's Rounding, bei dem 0,5 auf den nächsten geraden Wert gerundet wird. Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));scheitert daran. Wenn Sie im Math.Round-Aufruf "normale" Rundung (AwayFromZero) angeben, Assert.AreEqual(0m, 0m.TruncateEx(1));schlägt dies fehl
Jon Senchyna

1
Diese Lösung funktioniert nur, wenn Sie MidpointRounding.AwayFromZeroden Wert 0 verwenden und speziell codieren, um ihn zu verarbeiten.
Jon Senchyna

1
Jon ist korrekt: 0m.TruncateEx (0) ergibt -1, sofern 0 nicht explizit behandelt wird. Ebenso ergibt -11m.TruncateEx (0) -10, es sei denn, MidpointRounding.AwayFromZero wird in Math.Round verwendet. Scheint mit diesen Modifikationen gut zu funktionieren.
Ho Ho Ho

1
Selbst bei Änderungen für AwayFromZero und der expliziten Behandlung von 0 führt -9999999999999999999999999999m.TruncateEx (0) zu -9999999999999999999999999998, sodass es in einigen Fällen immer noch fehlbar ist.
Ho Ho Ho

3

Dies ist eine alte Frage, aber viele Antworten funktionieren bei großen Zahlen nicht gut oder laufen nicht über. Ich denke, die Antwort von D. Nesterov ist die beste: robust, einfach und schnell. Ich möchte nur meine zwei Cent hinzufügen. Ich habe mit Dezimalstellen herumgespielt und auch den Quellcode überprüft . Aus der public Decimal (int lo, int mid, int hi, bool isNegative, byte scale) Konstruktordokumentation .

Die binäre Darstellung einer Dezimalzahl besteht aus einem 1-Bit-Vorzeichen, einer 96-Bit-Ganzzahl und einem Skalierungsfaktor, mit dem die Ganzzahl geteilt und angegeben wird, welcher Teil davon ein Dezimalbruch ist. Der Skalierungsfaktor ist implizit die Zahl 10, die auf einen Exponenten im Bereich von 0 bis 28 angehoben wird.

In diesem Wissen bestand mein erster Ansatz darin, eine andere zu erstellen, decimalderen Skalierung den Dezimalstellen entspricht, die ich verwerfen wollte, sie dann abzuschneiden und schließlich eine Dezimalstelle mit der gewünschten Skalierung zu erstellen.

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

Diese Methode ist nicht schneller als die von D. Nesterov und komplexer, also habe ich ein bisschen mehr herumgespielt. Ich vermute, dass decimales langsamer wird, wenn man ein Auxiliar erstellen und die Bits zweimal abrufen muss. Bei meinem zweiten Versuch habe ich die von der Decimal.GetBits (Decimal d) -Methode zurückgegebenen Komponenten selbst manipuliert . Die Idee ist, die Komponenten so oft wie nötig durch 10 zu teilen und den Maßstab zu reduzieren. Der Code basiert (stark) auf der Methode 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;
    }

Ich habe keine strengen Leistungstests durchgeführt, aber auf einem MacOS Sierra 10.12.6, 3.06 GHz Intel Core i3-Prozessor und Targeting .NetCore 2.1 scheint diese Methode viel schneller zu sein als die von D. Nesterov (ich werde seitdem keine Zahlen mehr angeben Wie bereits erwähnt, sind meine Tests nicht streng. Es liegt an jedem, der dies implementiert, zu bewerten, ob sich die Leistungssteigerungen für die zusätzliche Codekomplexität auszahlen oder nicht.


Ich musste wegen all der Gedanken und Anstrengungen abstimmen. Sie haben Nesterovs als Maßstab gesetzt und weitergemacht - Hüte ab.
AndrewBenjamin

2

würde das für dich funktionieren?

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

2

Würden Sie ((long)(3.4679 * 100)) / 100.0geben, was Sie wollen?


1

Hier ist eine Erweiterungsmethode:

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

Wenn Sie sich nicht zu viele Sorgen um die Leistung machen und Ihr Endergebnis eine Zeichenfolge sein kann, ist der folgende Ansatz für Probleme mit schwebender Genauigkeit unempfindlich:

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
Eigentlich Hardcodierung '.' ist keine gute Idee, verwenden Sie besser System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator [0]
David Airapetyan

0

Hier ist meine Implementierung der TRUNC-Funktion

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

was ist damit?

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

Abgesehen von den oben genannten Lösungen gibt es noch einen anderen Weg, den wir erreichen können.

    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

Unter bestimmten Umständen kann dies ausreichen.

Ich hatte einen Dezimalwert von SubCent = 0,0099999999999999999999999999M , der dazu neigt, auf | SubCent: 0,010000 | zu formatieren überstring.Format("{0:N6}", SubCent ); und viele andere Formatierungsoptionen.

Meine Anforderung war nicht, den SubCent-Wert zu runden, sondern auch nicht jede Ziffer zu protokollieren.

Folgendes erfüllte meine Anforderung:

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

Was die Zeichenfolge zurückgibt: | SubCent: 0.0099999 |

Um den Wert mit einem ganzzahligen Teil aufzunehmen, ist das Folgende ein Anfang.

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

Welches gibt die Zeichenfolge zurück:

valFmt4 = "567890.00999999"

0

Ich benutze diese Funktion, um Wert nach Dezimalstelle in einer Zeichenfolgenvariablen abzuschneiden

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
Während dieser Code die Frage möglicherweise beantwortet, würde die Bereitstellung eines zusätzlichen Kontexts darüber, wie und / oder warum das Problem gelöst wird, den langfristigen Wert der Antwort verbessern.
Nic3500

0

Das habe ich getan:

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

Subtrahieren von zwei eingegebenen Zahlen, ohne die Dezimalstellen auf- oder abzurunden. weil die anderen Lösungen bei mir nicht funktionieren. Ich weiß nicht, ob es für andere funktionieren wird, ich möchte dies nur teilen :) Ich hoffe, es funktioniert auch für diejenigen, die eine Lösung für ein ähnliches Problem wie ich finden. Vielen Dank

PS: Ich bin ein Anfänger, also zögern Sie nicht, darauf hinzuweisen. : D Das ist gut, wenn Sie tatsächlich mit Geld zu tun haben, wegen der Cent, oder? Es hat nur 2 Dezimalstellen und die Rundung ist ein Nein Nein.


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

-2

Eigentlich willst du 3.46 von 3.4679. Dies ist nur eine Darstellung von Zeichen. Es gibt also nichts mit der mathematischen Funktion zu tun. Die Math-Funktion ist nicht für diese Arbeit vorgesehen. Verwenden Sie einfach den folgenden Code.

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

Der obige Code kann für beliebige Zahlen geändert werden. Fügen Sie den folgenden Code in ein Schaltflächenklickereignis ein

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

-2

wie wäre es mit

var i = Math.Truncate(number);var r = i + Math.Truncate((number - i) * 100) / 100;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.