Temukan jumlah tempat desimal dalam nilai desimal terlepas dari budayanya


91

Saya ingin tahu apakah ada cara yang ringkas dan akurat untuk menarik jumlah tempat desimal dalam nilai desimal (sebagai int) yang akan aman digunakan di berbagai info budaya?

Misalnya:
19.0 harus
menghasilkan 1, 27.5999 harus menghasilkan 4,
19.12 harus mengembalikan 2,
dll.

Saya menulis kueri yang memisahkan string pada suatu titik untuk menemukan tempat desimal:

int priceDecimalPlaces = price.ToString().Split('.').Count() > 1 
                  ? price.ToString().Split('.').ToList().ElementAt(1).Length 
                  : 0;

Tetapi menurut saya ini hanya akan berfungsi di wilayah yang menggunakan '.' sebagai pemisah desimal dan karenanya sangat rapuh di berbagai sistem.


Desimal sesuai judul pertanyaan
Jesse Carter

Bagaimana dengan beberapa pencocokan pola sebelum Split?. Pada dasarnya \ d + (\ D) \ d + di mana \ D mengembalikan pemisah (., Dll)
Anshul

7
Ini bukan pertanyaan tertutup karena mungkin pada blush pertama muncul. Meminta 19.0pengembalian 1adalah detail implementasi terkait penyimpanan internal nilai 19.0. Faktanya adalah bahwa program menyimpan ini sebagai 190×10⁻¹atau 1900×10⁻²atau 19000×10⁻³. Semuanya sama. Fakta bahwa ia menggunakan representasi pertama saat diberi nilai 19.0Mdan ini terpapar saat menggunakan ToStringtanpa penentu format hanyalah kebetulan, dan hal yang menyenangkan. Kecuali itu tidak senang ketika orang mengandalkan eksponen dalam kasus di mana mereka seharusnya tidak melakukannya.
ErikE

Jika Anda ingin jenis yang dapat membawa "jumlah tempat desimal yang digunakan" ketika dibuat, sehingga Anda dipercaya bisa membedakan 19Mdari 19.0Mdari 19.00M, Anda harus membuat kelas baru yang bundel nilai yang mendasari sebagai salah satu properti dan jumlah tempat desimal sebagai properti lain.
ErikE

1
Meskipun kelas Desimal dapat "membedakan" 19m, dari 19.0m dari 19.00m? Angka yang signifikan seperti salah satu kasus penggunaan utamanya. Apa itu 19.0m * 1.0m? Sepertinya mengatakan 19.00m, mungkin C # devs melakukan matematika yang salah: P? Sekali lagi angka yang signifikan adalah hal yang nyata. Jika Anda tidak menyukai angka yang signifikan, Anda mungkin sebaiknya tidak menggunakan kelas Desimal.
Nicholi

Jawaban:


168

Saya menggunakan cara Joe untuk mengatasi masalah ini :)

decimal argument = 123.456m;
int count = BitConverter.GetBytes(decimal.GetBits(argument)[3])[2];

6
Setelah melihat lebih jauh pada ini dan melihatnya beraksi, saya menandainya sebagai jawaban karena menurut pendapat saya ini adalah metode paling ringkas dan elegan untuk mengembalikan tempat desimal yang pernah saya lihat di sini. Akan memberi +1 lagi jika saya bisa: D
Jesse Carter

9
decimalterus menghitung digit setelah koma, itulah mengapa Anda menemukan "masalah" ini, Anda harus mengubah desimal menjadi dua kali lipat dan menjadi desimal lagi untuk memperbaiki: BitConverter.GetBytes (decimal.GetBits ((desimal) (double) argumen) [3]) [ 2];
burning_LEGION

3
Ini tidak berhasil untuk saya. Nilai yang kembali dari SQL adalah 21.17 artinya 4 digit. Tipe data didefinisikan sebagai DECIMAL (12,4) jadi mungkin hanya itu (menggunakan Entity Framework).
PeterX

11
@Nicholi - Tidak, ini sangat buruk karena metode ini mengandalkan penempatan bit yang mendasari desimal - sesuatu yang memiliki banyak cara untuk merepresentasikan bilangan yang sama . Anda tidak akan menguji kelas berdasarkan status bidang privatnya kan?
m.edmondson

14
Tidak yakin apa yang seharusnya elegan atau menyenangkan tentang ini. Ini sama kaburnya dengan yang didapat. Siapa yang tahu apakah itu berhasil dalam semua kasus. Mustahil untuk memastikan.
usr

24

Karena tidak ada jawaban yang diberikan cukup baik untuk angka ajaib "-0.01f" diubah menjadi desimal .. yaitu: GetDecimal((decimal)-0.01f);
Saya hanya dapat berasumsi bahwa virus kentut pikiran kolosal menyerang semua orang 3 tahun yang lalu :)
Inilah yang tampaknya berhasil implementasi untuk masalah jahat dan mengerikan ini, masalah yang sangat rumit menghitung tempat desimal setelah titik - tanpa string, tidak ada budaya, tidak perlu menghitung bit dan tidak perlu membaca forum matematika .. hanya matematika kelas 3 sederhana.

public static class MathDecimals
{
    public static int GetDecimalPlaces(decimal n)
    {
        n = Math.Abs(n); //make sure it is positive.
        n -= (int)n;     //remove the integer part of the number.
        var decimalPlaces = 0;
        while (n > 0)
        {
            decimalPlaces++;
            n *= 10;
            n -= (int)n;
        }
        return decimalPlaces;
    }
}

private static void Main(string[] args)
{
    Console.WriteLine(1/3m); //this is 0.3333333333333333333333333333
    Console.WriteLine(1/3f); //this is 0.3333333

    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.0m));                  //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(1/3m));                  //28
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)(1 / 3f)));     //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-1.123m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces(43.12345m));             //5
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0));                     //0
    Console.WriteLine(MathDecimals.GetDecimalPlaces(0.01m));                 //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces(-0.001m));               //3
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.00000001f)); //8
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.0001234f));   //7
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)0.01f));        //2
    Console.WriteLine(MathDecimals.GetDecimalPlaces((decimal)-0.01f));       //2
}

6
Solusi Anda akan gagal untuk sejumlah kasus yang berisi nol di belakangnya dan digitnya SIGNIFIKAN. 0,01m * 2,0m = 0,020m. Seharusnya 3 digit, metode Anda mengembalikan 2. Anda tampaknya salah memahami apa yang terjadi ketika Anda mengubah 0,01f ke Desimal. Titik mengambang pada dasarnya tidak tepat, sehingga nilai biner aktual yang disimpan untuk 0,01f tidaklah tepat. Saat Anda mentransmisikan ke Desimal (notasi angka yang sangat terstruktur), Anda mungkin tidak mendapatkan 0,01 m (sebenarnya Anda mendapatkan 0,010 m). Solusi GetBits sebenarnya benar untuk mendapatkan jumlah digit dari Desimal. Bagaimana Anda mengubah ke Desimal adalah kuncinya.
Nicholi

2
@Nicholi 0,020m sama dengan 0,02m .. angka nol di belakang tidak signifikan. OP menanyakan "terlepas dari budaya" dalam judul dan bahkan lebih spesifik menjelaskan "..yang akan aman digunakan di berbagai info budaya .." - oleh karena itu saya pikir jawaban saya tetap lebih valid daripada yang lain.
GY

6
OP mengatakan secara khusus: "19.0 harus mengembalikan 1". Kode ini gagal pada kasus itu.
daniloquio

9
mungkin ini bukan yang diinginkan OP, tetapi jawaban ini lebih sesuai dengan kebutuhan saya daripada jawaban atas pertanyaan ini
Arsen Zahray

2
Dua baris pertama harus diganti dengan n = n % 1; if (n < 0) n = -n;karena nilai yang lebih besar dari int.MaxValueakan menyebabkan OverflowException, misalnya 2147483648.12345.
Kebencian

23

Saya mungkin akan menggunakan solusi dalam jawaban @ fixagon .

Namun, meskipun struct Desimal tidak memiliki metode untuk mendapatkan jumlah desimal, Anda dapat memanggil Decimal.GetBits untuk mengekstrak representasi biner, lalu menggunakan nilai dan skala integer untuk menghitung jumlah desimal.

Ini mungkin akan lebih cepat daripada memformat sebagai string, meskipun Anda harus memproses banyak sekali desimal untuk melihat perbedaannya.

Saya akan meninggalkan implementasinya sebagai latihan.


1
Terima kasih @Joe, itu cara yang sangat rapi untuk mendekatinya. Bergantung pada bagaimana perasaan bos saya tentang menggunakan solusi lain, saya akan melihat penerapan ide Anda. Pasti akan menjadi latihan yang menyenangkan :)
Jesse Carter

17

Salah satu solusi terbaik untuk menemukan jumlah digit setelah koma desimal ditunjukkan dalam posting burning_LEGION .

Di sini saya menggunakan bagian-bagian dari artikel forum STSdb: Jumlah digit setelah titik desimal .

Di MSDN kita bisa membaca penjelasan berikut:

"Angka desimal adalah nilai floating-point yang terdiri dari tanda, nilai numerik di mana setiap digit dalam nilai berkisar dari 0 hingga 9, dan faktor skala yang menunjukkan posisi titik desimal mengambang yang memisahkan integral dan pecahan bagian dari nilai numerik. "

Dan juga:

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

Pada tingkat internal, nilai desimal diwakili oleh empat nilai integer.

Representasi internal desimal

Ada fungsi GetBits yang tersedia untuk umum untuk mendapatkan representasi internal. Fungsi mengembalikan larik int []:

[__DynamicallyInvokable] 
public static int[] GetBits(decimal d)
{
    return new int[] { d.lo, d.mid, d.hi, d.flags };
}

Elemen keempat dari array yang dikembalikan berisi faktor skala dan tanda. Dan seperti yang dikatakan MSDN, faktor penskalaan secara implisit adalah angka 10, dipangkatkan ke eksponen yang berkisar dari 0 hingga 28. Inilah yang kita butuhkan.

Jadi, berdasarkan semua investigasi di atas, kami dapat membangun metode kami:

private const int SIGN_MASK = ~Int32.MinValue;

public static int GetDigits4(decimal value)
{
    return (Decimal.GetBits(value)[3] & SIGN_MASK) >> 16;
}

Di sini SIGN_MASK digunakan untuk mengabaikan tanda. Setelah logis dan kami juga menggeser hasilnya dengan 16 bit ke kanan untuk menerima faktor skala yang sebenarnya. Nilai ini, akhirnya, menunjukkan jumlah digit setelah koma desimal.

Perhatikan bahwa di sini MSDN juga mengatakan faktor penskalaan juga mempertahankan angka nol di belakangnya dalam bilangan Desimal. Nol tertinggal tidak mempengaruhi nilai bilangan desimal dalam operasi aritmatika atau perbandingan. Namun, nol di belakang mungkin akan ditampilkan oleh metode ToString jika string format yang sesuai diterapkan.

Solusi ini sepertinya yang terbaik, tapi tunggu, masih ada lagi. Dengan mengakses metode privat di C # kita bisa menggunakan ekspresi untuk membangun akses langsung ke bidang bendera dan menghindari membangun larik int:

public delegate int GetDigitsDelegate(ref Decimal value);

public class DecimalHelper
{
    public static readonly DecimalHelper Instance = new DecimalHelper();

    public readonly GetDigitsDelegate GetDigits;
    public readonly Expression<GetDigitsDelegate> GetDigitsLambda;

    public DecimalHelper()
    {
        GetDigitsLambda = CreateGetDigitsMethod();
        GetDigits = GetDigitsLambda.Compile();
    }

    private Expression<GetDigitsDelegate> CreateGetDigitsMethod()
    {
        var value = Expression.Parameter(typeof(Decimal).MakeByRefType(), "value");

        var digits = Expression.RightShift(
            Expression.And(Expression.Field(value, "flags"), Expression.Constant(~Int32.MinValue, typeof(int))), 
            Expression.Constant(16, typeof(int)));

        //return (value.flags & ~Int32.MinValue) >> 16

        return Expression.Lambda<GetDigitsDelegate>(digits, value);
    }
}

Kode yang dikompilasi ini ditugaskan ke bidang GetDigits. Perhatikan bahwa fungsi tersebut menerima nilai desimal sebagai ref, jadi tidak ada penyalinan aktual yang dilakukan - hanya referensi ke nilai tersebut. Menggunakan fungsi GetDigits dari DecimalHelper itu mudah:

decimal value = 3.14159m;
int digits = DecimalHelper.Instance.GetDigits(ref value);

Ini adalah metode tercepat yang mungkin untuk mendapatkan jumlah digit setelah koma desimal untuk nilai desimal.


3
desimal r = (desimal) -0.01f; dan solusi gagal. (pada semua jawaban yang saya lihat di halaman ini ...) :)
GY

5
CATATAN: Tentang keseluruhan (Desimal) 0,01f, Anda mentransmisikan floating point, yang secara inheren TIDAK TEPAT, ke sesuatu yang sangat terstruktur seperti Desimal. Lihatlah keluaran Console.WriteLine ((Decimal) 0.01f). Desimal yang dibentuk di cor SEBENARNYA memiliki 3 digit, itulah sebabnya semua solusi yang diberikan mengatakan 3 bukan 2. Semuanya benar-benar berfungsi seperti yang diharapkan, "masalahnya" adalah Anda mengharapkan nilai floating point menjadi tepat. Mereka tidak.
Nicholi

@Nicholi Poin Anda gagal ketika Anda menyadarinya 0.01dan 0.010adalah angka yang sama persis . Selain itu, gagasan bahwa tipe data numerik memiliki semantik "jumlah digit yang digunakan" yang dapat diandalkan sepenuhnya salah (jangan disamakan dengan "jumlah digit yang diperbolehkan". Jangan membingungkan penyajian (tampilan nilai angka dalam basis tertentu, misalnya, perluasan desimal dari nilai yang ditunjukkan oleh ekspansi biner 111) dengan nilai yang mendasarinya! Untuk mengulangi, angka bukanlah digit, juga bukan terdiri dari digit .
ErikE

6
Keduanya setara nilainya, tetapi tidak dalam angka yang signifikan. Yang merupakan kasus penggunaan besar kelas Desimal. Jika saya bertanya berapa digit dalam 0,010 m literal, apakah Anda hanya menjawab 2? Meskipun sejumlah guru matematika / sains di seluruh dunia akan memberi tahu Anda bahwa angka 0 terakhir itu signifikan? Masalah yang kami maksud dimanifestasikan dengan mentransmisikan dari floating point ke Desimal. Bukan penggunaan GetBits itu sendiri, yang berfungsi persis seperti yang didokumentasikan. Jika Anda tidak peduli dengan angka yang signifikan, maka ya Anda memiliki masalah dan kemungkinan besar sebaiknya tidak menggunakan kelas Desimal sejak awal.
Nicholi

1
@theberserker Sejauh yang saya ingat, tidak ada tangkapan - harus bekerja dua arah.
Kristiyan Dimitrov

13

Mengandalkan representasi internal desimal tidaklah keren.

Bagaimana dengan ini:

    int CountDecimalDigits(decimal n)
    {
        return n.ToString(System.Globalization.CultureInfo.InvariantCulture)
                //.TrimEnd('0') uncomment if you don't want to count trailing zeroes
                .SkipWhile(c => c != '.')
                .Skip(1)
                .Count();
    }

11

Anda dapat menggunakan InvariantCulture

string priceSameInAllCultures = price.ToString(System.Globalization.CultureInfo.InvariantCulture);

kemungkinan lain adalah melakukan sesuatu seperti itu:

private int GetDecimals(decimal d, int i = 0)
{
    decimal multiplied = (decimal)((double)d * Math.Pow(10, i));
    if (Math.Round(multiplied) == multiplied)
        return i;
    return GetDecimals(d, i+1);
}

Bagaimana ini membantu saya menemukan jumlah tempat desimal dalam desimal? Saya tidak punya masalah untuk mengubah desimal menjadi string yang bagus dalam budaya apapun. Sesuai pertanyaan saya mencoba menemukan jumlah tempat desimal yang ada di desimal
Jesse Carter

@JesseCarter: Artinya Anda selalu bisa berpisah ..
Austin Salonen

@AustinSenen benarkah? Saya tidak menyadari bahwa menggunakan InvariantCulture akan memberlakukan penggunaan titik sebagai pemisah desimal
Jesse Carter

seperti yang Anda lakukan sebelumnya, itu akan selalu membuat harga menjadi string dengan. sebagai pemisah desimal. tapi ini bukan cara yang paling elegan menurut saya ...
fixagon


8

Kebanyakan orang di sini tampaknya tidak menyadari bahwa desimal menganggap nol di belakang sebagai hal yang penting untuk penyimpanan dan pencetakan.

Jadi 0,1m, 0,10m dan 0,100m dapat dibandingkan sebagai sama, mereka disimpan secara berbeda (sebagai nilai / skala 1/1, 10/2 dan 100/3, masing-masing), dan akan dicetak sebagai 0,1, 0,10 dan 0,100, masing-masing , oleh ToString().

Dengan demikian, solusi yang melaporkan "presisi terlalu tinggi" sebenarnya melaporkan presisi yang benar , sesuai decimalketentuan.

Selain itu, solusi berbasis matematika (seperti mengalikan dengan pangkat 10) kemungkinan akan sangat lambat (desimal ~ 40x lebih lambat dari dua kali lipat untuk aritmatika, dan Anda juga tidak ingin mencampurkan dalam floating-point karena hal itu kemungkinan akan menyebabkan ketidaktepatan ). Demikian pula, mentransmisikan ke intatau longsebagai alat pemotongan rawan kesalahan ( decimalmemiliki rentang yang jauh lebih besar daripada keduanya - ini didasarkan pada bilangan bulat 96-bit).

Meskipun tidak elegan seperti itu, berikut ini kemungkinan akan menjadi salah satu cara tercepat untuk mendapatkan ketepatan (bila didefinisikan sebagai "tempat desimal tidak termasuk nol di belakangnya"):

public static int PrecisionOf(decimal d) {
  var text = d.ToString(System.Globalization.CultureInfo.InvariantCulture).TrimEnd('0');
  var decpoint = text.IndexOf('.');
  if (decpoint < 0)
    return 0;
  return text.Length - decpoint - 1;
}

Budaya invarian menjamin '.' sebagai titik desimal, nol di belakangnya dipangkas, dan kemudian ini hanya masalah melihat berapa banyak posisi yang tersisa setelah titik desimal (jika ada satu).

Edit: mengubah tipe pengembalian menjadi int


1
@mvmorten Tidak yakin mengapa Anda merasa perlu mengubah tipe pengembalian ke int; byte lebih akurat mewakili nilai yang dikembalikan: unsigned and small range (0-29, dalam praktiknya).
Zastai

1
Saya setuju bahwa solusi berulang dan berbasis kalkulasi lambat (selain tidak memperhitungkan nol tertinggal). Namun, mengalokasikan string untuk ini dan mengoperasikannya juga bukan hal yang paling berkinerja untuk dilakukan, terutama dalam konteks kinerja kritis dan dengan GC yang lambat. Mengakses skala melalui logika penunjuk jauh lebih cepat dan alokasi gratis.
Martin Tilo Schmitz

Ya, mendapatkan timbangan dapat dilakukan dengan jauh lebih efisien - tetapi itu akan mencakup angka nol yang tertinggal. Dan menghapusnya membutuhkan melakukan aritmatika pada bagian integer.
Zastai

6

Dan inilah cara lain, gunakan tipe SqlDecimal yang memiliki properti skala dengan jumlah digit di kanan desimal. Transmisikan nilai desimal Anda ke SqlDecimal dan kemudian akses Scale.

((SqlDecimal)(decimal)yourValue).Scale

1
Melihat kode referensi Microsoft , mentransmisikan ke SqlDecimal secara internal menggunakan GetBytessehingga mengalokasikan larik Byte alih-alih mengakses byte dalam konteks yang tidak aman. Bahkan ada catatan dan kode komentar di kode referensi, yang menyatakan itu dan bagaimana mereka bisa melakukannya. Mengapa mereka tidak melakukannya adalah misteri bagi saya. Saya akan menghindari ini dan mengakses bit skala secara langsung daripada menyembunyikan Aloc GC dalam pemeran ini, karena tidak terlalu jelas apa yang dilakukannya di bawah tenda.
Martin Tilo Schmitz

4

Sejauh ini, hampir semua solusi yang terdaftar mengalokasikan Memori GC, yang merupakan cara C # untuk melakukan sesuatu tetapi jauh dari ideal dalam lingkungan kinerja kritis. (Yang tidak mengalokasikan loop penggunaan dan juga tidak mempertimbangkan nol di belakangnya.)

Jadi untuk menghindari GC Allocs, Anda cukup mengakses bit skala dalam konteks yang tidak aman. Itu mungkin terdengar rapuh tetapi sesuai sumber referensi Microsoft , tata letak struct desimal adalah Berurutan dan bahkan memiliki komentar di sana, bukan untuk mengubah urutan bidang:

    // NOTE: Do not change the order in which these fields are declared. The
    // native methods in this class rely on this particular order.
    private int flags;
    private int hi;
    private int lo;
    private int mid;

Seperti yang Anda lihat, int pertama di sini adalah bidang bendera. Dari dokumentasi dan seperti yang disebutkan dalam komentar lain di sini, kita tahu bahwa hanya bit dari 16-24 yang menyandikan skala dan bahwa kita perlu menghindari bit ke-31 yang menyandikan tanda. Karena int berukuran 4 byte, kita dapat melakukan ini dengan aman:

internal static class DecimalExtensions
{
  public static byte GetScale(this decimal value)
  {
    unsafe
    {
      byte* v = (byte*)&value;
      return v[2];
    }
  }
}

Ini harus menjadi solusi yang paling berkinerja karena tidak ada alokasi GC dari larik byte atau konversi ToString. Saya telah mengujinya terhadap .Net 4.x dan .Net 3.5 di Unity 2019.1. Jika ada versi yang gagal, beri tahu saya.

Edit:

Terima kasih kepada @Zastai karena telah mengingatkan saya tentang kemungkinan menggunakan tata letak struct eksplisit untuk secara praktis mencapai logika penunjuk yang sama di luar kode yang tidak aman:

[StructLayout(LayoutKind.Explicit)]
public struct DecimalHelper
{
    const byte k_SignBit = 1 << 7;

    [FieldOffset(0)]
    public decimal Value;

    [FieldOffset(0)]
    public readonly uint Flags;
    [FieldOffset(0)]
    public readonly ushort Reserved;
    [FieldOffset(2)]
    byte m_Scale;
    public byte Scale
    {
        get
        {
            return m_Scale;
        }
        set
        {
            if(value > 28)
                throw new System.ArgumentOutOfRangeException("value", "Scale can't be bigger than 28!")
            m_Scale = value;
        }
    }
    [FieldOffset(3)]
    byte m_SignByte;
    public int Sign
    {
        get
        {
            return m_SignByte > 0 ? -1 : 1;
        }
    }
    public bool Positive
    {
        get
        {
            return (m_SignByte & k_SignBit) > 0 ;
        }
        set
        {
            m_SignByte = value ? (byte)0 : k_SignBit;
        }
    }
    [FieldOffset(4)]
    public uint Hi;
    [FieldOffset(8)]
    public uint Lo;
    [FieldOffset(12)]
    public uint Mid;

    public DecimalHelper(decimal value) : this()
    {
        Value = value;
    }

    public static implicit operator DecimalHelper(decimal value)
    {
        return new DecimalHelper(value);
    }

    public static implicit operator decimal(DecimalHelper value)
    {
        return value.Value;
    }
}

Untuk mengatasi masalah asli, Anda dapat menghapus semua bidang selain Valuedan Scalemungkin berguna bagi seseorang untuk memiliki semuanya.


1
Anda juga dapat menghindari kode yang tidak aman dengan mengkodekan struct Anda sendiri dengan tata letak explict - letakkan desimal di posisi 0, lalu byte / int di lokasi yang sesuai. Sesuatu seperti:[StructLayout(LayoutKind.Explicit)] public struct DecimalHelper { [FieldOffset(0)] public decimal Value; [FieldOffset(0)] public uint Flags; [FieldOffset(0)] public ushort Reserved; [FieldOffset(2)] public byte Scale; [FieldOffset(3)] public DecimalSign Sign; [FieldOffset(4)] public uint ValuePart1; [FieldOffset(8)] public ulong ValuePart2; }
Zastai

Terima kasih @Zastai, poin yang bagus. Saya telah memasukkan pendekatan itu juga. :)
Martin Tilo Schmitz

1
Satu hal yang perlu diperhatikan: mengatur skala di luar kisaran 0-28 menyebabkan kerusakan. ToString () cenderung berfungsi, tetapi aritmatika gagal.
Zastai

Terima kasih lagi @Zastai, saya telah menambahkan cek untuk itu :)
Martin Tilo Schmitz

Hal lain: beberapa orang di sini tidak ingin memperhitungkan angka nol desimal. Jika Anda mendefinisikan a, const decimal Foo = 1.0000000000000000000000000000m;maka membagi desimal dengan yang akan mengubah skala ke skala serendah mungkin (yaitu tidak lagi menyertakan nol desimal di belakangnya). Saya belum membandingkan ini untuk melihat apakah itu lebih cepat daripada pendekatan berbasis string yang saya sarankan di tempat lain.
Zastai

2

Saya menulis metode kecil singkat kemarin yang juga mengembalikan jumlah tempat desimal tanpa harus bergantung pada pemisahan string atau budaya yang ideal:

public int GetDecimalPlaces(decimal decimalNumber) { // 
try {
    // PRESERVE:BEGIN
        int decimalPlaces = 1;
        decimal powers = 10.0m;
        if (decimalNumber > 0.0m) {
            while ((decimalNumber * powers) % 1 != 0.0m) {
                powers *= 10.0m;
                ++decimalPlaces;
            }
        }
return decimalPlaces;

@ fix-like-codings mirip dengan jawaban kedua Anda meskipun untuk sesuatu seperti ini saya lebih menyukai pendekatan berulang daripada menggunakan rekursi
Jesse Carter

Posting asli menyatakan bahwa: 19.0 should return 1. Solusi ini akan selalu mengasumsikan jumlah minimal 1 tempat desimal dan mengabaikan nol di belakangnya. desimal dapat memiliki itu karena menggunakan faktor skala. Faktor skala dapat diakses seperti pada byte 16-24 dari elemen dengan indeks 3 dalam larik yang didapat dari Decimal.GetBytes()atau dengan menggunakan logika penunjuk.
Martin Tilo Schmitz

2

Saya menggunakan sesuatu yang sangat mirip dengan jawaban Clement:

private int GetSignificantDecimalPlaces(decimal number, bool trimTrailingZeros = true)
{
  string stemp = Convert.ToString(number);

  if (trimTrailingZeros)
    stemp = stemp.TrimEnd('0');

  return stemp.Length - 1 - stemp.IndexOf(
         Application.CurrentCulture.NumberFormat.NumberDecimalSeparator);
}

Ingatlah untuk menggunakan System.Windows.Forms untuk mendapatkan akses ke Application.CurrentCulture


1

Anda dapat mencoba:

int priceDecimalPlaces =
        price.ToString(System.Globalization.CultureInfo.InvariantCulture)
              .Split('.')[1].Length;

7
Bukankah ini akan gagal jika desimal adalah bilangan bulat? [1]
Silvermind

1

Saya menggunakan mekanisme berikut dalam kode saya

  public static int GetDecimalLength(string tempValue)
    {
        int decimalLength = 0;
        if (tempValue.Contains('.') || tempValue.Contains(','))
        {
            char[] separator = new char[] { '.', ',' };
            string[] tempstring = tempValue.Split(separator);

            decimalLength = tempstring[1].Length;
        }
        return decimalLength;
    }

masukan desimal = 3,376; var instring = input.ToString ();

panggil GetDecimalLength (instring)


1
Ini tidak bekerja untuk saya karena representasi ToString () dari nilai desmial menambahkan "00" ke akhir data saya - Saya menggunakan tipe data Desimal (12,4) dari SQL Server.
PeterX

Dapatkah Anda mentransmisikan data Anda ke desimal tipe c # dan mencoba solusinya. Bagi saya ketika saya menggunakan Tostring () pada c # nilai desimal saya tidak pernah melihat "00".
Srikanth

1

Menggunakan rekursi, Anda dapat melakukan:

private int GetDecimals(decimal n, int decimals = 0)  
{  
    return n % 1 != 0 ? GetDecimals(n * 10, decimals + 1) : decimals;  
}

Posting asli menyatakan bahwa: 19.0 should return 1. Solusi ini akan mengabaikan nol di belakangnya. desimal dapat memiliki itu karena menggunakan faktor skala. Faktor skala dapat diakses seperti pada byte 16-24 dari elemen dengan indeks 3 dalam Decimal.GetBytes()larik atau dengan menggunakan logika penunjuk.
Martin Tilo Schmitz

1
string number = "123.456789"; // Convert to string
int length = number.Substring(number.IndexOf(".") + 1).Length;  // 6

0

Saya sarankan menggunakan metode ini:

    public static int GetNumberOfDecimalPlaces(decimal value, int maxNumber)
    {
        if (maxNumber == 0)
            return 0;

        if (maxNumber > 28)
            maxNumber = 28;

        bool isEqual = false;
        int placeCount = maxNumber;
        while (placeCount > 0)
        {
            decimal vl = Math.Round(value, placeCount - 1);
            decimal vh = Math.Round(value, placeCount);
            isEqual = (vl == vh);

            if (isEqual == false)
                break;

            placeCount--;
        }
        return Math.Min(placeCount, maxNumber); 
    }

0

Sebagai metode ekstensi desimal yang memperhitungkan:

  • Perbedaan budaya
  • Angka utuh
  • Angka negatif
  • Nol set tertinggal di tempat desimal (misalnya 1,2300M akan menghasilkan 2 bukan 4)
public static class DecimalExtensions
{
    public static int GetNumberDecimalPlaces(this decimal source)
    {
        var parts = source.ToString(CultureInfo.InvariantCulture).Split('.');

        if (parts.Length < 2)
            return 0;

        return parts[1].TrimEnd('0').Length;
    }
}
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.