2015-11-03 35 views
5

Ho cercato in qualche modo di determinare la scala e la precisione di un decimale in C#, che mi ha portato a diverse domande SO, ma nessuno di loro sembra aver corretto risposte, o hanno titoli fuorvianti (in realtà riguardano SQL server o qualche altro db, non C#), o nessuna risposta. Il seguente post, credo, è più vicino a quello che sto cercando, ma anche questo sembra sbagliato:In realtà, matematicamente determinare la precisione e la scala di un valore decimale

Determine the decimal precision of an input number

In primo luogo, sembra che ci sia una certa confusione circa la differenza tra bilancia e precisione. Per Google (per MSDN):

"La precisione è il numero di cifre in un numero. La scala è il numero di cifre a destra del punto decimale in un numero."

Con ciò detto, il numero 12345.67890M avrebbe una scala di 5 e una precisione di 10. Non ho scoperto un singolo esempio di codice che calcoli accuratamente questo in C#.

voglio fare due metodi helper, decimal.Scale() e decimal.Precision(), in modo tale che il seguente test di unità Passaggi:

[TestMethod] 
    public void ScaleAndPrecisionTest() 
    { 
     //arrange 
     var number = 12345.67890M; 

     //act 
     var scale = number.Scale(); 
     var precision = number.Precision(); 

     //assert 
     Assert.IsTrue(precision == 10); 
     Assert.IsTrue(scale == 5); 
    } 

... ma devo ancora trovare un frammento che farà questo, anche se molti la gente ha suggerito di usare decimal.GetBits(), e altri hanno detto, convertirlo in una stringa e analizzarlo.

Convertirlo in una stringa e analizzarlo è, a mio avviso, un'idea orribile, ignorando anche il problema di localizzazione con il punto decimale. La matematica dietro al metodo GetBits(), tuttavia, è come il greco per me.

Qualcuno può descrivere come sarebbero i calcoli per determinare la scala e la precisione in un valore decimal per C#?

+2

Puoi spiegare perché credi che la precisione sia 10? Ci sono 11 cifre in quel numero, con due zeri finali, lasciando 9 ovviamente significative. Non vedo da dove vengono 10. –

+2

La modifica non cambierà anche la scala? –

+0

... si. Sì, lo farebbe. Io succhio. Risolto il problema –

risposta

3

In questo modo si ottiene la scala utilizzando il GetBits() function:

decimal x = 12345.67890M; 
int[] bits = decimal.GetBits(x); 
byte scale = (byte) ((bits[3] >> 16) & 0x7F); 

E il modo migliore che posso pensare di ottenere la precisione è rimuovendo il punto di frazione (cioè, usare la Decimal Constructor per ricostruire il numero decimale senza la scala di cui sopra) e quindi utilizzare il logaritmo:

decimal x = 12345.67890M; 
int[] bits = decimal.GetBits(x); 
//We will use false for the sign (false = positive), because we don't care about it. 
//We will use 0 for the last argument instead of bits[3] to eliminate the fraction point. 
decimal xx = new Decimal(bits[0], bits[1], bits[2], false, 0); 
int precision = (int)Math.Floor(Math.Log10((double)xx)) + 1; 

Ora li può mettere in estensioni:

public static class Extensions{ 
    public static int GetScale(this decimal value){ 
    if(value == 0) 
      return 0; 
    int[] bits = decimal.GetBits(value); 
    return (int) ((bits[3] >> 16) & 0x7F); 
    } 

    public static int GetPrecision(this decimal value){ 
    if(value == 0) 
     return 0; 
    int[] bits = decimal.GetBits(value); 
    //We will use false for the sign (false = positive), because we don't care about it. 
    //We will use 0 for the last argument instead of bits[3] to eliminate the fraction point. 
    decimal d = new Decimal(bits[0], bits[1], bits[2], false, 0); 
    return (int)Math.Floor(Math.Log10((double)d)) + 1; 
    } 
} 

E qui c'è un fiddle.

+0

@ivan_pozdeev Stavo cercando di capire il modo migliore per ottenere la precisione. Ho aggiunto collegamenti a MSDN. Scusate, non vedo un vero punto nel ribadire ciò che è perfettamente spiegato dal MSDN, ma avete fatto un buon lavoro. Ma a quale errore ti riferisci? –

+0

@JeremyHolovacs Ho cercato di capire il modo migliore per implementarlo. Vedi la mia risposta aggiornata. –

+0

Eccellente. Questo sembra funzionare per tutti i casi limite che posso lanciarci. –

1

Prima di tutto, risolvere il problema "fisico": come decidere quali cifre sono significative. Il fatto è, "precision" has no physical meaning unless you know or guess the absolute error.


Ora, ci sono 2 modi fondamentali per determinare ogni cifra (e, quindi, il loro numero):

  • prendono + interpretare le parti significative
  • calcolare matematicamente

Il La 2a via non può rilevare gli zeri finali nella parte frazionaria (che può essere o non essere significativa a seconda della risposta al problema "fisico"), quindi non lo coprirò se non richiesto.

Per il primo, nel numero Decimal's interface, vedo 2 metodi di base per ottenere le parti: ToString() (alcuni sovraccarichi) e GetBits().

  1. ToString(String, IFormatInfo) è in realtà un modo affidabile in quanto è possibile definire il formato esattamente.

  2. La semantica di GetBits() risultato sono documentate chiaramente its MSDN article (così si lamenta come "è greco a me" non va bene;)). Decompiling con ILSpy mostra che in realtà è una tupla di campi dati grezzi dell'oggetto:

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

    E loro semantica sono:

    • |high|mid|low| - cifre binarie (96 bit), interpretati come un numero intero (= allineati a destra)
    • flags:
      • bit 16-23 - "la potenza di 10 a div ide il numero intero"(= numero di cifre decimali frazionarie)
        • (quindi (flags>>16)&0xFF è il valore grezzo di questo campo)
      • po 31 - segno (non riguarda noi)

    come si può vedere, questo è molto simile a IEEE 754 floats.

    Così, il numero di cifre decimali è il valore dell'esponente. Il numero è the number of digits in the decimal representation of the 96-bit integer.

+0

Questo non risponde alla domanda, e praticamente ignora anche la domanda. –

+0

Il mio obiettivo non è solo dare una risposta "prova questo" codice "risposta, ma dimostrare come produrne uno in modo da capire cosa e perché stai facendo. L'essenza di ciascun metodo è evidenziata in grassetto. Non fanno riferimento direttamente alla "scala" e alla "precisione" perché, come dice la sezione guida, questi termini possono significare cose diverse a seconda dell'applicazione. –

-1

risposta di Racil ti dà il valore del valore di scala interna del decimal che è corretto, anche se la rappresentazione interna cambia mai sarà interessante.

Nel formato corrente la porzione di precisione di decimal è fissata a 96 bit, ovvero tra 28 e 29 cifre decimali a seconda del numero. Tutti i valori .NET decimal condividono questa precisione. Poiché questo è costante, non è possibile utilizzare un valore interno per determinarlo.

Quello che apparentemente dopo è il numero di cifre, che possiamo facilmente determinare dalla rappresentazione della stringa. Possiamo anche ottenere la scala allo stesso tempo o almeno utilizzando lo stesso metodo.

public struct DecimalInfo 
{ 
    public int Scale; 
    public int Length; 

    public override string ToString() 
    { 
     return string.Format("Scale={0}, Length={1}", Scale, Length); 
    } 
} 

public static class Extensions 
{ 
    public static DecimalInfo GetInfo(this decimal value) 
    { 
     string decStr = value.ToString().Replace("-", ""); 
     int decpos = decStr.IndexOf("."); 
     int length = decStr.Length - (decpos < 0 ? 0 : 1); 
     int scale = decpos < 0 ? 0 : length - decpos; 
     return new DecimalInfo { Scale = scale, Length = length }; 
    } 
} 
+1

Questo è più o meno quello che ho detto esplicitamente che non volevo fare. –

-2

È possibile creare due piccoli metodi (se si vuole separare sui metodi) utilizzando la conversione + stringa:

Se si converte il decimale a String (var d = 4.555M; d.ToString();) quindi è possibile utilizzare Split('.') allora indice 0 della String Array è la tua precisione e l'indice 1 è la scala. Ma decimal ha anche il metodo .Truncate che ti dà comunque la precisione.