Potong Dua tempat desimal tanpa pembulatan


107

Katakanlah saya memiliki nilai 3,4679 dan ingin 3,46, bagaimana saya dapat memotong ke dua tempat desimal tanpa pembulatan?

Saya telah mencoba yang berikut tetapi ketiganya memberi saya 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));
}

Ini mengembalikan 3,46, tetapi sepertinya kotor entah bagaimana:

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

Jawaban:


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

Berhati-hatilah karena pecahan seperti ini tidak dapat direpresentasikan secara akurat dalam floating point.


12
Gunakan desimal untuk nilai Anda dan jawaban ini akan berhasil. Ini tidak mungkin untuk selalu berfungsi dalam representasi floating point apa pun.
driis

1
Itu membuat saya bertanya-tanya apakah mungkin untuk menentukan arah pembulatan dalam literal floating point. Hmmmm.
Steve314

Harus ada beberapa cara untuk memberitahu programmer yang menghitung dengan asumsi bahwa angka dapat menyimpan lebih dari 308 digit adalah terlalu pantas. Double hanya dapat menyimpan 15. Overflow adalah fitur yang sangat banyak di sini, overflow agak buruk.
Hans Passant

Maaf, saya pikir "nilai" adalah desimal.
nightcoder

54

Akan lebih berguna jika memiliki fungsi lengkap untuk penggunaan dunia nyata memotong desimal dalam C #. Ini dapat diubah menjadi metode ekstensi Desimal dengan cukup mudah jika Anda ingin:

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

Jika Anda membutuhkan VB.NET coba ini:

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

Kemudian gunakan seperti ini:

decimal result = TruncateDecimal(0.275, 2);

atau

Dim result As Decimal = TruncateDecimal(0.275, 2)

1
Ini akan meluap dalam jumlah besar.
nightcoder

1
Untuk menambah pengode malam, fakta bahwa Anda menggunakan Int32 sebagai perantara dalam fungsi Anda akan menyebabkan luapan. Anda harus menggunakan Int64 jika Anda benar-benar harus mentransmisikannya ke Integer. Pertanyaannya adalah mengapa Anda ingin mengeluarkan overhead tambahan itu karena Truncate mengembalikan integral Desimal. Lakukan saja seperti: langkah desimal = (desimal) Math.Pow (10, presisi); return Math.Truncate (langkah * nilai) / langkah;
Sarel Esterhuizen

Saya menjatuhkan cast ke Integer. Saya meninggalkan baris terpisah untuk keterbacaan dan pemahaman yang lebih baik tentang cara kerja fungsi.
Corgalore

27

Gunakan operator modulus:

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

hasil: 0,54


1
Saya tidak mengerti (baca: tidak menghabiskan waktu untuk memverifikasi) semua solusi mewah lainnya, ini persis seperti yang saya cari. Terima kasih!
Isaac Baker

Menjalankan ini di .Net Fiddle clicky menghasilkan 0.5400... Jawaban oleh D. Nesterov di bawah ini menghasilkan yang diharapkan 0.54.
ttugates

Anda menyadari, @ttugates, bahwa 0,54 dan 0,5400 adalah nilai yang persis sama, bukan? Tidak masalah berapa banyak angka nol yang mengikuti kecuali / sampai tiba waktunya untuk memformat tampilan — dalam hal ini, hasilnya akan sama jika diformat dengan benar: $"{0.54m:C}"produksi "$0.54"dan ya, $"{0.5400m:C}"produksi "$0.54".
Leonard Lewis

25

Metode universal dan cepat (tanpa Math.Pow()/ perkalian) untuk 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
Saya menjalankan ini melalui semua tes yang disebutkan di jawaban lain dan berfungsi dengan sempurna. Terkejut, tidak ada lebih banyak suara positif. Perlu dicatat bahwa desimal hanya bisa antara 0 dan 28 (Mungkin OK untuk kebanyakan orang).
RichardOD

1
Saya setuju itu. Ini jawaban terbaik. +1
Branko Dimitrijevic

1
Jawaban yang bagus, itulah yang saya sebut "berpikir di luar kotak"
bruno.almeida

23

Satu masalah dengan contoh lainnya adalah mereka mengalikan nilai input sebelum membaginya. Ada kasus tepi di sini bahwa Anda dapat melimpah desimal dengan mengalikannya terlebih dahulu, kasus tepi, tetapi sesuatu yang saya temui. Lebih aman menangani bagian pecahan secara terpisah sebagai berikut:

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

Saya tahu ini sudah tua tetapi saya memperhatikan dan masalah dengan ini. Faktor yang Anda miliki di sini adalah int dan jadi jika Anda memotong ke sejumlah besar tempat desimal (katakanlah 25) itu akan menyebabkan hasil akhirnya memiliki kesalahan presisi. Saya memperbaikinya dengan mengubah jenis faktor menjadi desimal.
TheKingDave

@TheKingDave: mungkin itu tidak relevan tetapi karena faktor tidak dapat memiliki desimal harus lebih baik untuk memodelkannya selama kan?
Ignacio Soler Garcia

@SoS Bagi saya Desentralisasi bekerja lebih baik karena memberi saya nilai penyimpanan tertinggi untuk faktor. Ini masih memiliki batasan tetapi cukup besar untuk aplikasi saya. Di sisi lain, lama tidak dapat menyimpan jumlah yang cukup besar untuk aplikasi saya. Misalnya jika Anda melakukan Truncate (25) dengan panjang maka akan ada beberapa ketidakakuratan.
TheKingDave

Diperbarui untuk memungkinkan pemotongan ke lebih banyak tempat sesuai saran @TheKingDave, terima kasih.
Tim Lloyd

6

Saya akan meninggalkan solusi untuk angka desimal.

Beberapa solusi untuk desimal di sini cenderung meluap (jika kita melewatkan angka desimal yang sangat besar dan metode akan mencoba mengalikannya).

Solusi Tim Lloyd dilindungi dari luapan tetapi tidak terlalu cepat.

Solusi berikut ini sekitar 2 kali lebih cepat dan tidak memiliki masalah 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
Saya tidak suka memberi akhiran "Ex" untuk itu. C # mendukung overloadding, Truncatemetode Anda akan dikelompokkan bersama dengan yang asli .net, memberikan pengalaman yang mulus bagi pengguna.
Gqqnbig

1
Algoritme Anda menghasilkan beberapa hasil yang salah. Mode MidpointRounding default adalah Banker's Rounding, yang membulatkan 0,5 ke nilai genap terdekat. Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));gagal karena ini. Jika Anda menentukan pembulatan "normal" (AwayFromZero) dalam panggilan Math.Round, maka Assert.AreEqual(0m, 0m.TruncateEx(1));gagal
Jon Senchyna

1
Satu-satunya cara solusi ini akan berhasil adalah jika Anda menggunakan MidpointRounding.AwayFromZerodan secara khusus kode untuk menangani nilai 0.
Jon Senchyna

1
Jon benar: 0m.TruncateEx (0) menghasilkan -1 kecuali 0 ditangani secara eksplisit. Demikian juga -11m.TruncateEx (0) menghasilkan -10 kecuali MidpointRounding.AwayFromZero digunakan dalam Math.Round. Tampaknya bekerja dengan baik dengan modifikasi itu.
Ho Ho Ho

1
Bahkan dengan perubahan untuk AwayFromZero dan penanganan eksplisit 0, -9999999999999999999999999999m.TruncateEx (0) menghasilkan -9999999999999999999999999998, jadi masih bisa salah dalam beberapa kasus.
Ho Ho Ho

3

Ini adalah pertanyaan lama, tetapi banyak pengguna tidak berkinerja baik atau berlebihan untuk jumlah besar. Saya pikir jawaban D. Nesterov adalah yang terbaik: kuat, sederhana dan cepat. Saya hanya ingin menambahkan dua sen saya. Saya bermain-main dengan desimal dan juga memeriksa kode sumbernya . Dari public Decimal (int lo, int mid, int hi, bool isNegative, byte scale) dokumentasi konstruktor .

Representasi biner dari bilangan desimal terdiri dari tanda 1-bit, bilangan bulat 96-bit, dan faktor skala yang digunakan untuk membagi bilangan bulat dan menentukan bagian mana yang merupakan pecahan desimal. Faktor skala secara implisit adalah angka 10 yang dipangkatkan menjadi eksponen yang berkisar dari 0 hingga 28.

Mengetahui hal ini, pendekatan pertama saya adalah membuat yang lain decimalyang skalanya sesuai dengan desimal yang ingin saya buang, lalu potong dan akhirnya buat desimal dengan skala yang diinginkan.

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

Metode ini tidak lebih cepat dari metode D. Nesterov dan lebih kompleks, jadi saya bermain-main sedikit lebih banyak. Dugaan saya adalah bahwa harus membuat auxiliar decimaldan mengambil bit dua kali membuatnya lebih lambat. Pada upaya kedua saya, saya memanipulasi komponen yang dikembalikan oleh metode Decimal.GetBits (Decimal d) sendiri. Idenya adalah membagi komponen dengan 10 kali sebanyak yang dibutuhkan dan mengurangi skala. Kode ini didasarkan (sangat) pada metode 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;
    }

Saya belum melakukan tes kinerja yang ketat, tetapi pada MacOS Sierra 10.12.6, prosesor Intel Core i3 3,06 GHz dan menargetkan .NetCore 2.1 metode ini tampaknya jauh lebih cepat daripada D. Nesterov (saya tidak akan memberikan angka sejak , seperti yang telah saya sebutkan, pengujian saya tidak ketat). Terserah siapa pun yang menerapkan ini untuk mengevaluasi apakah kinerja yang diperoleh terbayar untuk kompleksitas kode yang ditambahkan.


Saya harus memberi suara positif karena semua pemikiran dan usaha. Anda menetapkan Nesterov sebagai patokan dan terus maju - angkat topi.
AndrewBenjamin

2

apakah ini akan berhasil untukmu?

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

2

Akan ((long)(3.4679 * 100)) / 100.0memberikan apa yang Anda inginkan?


1

Berikut adalah metode ekstensi:

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

Jika Anda tidak terlalu khawatir tentang kinerja dan hasil akhir Anda bisa berupa string, pendekatan berikut akan tahan terhadap masalah presisi mengambang:

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
Sebenarnya, hardcode '.' bukan ide yang baik, lebih baik gunakan System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator [0]
David Airapetyan

0

Berikut adalah implementasi fungsi TRUNC saya

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

bagaimana dengan ini?

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

Selain solusi di atas, ada cara lain yang bisa kami capai.

    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

Dalam beberapa kondisi, ini mungkin cukup.

Saya memiliki nilai desimal SubCent = 0.0099999999999999999999999999M yang cenderung diformat ke | SubCent: 0.010000 | melaluistring.Format("{0:N6}", SubCent ); dan banyak pilihan pemformatan lainnya.

Persyaratan saya bukanlah membulatkan nilai SubCent, tetapi juga tidak mencatat setiap digit.

Berikut ini memenuhi kebutuhan saya:

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

Yang mengembalikan string: | SubCent: 0.0099999 |

Untuk mengakomodasi nilai yang memiliki bagian integer, berikut ini adalah permulaan.

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

Yang mengembalikan string:

valFmt4 = "567890.00999999"

0

saya menggunakan fungsi ini untuk memotong nilai setelah desimal dalam variabel string

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
Meskipun kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang bagaimana dan / atau mengapa kode ini memecahkan masalah akan meningkatkan nilai jangka panjang jawaban.
Nic3500

0

Inilah yang saya lakukan:

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

mengurangi dua angka yang dimasukkan tanpa membulatkan ke atas atau ke bawah desimal. karena solusi lain tidak berhasil untuk saya. tidak tahu apakah ini akan berhasil untuk orang lain, saya hanya ingin membagikan ini :) Semoga berhasil bagi mereka yang menemukan solusi untuk masalah yang mirip dengan saya. Terima kasih

PS: Saya seorang pemula jadi silakan tunjukkan sesuatu tentang ini. : D ini bagus jika Anda benar-benar berurusan dengan uang, penyebab sen kan? itu hanya memiliki 2 tempat desimal dan pembulatannya adalah tidak, tidak.


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

Sebenarnya Anda menginginkan 3,46 dari 3,4679. Ini hanya representasi karakter, jadi tidak ada hubungannya dengan fungsi matematika, fungsi matematika tidak dimaksudkan untuk melakukan pekerjaan ini. Cukup gunakan kode berikut.

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

Kode di atas dapat dimodifikasi untuk nomor apa pun. Letakkan kode berikut dalam acara klik tombol

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

-2

bagaimana dengan

var i = Math.Truncate(number);var r = i + Math.Truncate((number - i) * 100) / 100;
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.