Kültürden bağımsız olarak ondalık değerdeki ondalık basamakların sayısını bulun


91

Farklı kültür bilgilerinde kullanmak için güvenli olacak ondalık basamakların sayısını ondalık bir değerde (int olarak) çıkarmanın kısa ve doğru bir yolu olup olmadığını merak ediyorum.

Örneğin:
19.0, 1,
27.5999, 4,
19.12, 2,
vb. Döndürmelidir .

Ondalık basamakları bulmak için bir noktaya bölünmüş bir dize yapan bir sorgu yazdım:

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

Ama bana öyle geliyor ki, bu sadece '.' Kullanan bölgelerde işe yarayacak. ondalık ayırıcı olarak kullanılır ve bu nedenle farklı sistemler arasında çok kırılgandır.


Soru başlığına göre bir ondalık
Jesse Carter

Split'ten önce bazı desen eşleştirmeye ne dersiniz? Temel olarak \ d + (\ D) \ d + burada \ D ayırıcıyı (., Vb.)
Döndürür

7
İlk bakışta görünebileceği için bu kapalı uçlu bir soru değildir. İstenen 19.0getiri 1bir olan uygulama ayrıntı değerinin dahili depolama konusunda 19.0. Gerçek şu ki, programın bunu 190×10⁻¹veya 1900×10⁻²veya olarak saklaması tamamen meşru 19000×10⁻³. Bunların hepsi eşittir. Bir değeri verildiğinde ilk gösterimi 19.0Mkullanması ToStringve bir biçim belirticisi olmadan kullanıldığında bunun ortaya çıkması sadece bir tesadüf ve mutlu bir şeydir. İnsanların yapmamaları gereken durumlarda üslere güvenmeleri dışında.
ErikE

Eğer güvenilir bir ayırt böylece, bu oluşturulduğunda "ondalık basamak sayısı kullanılır" taşıyabileceği bir tür istiyorsanız 19Mdan 19.0Mitibaren 19.00M, bir özelliği olarak temel değeri ve numarayı ait paketleyen yeni bir sınıf oluşturmanız gerekir başka bir özellik olarak ondalık basamaklar.
ErikE

1
Ondalık sınıfı 19m'yi 19.00m'den 19.0m'den “ayırabilse de”? Önemli rakamlar, ana kullanım durumlarından biri gibidir. 19.0m * 1.0m nedir? 19.00m diyor gibi görünüyor, belki de C # geliştiricileri matematiği yanlış yapıyor: P? Yine önemli rakamlar gerçektir. Önemli basamakları sevmiyorsanız, muhtemelen Decimal sınıfını kullanmamalısınız.
Nicholi

Yanıtlar:


169

Bu sorunu çözmek için Joe'nun yöntemini kullandım :)

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

6
Buna daha fazla baktıktan ve eylemde gördükten sonra, yanıt olarak işaretliyorum çünkü bu bence burada gördüğüm ondalık basamakları geri döndürmenin en özlü ve zarif yöntemi. Yapabilseydim tekrar +1 olurdu: D
Jesse Carter

9
decimalkomadan sonra sayı rakamını tutar, bu yüzden bu "sorunu" bulursunuz, düzeltmek için ondalıktan ikiye ve tekrar ondalık sayıya çevirmeniz gerekir: BitConverter.GetBytes (ondalık.GetBits ((ondalık) (çift) argüman) [3]) [ 2];
burning_LEGION

3
Bu benim için işe yaramadı. SQL'den gelen değer 21.17'dir, 4 hane diyor. Veri türü DECIMAL (12,4) olarak tanımlanır, bu yüzden belki de budur (Entity Framework kullanarak).
PeterX

11
@Nicholi - Hayır, bu son derece kötü çünkü yöntem ondalık basamağın temelindeki bitlerin yerleştirilmesine dayanıyor - aynı sayıyı temsil etmenin birçok yolu olan bir şey . Bir sınıfı özel alanlarının durumuna göre test etmezsiniz, değil mi?
m.edmondson

14
Bu konuda neyin zarif ya da hoş olacağından emin değilim. Bu, olabildiğince karmaşık hale geliyor. Her durumda işe yarayıp yaramadığını kim bilebilir. Emin olmak imkansız.
usr

24

Verilen cevaplardan hiçbiri, ondalık sayıya dönüştürülen "-0.01f" sihirli sayısı için yeterince iyi olmadığından .. yani: GetDecimal((decimal)-0.01f);
Sadece 3 yıl önce herkese muazzam bir akıl-osuruğu virüsünün saldırdığını varsayabilirim :)
İşte çalışıyor gibi görünüyor bu kötü ve canavarca problemin uygulanması, noktadan sonraki ondalık basamakları saymanın çok karmaşık problemi - dizeler yok, kültür yok, bitleri saymaya gerek yok ve matematik forumlarını okumaya gerek yok .. sadece basit 3. sınıf matematik.

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
Çözümünüz, takip eden sıfırlar içeren ve rakamların ÖNEMLİ olduğu birkaç durum için başarısız olacaktır. 0.01m * 2.0m = 0.020m. 3 basamaklı olmalıdır, yönteminiz 2 değerini döndürür. 0.01f'yi Ondalık sayıya çevirdiğinizde ne olduğunu yanlış anlıyorsunuz. Kayan noktalar doğası gereği kesin değildir, bu nedenle 0.01f için saklanan gerçek ikili değer kesin değildir. Ondalık sayıya çevirdiğinizde (çok yapılandırılmış bir sayı gösterimi) 0,01 m alamayabilirsiniz (aslında 0,010 m elde edersiniz). GetBits çözümü, bir Decimal'den basamak sayısını almak için aslında doğrudur. Decimal'e nasıl dönüştürdüğünüz anahtardır.
Nicholi

2
@Nicholi 0.020m, 0.02m'ye eşittir .. sondaki sıfırlar önemli değildir. OP başlıkta "kültür ne olursa olsun" soruyor ve daha da spesifik olarak "..farklı kültür bilgilerinde kullanılması güvenli olacak .." açıklıyor - bu nedenle cevabımın diğerlerinden daha da geçerli olduğunu düşünüyorum.
GY

6
OP özellikle: "19.0, 1 döndürmeli" dedi. Bu kod, bu durumda başarısız olur.
daniloquio

9
belki bu OP istediğim şey değil, ama bu bir çözüm daha iyi bu soruya üst cevabı daha benim ihtiyaçlarına uygun
Arsen Zahray

2
İlk iki satır ile değiştirilmesi gereken n = n % 1; if (n < 0) n = -n;daha büyük bir değer için int.MaxValuebir neden olur OverflowException, örneğin 2147483648.12345.
Nefret

24

Çözümü muhtemelen @ fixagon'un cevabında kullanırdım .

Ancak, Ondalık yapının ondalık sayıları almak için bir yöntemi olmasa da , ikili gösterimi çıkarmak için Decimal.GetBits'i çağırabilir , ardından ondalıkların sayısını hesaplamak için tamsayı değerini ve ölçeği kullanabilirsiniz.

Bu, muhtemelen bir dize olarak biçimlendirmekten daha hızlı olacaktır, ancak farkı görmek için çok sayıda ondalık sayı işliyor olmanız gerekir.

Uygulamayı bir alıştırma olarak bırakacağım.


1
Teşekkürler @ Joe, bu ona yaklaşmanın gerçekten düzgün bir yolu. Patronumun diğer çözümü kullanma konusunda ne hissettiğine bağlı olarak fikrinizi uygulamaya bir göz atacağım. Kesinlikle eğlenceli bir egzersiz olurdu :)
Jesse Carter

17

Ondalık noktadan sonraki basamak sayısını bulmanın en iyi çözümlerinden biri, yanan_LEGION'un gönderisinde gösterilmiştir .

Burada bir STSdb ​​forum makalesinden bölümler kullanıyorum: Ondalık noktadan sonraki hane sayısı .

MSDN'de aşağıdaki açıklamayı okuyabiliriz:

"Ondalık sayı, bir işaretten, değerdeki her basamağın 0 ile 9 arasında değiştiği bir sayısal değerden ve integral ile kesirliyi ayıran kayan bir ondalık noktanın konumunu gösteren bir ölçekleme faktöründen oluşan bir kayan nokta değeridir. sayısal değerin bölümleri. "

Ve ayrıca:

"Ondalık değerin ikili gösterimi, 1 bitlik bir işaret, 96 bitlik bir tam sayı ve 96 bitlik tamsayıyı bölmek ve bunun hangi bölümünün ondalık kesir olduğunu belirlemek için kullanılan bir ölçekleme faktöründen oluşur. Ölçeklendirme faktörü şu şekildedir: örtük olarak 10 sayısı, 0 ile 28 arasında bir üsse yükseltildi. "

Dahili düzeyde ondalık değer dört tam sayı değeriyle temsil edilir.

Ondalık dahili gösterim

Dahili gösterimi elde etmek için halka açık bir GetBits işlevi vardır. İşlev bir int [] dizisi döndürür:

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

Döndürülen dizinin dördüncü öğesi bir ölçek faktörü ve bir işaret içerir. Ve MSDN'nin dediği gibi, ölçeklendirme faktörü örtük olarak 10 sayısıdır ve 0 ile 28 arasında değişen bir üsse yükseltilir. Tam da ihtiyacımız olan şey budur.

Böylece, yukarıdaki tüm araştırmalara dayanarak yöntemimizi oluşturabiliriz:

private const int SIGN_MASK = ~Int32.MinValue;

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

Burada işareti yok saymak için SIGN_MASK kullanılır. Mantıksal sonra ve biz de gerçek ölçek faktörünü almak için sonucu 16 bit sağa kaydırdık. Son olarak bu değer, ondalık noktadan sonraki basamak sayısını gösterir.

Burada MSDN'nin ayrıca ölçeklendirme faktörünün de ondalık sayıdaki sondaki sıfırları koruduğunu söylediğine dikkat edin. Sondaki sıfırlar, aritmetik veya karşılaştırma işlemlerinde Ondalık sayının değerini etkilemez. Ancak, uygun bir biçim dizesi uygulanırsa, sondaki sıfırlar ToString yöntemi tarafından ortaya çıkarılabilir.

Bu çözümler en iyisine benziyor, ama bekleyin, dahası var. By C # özel yöntemler erişen biz bayraklar alanına doğrudan erişim oluşturmak ve int dizi inşa önlemek için ifadeleri kullanabilirsiniz:

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

Derlenen bu kod GetDigits alanına atanır. Fonksiyonun ondalık değeri ref olarak aldığına dikkat edin, bu nedenle gerçek bir kopyalama gerçekleştirilmez - sadece değere bir referans. DecimalHelper'daki GetDigits işlevini kullanmak kolaydır:

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

Bu, ondalık değerler için ondalık noktadan sonra basamak sayısını elde etmenin mümkün olan en hızlı yöntemidir.


3
ondalık r = (ondalık) -0.01f; ve çözüm başarısız olur. (bu sayfada gördüğüm tüm cevaplarda ...) :)
GY

5
NOT: Tam (Ondalık) 0.01f şeyiyle ilgili olarak, ondalık gibi çok yapılandırılmış bir şeye, doğası gereği KESİN OLMAYAN bir kayan nokta atıyorsunuz. Console.WriteLine ((Ondalık) 0.01f) çıktısına bir göz atın. Dökümde oluşturulan Ondalık GERÇEKTEN 3 haneye sahiptir, bu nedenle sağlanan tüm çözümler 2 yerine 3 diyor. Her şey aslında beklendiği gibi çalışıyor, "sorun" kayan nokta değerlerinin tam olmasını beklemenizdir. Onlar değil.
Nicholi

@Nicholi Bunu anladığınızda 0.01ve 0.010tam olarak eşit sayılar olduğunda puanınız başarısız olur . Ayrıca, sayısal bir veri türünün güvenilebilecek bir tür "kullanılan basamak sayısı" anlambilimine sahip olduğu fikri tamamen yanlıştır ("izin verilen basamak sayısı" ile karıştırılmamalıdır. bir sayının belirli bir tabandaki değeri, örneğin, 111) ikili açılımının gösterdiği değerin temel değerle ondalık açılımı!
Tekrarlamak gerekirse

6
Değer olarak eşdeğerdirler, ancak önemli rakamlarda değildirler. Decimal sınıfının büyük kullanım durumu budur. Değişmez 0.010m'de kaç basamak olduğunu sorsam, sadece 2 der misiniz? Dünyanın dört bir yanındaki matematik / fen bilimleri öğretmenlerinin puanları size son 0'ın önemli olduğunu söylese bile? Bahsettiğimiz sorun, kayan noktalardan Ondalık sayıya çevrim yapılarak ortaya çıkar. GetBits'in kullanımı değil, tam olarak belgelendiği gibi yapıyor. Önemli basamakları umursamıyorsanız, evet, bir sorununuz var ve muhtemelen ilk başta Decimal sınıfını kullanmamalısınız.
Nicholi

1
@theberserker Hatırladığım kadarıyla yakalama yoktu - her iki yönde de çalışmalı.
Kristiyan Dimitrov

13

Ondalık sayıların dahili temsiline güvenmek pek hoş değil.

Buna ne dersin:

    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

InvariantCulture kullanabilirsiniz

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

başka bir olasılık da böyle bir şey yapmak olabilir:

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

Bu, ondalıktaki ondalık basamak sayısını bulmama nasıl yardımcı olur? Ondalık değeri herhangi bir kültürde iyi olan bir dizgeye dönüştürmede sorun yaşamıyorum. Soruya göre, ondalık sayıdaki ondalık basamakların sayısını bulmaya çalışıyorum
Jesse Carter

@JesseCarter: Bu, her zaman ayrılabileceğiniz anlamına gelir ..
Austin Salonen

@AustinSalonen Gerçekten mi? InvariantCulture kullanmanın ondalık ayırıcı olarak bir dönemin kullanılmasını zorunlu kılacağının farkında değildim
Jesse Carter

daha önce yaptığınız gibi, fiyatı her zaman bir. ondalık ayırıcı olarak. ama bence en zarif yol değil ...
fixagon


8

Buradaki çoğu insan, ondalığın sondaki sıfırları depolama ve yazdırma için önemli saydığının farkında değil gibi görünüyor.

Yani 0.1m, 0.10m ve 0.100m eşit olarak karşılaştırılabilir, farklı şekilde depolanır (sırasıyla 1/1, 10/2 ve 100/3 değer / ölçek olarak) ve sırasıyla 0.1, 0.10 ve 0.100 olarak yazdırılır. , tarafından ToString().

Bu nedenle, "çok yüksek bir hassasiyet" bildiren çözümler aslında doğru hassasiyeti rapor ediyor .decimal şartlarına .

Ek olarak, matematik tabanlı çözümler (10'un katları ile çarpmak gibi) muhtemelen çok yavaş olacaktır (ondalık, aritmetik için iki kattan ~ 40 kat daha yavaştır ve kayan noktayı da karıştırmak istemezsiniz çünkü bu büyük olasılıkla belirsizliğe neden olacaktır. ). Benzer bir şekilde, döküm intya da longkesilmesi için bir araç olarak (hata eğilimli decimal- bir 96 bit tamsayı etrafına yerleştirilmiştir bunlardan biri çok daha büyük bir aralığına sahiptir).

Bu kadar zarif olmasa da, kesinliği elde etmenin en hızlı yollarından biri muhtemelen aşağıdakilerden biri olacaktır ("sondaki sıfırlar hariç ondalık basamaklar" olarak tanımlandığında):

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

Değişmez kültür, bir '.' ondalık nokta olarak, sondaki sıfırlar kırpılır ve sonra mesele, ondalık noktadan sonra kaç pozisyon kaldığını görmekle ilgilidir (eğer varsa).

Düzenleme: dönüş türü int olarak değiştirildi


1
@mvmorten Dönüş türünü int olarak değiştirmenin neden gerekli olduğunu düşündüğünüzden emin değilim; bayt, döndürülen değeri daha doğru bir şekilde temsil eder: işaretsiz ve küçük aralık (pratikte 0-29).
Zastai

1
Yinelemeli ve hesaplamaya dayalı çözümlerin yavaş olduğuna katılıyorum (sondaki sıfırları hesaba katmamanın yanı sıra). Bununla birlikte, bunun için bir dizge ayırmak ve bunun yerine çalışmak, özellikle performans açısından kritik bağlamlarda ve yavaş bir GC ile yapılacak en performanslı şey değildir. Ölçeğe işaretçi mantığı ile erişmek oldukça hızlı ve tahsis gerektirmeyen bir işlemdir.
Martin Tilo Schmitz

Evet, ölçeği elde etmek çok daha verimli bir şekilde yapılabilir - ancak buna sondaki sıfırlar da dahildir. Ve onları kaldırmak, tamsayı kısmında aritmetik yapmayı gerektirir.
Zastai

6

Ve işte başka bir yol, ondalık basamağın sağındaki basamak sayısıyla bir ölçek özelliğine sahip olan SqlDecimal türünü kullanın. Ondalık değerinizi SqlDecimal'e çevirin ve ardından Ölçek'e erişin.

((SqlDecimal)(decimal)yourValue).Scale

1
Microsoft referans koduna bakıldığında, SqlDecimal'e çevrim dahili olarak kullanır, GetBytesbu yüzden güvenli olmayan bir bağlamda baytlara erişmek yerine Byte dizisini ayırır . Referans kodunda bunu ve bunun yerine nasıl yapabileceklerini belirten bir not ve yorumlanmış kod bile var. Neden olmadıkları benim için bir muamma. Bundan uzak durur ve GC Alloc'u bu dökümde saklamak yerine doğrudan ölçek bitlerine erişirim, çünkü kaputun altında ne yaptığı çok açık değildir.
Martin Tilo Schmitz

4

Şimdiye kadar, listelenen çözümlerin neredeyse tamamı, işleri yapmanın C # yolu olan ancak performans açısından kritik ortamlarda ideal olmaktan uzak olan GC Belleğini tahsis ediyor. (Kullanım döngülerini tahsis etmeyenler ve sondaki sıfırları da hesaba katmayanlar.)

Bu nedenle, GC Allocs'tan kaçınmak için, ölçek bitlerine güvenli olmayan bir bağlamda erişebilirsiniz. Kulağa kırılgan gelebilir, ancak Microsoft'un referans kaynağına göre , ondalık yapı düzeni Sıralı'dır ve alanların sırasını değiştirmemek için orada bir açıklama bile vardır:

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

Gördüğünüz gibi, buradaki ilk int, bayraklar alanıdır. Dokümantasyondan ve buradaki diğer yorumlarda belirtildiği gibi, yalnızca 16-24 arasındaki bitlerin ölçeği kodladığını ve işareti kodlayan 31. bitten kaçınmamız gerektiğini biliyoruz. İnt 4 bayt boyutunda olduğundan, bunu güvenle yapabiliriz:

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

Bayt dizisinin veya ToString dönüşümlerinin GC ayırması olmadığından, bu en yüksek performanslı çözüm olmalıdır. Unity 2019.1'de .Net 4.x ve .Net 3.5'e karşı test ettim. Bunun başarısız olduğu herhangi bir sürüm varsa, lütfen bana bildirin.

Düzenle:

Güvenli olmayan kodun dışında aynı işaretçi mantığını pratik olarak elde etmek için açık bir yapı düzeni kullanma olasılığını hatırlattığı için @ Zastai'ye teşekkürler:

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

Asıl sorunu çözmek için, tüm alanları ortadan kaldırabilirsiniz Valueve Scalebelki de birisinin hepsine sahip olması yararlı olabilir.


1
Ayrıca, kendi yapınızı açık bir düzen ile kodlayarak güvenli olmayan kodlardan da kaçınabilirsiniz - 0 konumuna bir ondalık, ardından uygun konumlara baytlar / inçler koyun. [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; }
Şunun

Teşekkürler @Zastai, iyi nokta. Ben de bu yaklaşımı dahil ettim. :)
Martin Tilo Schmitz

1
Unutulmaması gereken bir şey: ölçeğin 0-28 aralığının dışında ayarlanması kırılmaya neden olur. ToString () çalışma eğilimindedir, ancak aritmetik başarısız olur.
Zastai

Tekrar teşekkürler @Zastai, bunun için bir çek ekledim :)
Martin Tilo Schmitz

Başka bir şey: Buradaki birkaç kişi sondaki ondalık sıfırları hesaba katmak istemedi. Eğer a tanımlarsanız, const decimal Foo = 1.0000000000000000000000000000m;ondalık sayıyı buna bölmek, onu mümkün olan en düşük ölçeğe yeniden ölçeklendirecektir (yani, artık ondalık sıfırları dahil etmeyecektir). Başka bir yerde önerdiğim dizge tabanlı yaklaşımdan daha hızlı olup olmadığını görmek için bunu karşılaştırmadım.
Zastai

2

Dün, ideal olan herhangi bir dize bölünmesine veya kültüre güvenmek zorunda kalmadan ondalık basamakların sayısını da döndüren kısa ve küçük bir yöntem yazdım:

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 ikinci cevabınıza benzer, ancak bunun gibi bir şey için özyineleme kullanmak yerine yinelemeli yaklaşımı tercih ediyorum
Jesse Carter

Orijinal gönderi şunları belirtir: 19.0 should return 1 . Bu çözüm her zaman minimum miktarda 1 ondalık basamak varsayar ve sondaki sıfırları göz ardı eder. ondalık, ölçek faktörü kullandığı için bunlara sahip olabilir. Ölçek faktörüne, Decimal.GetBytes()işaretçi mantığından veya işaretçi mantığı kullanılarak elde edilen dizideki 3. indisli elemanın 16-24 baytlarında olduğu gibi erişilebilir .
Martin Tilo Schmitz

2

Clement'in cevabına çok benzer bir şey kullanıyorum:

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

Application.CurrentCulture'a erişmek için System.Windows.Forms'u kullanmayı unutmayın


1

Deneyebilirsin:

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

7
Ondalık bir tam sayı olduğunda bu başarısız olmaz mı? [1]
Silvermind

1

Kodumda aşağıdaki mekanizmayı kullanıyorum

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

ondalık girdi = 3.376; var enstring = input.ToString ();

GetDecimalLength (enstring) çağırın


1
Ondalık değerin ToString () temsili verilerimin sonuna "00" eklediği için bu benim için çalışmıyor - SQL Server'dan bir Ondalık (12,4) veri türü kullanıyorum.
PeterX

Verilerinizi c # tipi ondalık sayıya çevirip çözümü deneyebilir misiniz? Benim için Tostring () 'i c # ondalık değerde kullandığımda asla "00" görmüyorum.
Srikanth

1

Özyinelemeyi kullanarak şunları yapabilirsiniz:

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

Orijinal sonrası belirtiyor: 19.0 should return 1. Bu çözüm sondaki sıfırları yok sayacaktır. ondalık, ölçek faktörü kullandığı için bunlara sahip olabilir. Ölçek faktörüne, Decimal.GetBytes()dizide indeks 3 olan elemanın 16-24 baytlarında olduğu gibi veya işaretçi mantığı kullanılarak erişilebilir .
Martin Tilo Schmitz

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

0

Bu yöntemi kullanmanızı öneririm:

    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

Dikkate alan bir ondalık uzatma yöntemi olarak:

  • Farklı kültürler
  • Bütün sayılar
  • Negatif sayılar
  • Ondalık basamakta sondaki set sıfırları (örneğin, 1.2300M 2 değil 4 döndürür)
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;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.