Trova il numero di posizioni decimali in valore decimale indipendentemente dalla lingua


91

Mi chiedo se esiste un modo conciso e accurato per estrarre il numero di cifre decimali in un valore decimale (come int) che sarà sicuro da usare in diverse informazioni di cultura?

Ad esempio:
19.0 dovrebbe restituire 1,
27.5999 dovrebbe restituire 4,
19.12 dovrebbe restituire 2,
ecc.

Ho scritto una query che ha suddiviso una stringa su un punto per trovare posizioni decimali:

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

Ma mi viene in mente che questo funzionerà solo nelle regioni che utilizzano il "." come separatore decimale ed è quindi molto fragile nei diversi sistemi.


Un decimale come da titolo della domanda
Jesse Carter

Che ne dici di un po 'di pattern matching prima di Split? Fondamentalmente \ d + (\ D) \ d + dove \ D restituisce il separatore (.,
Ecc

7
Questa non è una domanda a risposta chiusa come potrebbe apparire a prima vista. Chiedendo 19.0di ritorno 1è un dettaglio di implementazione per quanto riguarda la memoria interna del valore 19.0. Il fatto è che è perfettamente legittimo che il programma lo memorizzi come 190×10⁻¹o 1900×10⁻²o 19000×10⁻³. Tutti quelli sono uguali. Il fatto che utilizzi la prima rappresentazione quando viene fornito un valore di 19.0Me questo viene esposto quando viene utilizzato ToStringsenza un identificatore di formato è solo una coincidenza e una cosa allegra. Tranne che non è felice quando le persone si affidano all'esponente nei casi in cui non dovrebbero.
ErikE

Se desideri un tipo che possa contenere "numero di posizioni decimali utilizzate" quando viene creato, in modo da poter distinguere in modo affidabile 19Mda 19.0Mda 19.00M, dovrai creare una nuova classe che raggruppa il valore sottostante come una proprietà e il numero di posizioni decimali come un'altra proprietà.
ErikE

1
Anche se la classe Decimal può "distinguere" 19 m, da 19,0 m da 19,00 m? Le cifre significative sono come uno dei suoi principali casi d'uso. Che cos'è 19,0 m * 1,0 m? Sembra che stia dicendo 19.00 m, forse gli sviluppatori di C # stanno sbagliando i calcoli: P? Anche in questo caso le cifre significative sono una cosa reale. Se non ti piacciono le cifre significative, probabilmente non dovresti usare la classe Decimal.
Nicholi

Risposte:


168

Ho usato il modo di Joe per risolvere questo problema :)

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

6
Dopo aver esaminato ulteriormente questo aspetto e averlo visto in azione, lo contrassegno come la risposta perché questo è a mio parere il metodo più conciso ed elegante per restituire i decimali che ho visto qui. Farei di nuovo +1 se potessi: D
Jesse Carter

9
decimalmantiene il conteggio delle cifre dopo la virgola, ecco perché trovi questo "problema", devi lanciare decimal in double e di nuovo in decimal per la correzione: BitConverter.GetBytes (decimal.GetBits ((decimal) (double) argument) [3]) [ 2];
burning_LEGION

3
Questo non ha funzionato per me. Il valore che ritorna da SQL è 21.17 sta dicendo 4 cifre. Il tipo di dati è definito come DECIMAL (12,4), quindi forse è così (utilizzando Entity Framework).
PeterX

11
@Nicholi - No, questo è eccezionalmente negativo perché il metodo si basa sul posizionamento dei bit sottostanti del decimale - qualcosa che ha molti modi per rappresentare lo stesso numero . Non testeresti una classe in base allo stato dei suoi campi privati, vero?
m.edmondson

14
Non sono sicuro di cosa dovrebbe essere elegante o carino in questo. Questo è quanto più offuscato possibile. Chissà se funziona anche in tutti i casi. Impossibile accertarsene.
usr

24

Dal momento che nessuna delle risposte fornite era sufficiente per il numero magico "-0.01f" convertito in decimale .. cioè: GetDecimal((decimal)-0.01f);
posso solo presumere che un colossale virus della scoreggia mentale abbia attaccato tutti 3 anni fa :)
Ecco quello che sembra essere un funzionante implementazione di questo problema malvagio e mostruoso, il complicatissimo problema di contare le cifre decimali dopo il punto - niente stringhe, niente culture, nessuna necessità di contare i bit e nessuna necessità di leggere forum di matematica .. solo semplice matematica di terza elementare.

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
La tua soluzione fallirà per un numero di casi che contengono zeri finali e le cifre sono SIGNIFICATIVE. 0,01 m * 2,0 m = 0,020 m. Dovrebbe essere di 3 cifre, il tuo metodo restituisce 2. Sembra che tu non stia comprendendo correttamente cosa succede quando lanci 0.01f a Decimal. I punti mobili non sono intrinsecamente precisi, quindi il valore binario effettivo memorizzato per 0.01f non è esatto. Quando lanci in Decimal (una notazione numerica molto strutturata) potresti non ottenere 0,01 m (in realtà ottieni 0,010 m). La soluzione GetBits è effettivamente corretta per ottenere il numero di cifre da un decimale. Il modo in cui converti in decimale è la chiave.
Nicholi

2
@Nicholi 0.020m è uguale a 0.02m .. gli zeri finali non sono significativi. OP chiede "indipendentemente dalla cultura" nel titolo e in modo ancora più specifico spiega "..che sarà sicuro da usare in diverse informazioni culturali .." - quindi penso che la mia risposta rimanga ancora più valida di altre.
GY

6
OP ha detto specificamente: "19.0 dovrebbe restituire 1". Questo codice non riesce in quel caso.
daniloquio

9
forse questo non è ciò che voleva l'OP, ma questa risposta si adatta meglio alle mie esigenze rispetto alla risposta principale a questa domanda
Arsen Zahray

2
Le prime due righe devono essere sostituite con n = n % 1; if (n < 0) n = -n;perché un valore maggiore di int.MaxValuecauserà OverflowException, ad es 2147483648.12345.
Delirio

23

Probabilmente userò la soluzione nella risposta di @ fixagon .

Tuttavia, sebbene la struttura Decimal non abbia un metodo per ottenere il numero di decimali, è possibile chiamare Decimal.GetBits per estrarre la rappresentazione binaria, quindi utilizzare il valore intero e la scala per calcolare il numero di decimali.

Questo probabilmente sarebbe più veloce della formattazione come stringa, anche se dovresti elaborare un numero enorme di decimali per notare la differenza.

Lascio l'implementazione come esercizio.


1
Grazie @ Joe, questo è un modo davvero carino per affrontarlo. A seconda di come si sente il mio capo sull'utilizzo dell'altra soluzione, darò un'occhiata all'implementazione della tua idea. Sarebbe sicuramente un esercizio divertente :)
Jesse Carter

17

Una delle migliori soluzioni per trovare il numero di cifre dopo il punto decimale è mostrata nel post di burning_LEGION .

Qui sto usando parti di un articolo del forum STSdb: Numero di cifre dopo il punto decimale .

In MSDN possiamo leggere la seguente spiegazione:

"Un numero decimale è un valore a virgola mobile che consiste in un segno, un valore numerico in cui ogni cifra del valore è compresa tra 0 e 9 e un fattore di scala che indica la posizione di un punto decimale mobile che separa l'integrale e la frazione parti del valore numerico. "

E anche:

"La rappresentazione binaria di un valore Decimal è costituita da un segno a 1 bit, un numero intero a 96 bit e un fattore di scala utilizzato per dividere l'intero a 96 bit e specificare quale parte di esso è una frazione decimale. Il fattore di scala è implicitamente il numero 10, elevato a un esponente compreso tra 0 e 28 ".

A livello interno il valore decimale è rappresentato da quattro valori interi.

Rappresentazione interna decimale

Esiste una funzione GetBits disponibile pubblicamente per ottenere la rappresentazione interna. La funzione restituisce un array int []:

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

Il quarto elemento della matrice restituita contiene un fattore di scala e un segno. E come dice l'MSDN, il fattore di scala è implicitamente il numero 10, elevato a un esponente compreso tra 0 e 28. Questo è esattamente ciò di cui abbiamo bisogno.

Quindi, sulla base di tutte le indagini di cui sopra, possiamo costruire il nostro metodo:

private const int SIGN_MASK = ~Int32.MinValue;

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

Qui un SIGN_MASK viene utilizzato per ignorare il segno. Dopo logico e abbiamo anche spostato il risultato con 16 bit a destra per ricevere il fattore di scala effettivo. Questo valore, infine, indica il numero di cifre dopo il punto decimale.

Notare che qui MSDN dice anche che il fattore di scala preserva anche gli zeri finali in un numero decimale. Gli zeri finali non influiscono sul valore di un numero Decimal nelle operazioni aritmetiche o di confronto. Tuttavia, gli zeri finali potrebbero essere rilevati dal metodo ToString se viene applicata una stringa di formato appropriata.

Questa soluzione sembra la migliore, ma aspetta, c'è di più. Con l'accesso a metodi privati in C # possiamo usare espressioni per costruire un accesso diretto al campo di bandiere e di evitare la costruzione della matrice 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);
    }
}

Questo codice compilato viene assegnato al campo GetDigits. Si noti che la funzione riceve il valore decimale come ref, quindi non viene eseguita alcuna copia effettiva, solo un riferimento al valore. Usare la funzione GetDigits da DecimalHelper è facile:

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

Questo è il metodo più veloce possibile per ottenere il numero di cifre dopo il punto decimale per i valori decimali.


3
decimale r = (decimale) -0,01f; e la soluzione fallisce. (su tutte le risposte che ho visto in questa pagina ...) :)
GY

5
NOTA: Riguardo l'intera cosa (Decimal) 0.01f, stai lanciando un punto mobile, intrinsecamente NON PRECISO, a qualcosa di molto strutturato come un Decimal. Dai un'occhiata all'output di Console.WriteLine ((Decimal) 0.01f). Il decimale formato nel cast IN REALTÀ ha 3 cifre, ecco perché tutte le soluzioni fornite dicono 3 invece di 2. Tutto funziona effettivamente come previsto, il "problema" è che ti aspetti che i valori in virgola mobile siano esatti. Non sono.
Nicholi

@Nicholi Il tuo punto fallisce quando te ne accorgi 0.01e 0.010sono esattamente numeri uguali . Inoltre, l'idea che un tipo di dato numerico abbia una sorta di semantica "numero di cifre usate" su cui si può fare affidamento è completamente sbagliata (da non confondere con "numero di cifre consentite". Non confondere la presentazione (la visualizzazione di il valore di un numero in una base particolare, ad esempio, l'espansione decimale del valore indicato dall'espansione binaria 111) con il valore sottostante! Per ribadire, i numeri non sono cifre, né sono composti da cifre .
ErikE

6
Sono equivalenti in valore, ma non in cifre significative. Che è un grande caso d'uso della classe Decimal. Se chiedessi quante cifre ci sono negli 0,010 m letterali, risponderesti solo 2? Anche se decine di insegnanti di matematica / scienze in tutto il mondo ti direbbero che lo 0 finale è significativo? Il problema a cui ci riferiamo si manifesta con il casting da virgola mobile a Decimal. Non l'utilizzo di GetBits stesso, che funziona esattamente come documentato. Se non ti interessano le cifre significative, allora sì, hai un problema e probabilmente non dovresti usare la classe Decimal in primo luogo.
Nicholi

1
@theberserker Per quanto ricordo, non c'era nessun problema: dovrebbe funzionare in entrambi i modi.
Kristiyan Dimitrov

13

Affidarsi alla rappresentazione interna dei decimali non è cool.

Cosa ne pensi di questo:

    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

puoi usare InvariantCulture

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

un'altra possibilità sarebbe fare qualcosa del genere:

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

In che modo questo mi aiuta a trovare il numero di posizioni decimali nel decimale? Non ho problemi a convertire il decimale in una stringa valida in qualsiasi cultura. Secondo la domanda, sto cercando di trovare il numero di cifre decimali che erano sul decimale
Jesse Carter,

@ JesseCarter: significa che puoi sempre dividere ..
Austin Salonen

@AustinSalonen Davvero? Non sapevo che l'utilizzo di InvariantCulture avrebbe imposto l'uso di un punto come separatore decimale
Jesse Carter,

come hai fatto prima, casterà sempre il prezzo per stringere con a. come separatore decimale. ma non è il modo più elegante secondo me ...
fixagon


8

La maggior parte delle persone qui sembra non essere a conoscenza del fatto che i decimali considerano gli zeri finali significativi per l'archiviazione e la stampa.

Quindi 0,1 m, 0,10 me 0,100 m possono essere paragonati allo stesso modo, vengono memorizzati in modo diverso (come valore / scala 1/1, 10/2 e 100/3, rispettivamente) e verranno stampati rispettivamente come 0,1, 0,10 e 0,100 , di ToString() .

In quanto tali, le soluzioni che riportano "una precisione troppo alta" stanno effettivamente riportando la precisione corretta , alle decimalcondizioni di.

Inoltre, le soluzioni basate sulla matematica (come la moltiplicazione per potenze di 10) saranno probabilmente molto lente (il decimale è ~ 40 volte più lento del doppio per l'aritmetica, e non vuoi nemmeno mescolare in virgola mobile perché è probabile che introduca imprecisione ). Allo stesso modo, il casting intao longcome mezzo di troncamento è soggetto a errori ( decimalha un intervallo molto maggiore di uno di questi - si basa su un numero intero a 96 bit).

Sebbene non sia elegante in quanto tale, quanto segue sarà probabilmente uno dei modi più veloci per ottenere la precisione (se definito come "posizioni decimali esclusi gli zeri finali"):

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

La cultura invariante garantisce un "." come punto decimale, gli zeri finali vengono tagliati, quindi è solo questione di vedere quante posizioni rimangono dopo il punto decimale (se ce n'è uno).

Modifica: modificato il tipo di ritorno in int


1
@mvmorten Non sono sicuro del motivo per cui hai ritenuto necessario modificare il tipo di ritorno in int; byte rappresenta più accuratamente il valore restituito: unsigned e small range (0-29, in pratica).
Zastai

1
Sono d'accordo sul fatto che le soluzioni iterative e basate su calcoli siano lente (oltre a non tenere conto degli zeri finali). Tuttavia, allocare una stringa per questo e operare su quella invece non è nemmeno la cosa più performante da fare, specialmente in contesti critici per le prestazioni e con un GC lento. L'accesso alla bilancia tramite la logica del puntatore è molto più veloce e privo di allocazioni.
Martin Tilo Schmitz

Sì, ottenere la scala può essere fatto in modo molto più efficiente, ma ciò includerebbe gli zeri finali. E per rimuoverli è necessario eseguire operazioni aritmetiche sulla parte intera.
Zastai

6

Ed ecco un altro modo, usa il tipo SqlDecimal che ha una proprietà di scala con il conteggio delle cifre a destra del decimale. Trasmetti il ​​tuo valore decimale a SqlDecimal e quindi accedi a Scale.

((SqlDecimal)(decimal)yourValue).Scale

1
Esaminando il codice di riferimento Microsoft , il cast a SqlDecimal utilizza internamente il GetBytesquindi alloca l'array Byte invece di accedere ai byte in un contesto non sicuro. C'è anche una nota e un codice commentato nel codice di riferimento, che lo afferma e come potrebbero farlo invece. Perché non l'hanno fatto è un mistero per me. Rimango alla larga da questo e accedo direttamente ai bit della scala invece di nascondere il GC Alloc in questo cast, poiché non è molto ovvio cosa fa sotto il cofano.
Martin Tilo Schmitz

4

Finora, quasi tutte le soluzioni elencate stanno allocando la memoria GC, che è in gran parte il modo C # di fare le cose, ma tutt'altro che ideale in ambienti critici per le prestazioni. (Quelli che non allocano i loop e inoltre non prendono in considerazione gli zeri finali.)

Quindi, per evitare GC Allocs, puoi semplicemente accedere ai bit di scala in un contesto non sicuro. Potrebbe sembrare fragile ma, secondo la fonte di riferimento di Microsoft , il layout della struttura del decimale è sequenziale e contiene anche un commento, per non modificare l'ordine dei campi:

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

Come puoi vedere, il primo int qui è il campo flags. Dalla documentazione e come accennato in altri commenti qui, sappiamo che solo i bit da 16-24 codificano la scala e che dobbiamo evitare il 31 ° bit che codifica il segno. Poiché int ha la dimensione di 4 byte, possiamo tranquillamente farlo:

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

Questa dovrebbe essere la soluzione più performante poiché non è presente alcuna allocazione GC dell'array di byte o conversioni ToString. L'ho testato con .Net 4.xe .Net 3.5 in Unity 2019.1. Se ci sono versioni in cui questo non riesce, fammelo sapere.

Modificare:

Grazie a @Zastai per avermi ricordato la possibilità di utilizzare un layout struct esplicito per ottenere praticamente la stessa logica del puntatore al di fuori del codice non sicuro:

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

Per risolvere il problema originale, potresti rimuovere tutti i campi oltre Valuee Scalema forse potrebbe essere utile che qualcuno li abbia tutti.


1
Puoi anche evitare codice non sicuro codificando la tua struttura con layout esplicito: inserisci un decimale nella posizione 0, quindi byte / int nelle posizioni appropriate. Qualcosa del tipo:[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

Grazie @Zastai, buon punto. Ho incorporato anche questo approccio. :)
Martin Tilo Schmitz

1
Una cosa da notare: l'impostazione della scala al di fuori dell'intervallo 0-28 provoca la rottura. ToString () tende a funzionare, ma l'aritmetica fallisce.
Zastai

Grazie ancora @Zastai, ho aggiunto un assegno per questo :)
Martin Tilo Schmitz

Un'altra cosa: molte persone qui non volevano prendere in considerazione gli zeri decimali finali. Se si definisce a, const decimal Foo = 1.0000000000000000000000000000m;dividere un decimale per questo lo ridimensionerà alla scala più bassa possibile (cioè non includendo più gli zeri decimali finali). Non l'ho confrontato per vedere se è più veloce o meno dell'approccio basato su stringhe che ho suggerito altrove.
Zastai

2

Ho scritto ieri un piccolo metodo conciso che restituisce anche il numero di cifre decimali senza dover fare affidamento su divisioni di stringhe o culture, il che è l'ideale:

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 simili alla tua seconda risposta anche se per qualcosa del genere preferisco l'approccio iterativo piuttosto che usare la ricorsione
Jesse Carter

Il post originale afferma che: 19.0 should return 1. Questa soluzione presupporrà sempre una quantità minima di 1 cifra decimale e ignorerà gli zeri finali. decimal può avere quelli in quanto utilizza un fattore di scala. Il fattore di scala è accessibile come nei byte 16-24 dell'elemento con indice 3 nell'array ottenuto da Decimal.GetBytes()o utilizzando la logica del puntatore.
Martin Tilo Schmitz

2

Sto usando qualcosa di molto simile alla risposta di 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);
}

Ricorda di utilizzare System.Windows.Forms per ottenere l'accesso ad Application.CurrentCulture


1

Puoi provare:

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

7
Questo non fallirebbe se il decimale è un numero intero? [1]
Silvermind

1

Uso il seguente meccanismo nel mio codice

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

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

chiama GetDecimalLength (istruendo)


1
Questo non funziona per me poiché la rappresentazione ToString () del valore decmial aggiunge "00" alla fine dei miei dati: sto usando un tipo di dati Decimal (12,4) da SQL Server.
PeterX

Puoi trasmettere i tuoi dati al tipo decimale c # e provare la soluzione. Per me quando uso Tostring () su un valore decimale c # non vedo mai uno "00".
Srikanth

1

Usando la ricorsione puoi fare:

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

Il post originale afferma che: 19.0 should return 1. Questa soluzione ignorerà gli zeri finali. decimal può avere quelli in quanto utilizza un fattore di scala. È possibile accedere al fattore di scala come nei byte 16-24 dell'elemento con indice 3 in Decimal.GetBytes()array o utilizzando la logica del puntatore.
Martin Tilo Schmitz

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

0

Suggerisco di utilizzare questo metodo:

    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

Come metodo di estensione decimale che tiene conto di:

  • Culture differenti
  • Numeri interi
  • Numeri negativi
  • Trailing set zeri sulla cifra decimale (ad esempio 1.2300M restituirà 2 non 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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.