2010-09-10 9 views
15

Aggiornamento

OK, dopo alcune indagini, e grazie in gran parte alle utili risposte fornite da Jon e Hans, questo è quello che sono stato in grado di mettere insieme. Finora penso che sembra funzionare bene. Non scommetterei la mia vita sulla sua totale correttezza, ovviamente.C'è un modo per ottenere le "cifre significative" di un decimale?

public static int GetSignificantDigitCount(this decimal value) 
{ 
    /* So, the decimal type is basically represented as a fraction of two 
    * integers: a numerator that can be anything, and a denominator that is 
    * some power of 10. 
    * 
    * For example, the following numbers are represented by 
    * the corresponding fractions: 
    * 
    * VALUE NUMERATOR DENOMINATOR 
    * 1  1   1 
    * 1.0  10   10 
    * 1.012 1012  1000 
    * 0.04  4   100 
    * 12.01 1201  100 
    * 
    * So basically, if the magnitude is greater than or equal to one, 
    * the number of digits is the number of digits in the numerator. 
    * If it's less than one, the number of digits is the number of digits 
    * in the denominator. 
    */ 

    int[] bits = decimal.GetBits(value); 

    if (value >= 1M || value <= -1M) 
    { 
     int highPart = bits[2]; 
     int middlePart = bits[1]; 
     int lowPart = bits[0]; 

     decimal num = new decimal(lowPart, middlePart, highPart, false, 0); 

     int exponent = (int)Math.Ceiling(Math.Log10((double)num)); 

     return exponent; 
    } 
    else 
    { 
     int scalePart = bits[3]; 

     // Accoring to MSDN, the exponent is represented by 
     // bits 16-23 (the 2nd word): 
     // http://msdn.microsoft.com/en-us/library/system.decimal.getbits.aspx 
     int exponent = (scalePart & 0x00FF0000) >> 16; 

     return exponent + 1; 
    } 
} 

Non l'ho provato a fondo. Qui ci sono un paio di ingressi/uscite del campione, però:

 
Value   Precision 
0    1 digit(s). 
0.000   4 digit(s). 
1.23   3 digit(s). 
12.324   5 digit(s). 
1.2300   5 digit(s). 
-5    1 digit(s). 
-5.01   3 digit(s). 
-0.012   4 digit(s). 
-0.100   4 digit(s). 
0.0   2 digit(s). 
10443.31  7 digit(s). 
-130.340  6 digit(s). 
-80.8000  6 digit(s). 

Utilizzando questo codice, immagino vorrei realizzare il mio obiettivo facendo qualcosa di simile a questo:

public static decimal DivideUsingLesserPrecision(decimal x, decimal y) 
{ 
    int xDigitCount = x.GetSignificantDigitCount(); 
    int yDigitCount = y.GetSignificantDigitCount(); 

    int lesserPrecision = System.Math.Min(xDigitCount, yDigitCount); 

    return System.Math.Round(x/y, lesserPrecision); 
} 

non ho davvero finito di lavorare attraverso questo, però. Chiunque voglia condividere pensieri: sarebbe molto apprezzato!


domanda iniziale

Supponiamo che io ho scrivere questo codice:

decimal a = 1.23M; 
decimal b = 1.23000M; 

Console.WriteLine(a); 
Console.WriteLine(b); 

l'uscita sarà sopra:

 
1.23 
1.23000 

Trovo che questo funziona anche se uso decimal.Parse("1.23") per a e decimal.Parse("1.23000") per b (whi ch significa che questa domanda si applica ai casi in cui il programma riceve input dell'utente).

Quindi chiaramente un valore di decimal è in qualche modo "consapevole" di ciò che chiamerò la sua precisione . Tuttavia, non vedo membri sul tipo decimal che forniscono un modo per accedere a questo, a parte lo stesso ToString.

Supponiamo di voler moltiplicare due valori decimal e di tagliare il risultato alla precisione dell'argomento meno preciso. In altre parole:

decimal a = 123.4M; 
decimal b = 5.6789M; 

decimal x = a/b; 

Console.WriteLine(x); 

Le uscite di cui sopra:

 
21.729560302171195125816619416 

Quello che mi chiedo è: come potrei scrivere un metodo che sarebbe tornato 21.73 invece (dal 123.4M ha quattro cifre significative)?

Per essere chiari: mi rendo conto che potrei chiamare ToString su entrambi gli argomenti, contare le cifre significative in ogni stringa e utilizzare questo numero per arrotondare il risultato del calcolo. Sto cercando uno diverso modo, se possibile.

(I anche si rendono conto che nella maggior parte degli scenari in cui hai a che fare con le figure significative, probabilmente non c'è bisogno di utilizzare il tipo di decimal. Ma mi chiedo perché, come ho detto all'inizio, il decimalappare per includere informazioni sulla precisione, mentre double non lo fa, per quanto ne so.)

+2

Non sapevo che volevo sapere questo finché non lo hai chiesto +1! – msarchet

+3

La tua funzione non funziona correttamente per alcuni di questi ingressi. Ad esempio, -0.012 è solo 2 cifre significative - non 4. –

+0

@JamesJones Chiaramente si riferisce a un diverso concetto di cifre significative, non che si apprenderebbe in un corso di matematica. Forse il nome "cifre usate" avrebbe più senso. – ErikE

risposta

3

È possibile utilizzare Decimal.GetBits per ottenere i dati grezzi e risolverli.

Purtroppo non ho il tempo di scrivere codice di esempio al momento - e probabilmente userete il BigInteger per alcune manipolazioni, se state usando .NET 4 - ma si spera che questo vi porti avanti . L'elaborazione della precisione e la chiamata allo Math.Round sul risultato originale potrebbero essere un buon inizio.

+0

Accetto questa risposta anche se non ho ancora finito di elaborare la mia soluzione, perché mi fornisce sicuramente le informazioni di cui ho bisogno. (Purtroppo, speravo in qualcosa di un po 'più accessibile di 'GetBits', ma sembra che sia il migliore con cui lavorare!) –

3

Sì, a differenza dei tipi a virgola mobile, System.Decimal tiene traccia del numero di cifre nel letterale. Questa è una funzionalità di decimal.Parse(), che viene eseguita dal tuo codice tu stesso o dal compilatore quando analizza un valore letterale nel tuo programma. È possibile recuperare queste informazioni, controllare il codice nella mia risposta in this thread.

Recupero del numero di cifre significative dopo che la matematica sul valore mi colpisce come un tiro lungo. Non importa se gli operatori li conservano, facci sapere cosa scopri.

+3

' decimal' * è * un tipo a virgola mobile :) –

+0

Sì, sono carina sicuro che le informazioni non siano state conservate. Il che ha senso - voglio dire, considerando qualcosa come '1M/4M', dovrebbe ovviamente uscire a' 0.25M' non '0.3M'. Ad ogni modo, ho affrontato il problema per un po 'e mi è venuto in mente qualcosa di simile a una soluzione in-the-making (completa dello stesso bit bit che ho visto suggerito nella risposta collegata!) ... il mio codice è molto brutto , anche se. Se ottieni un minuto gratis dopo, apprezzerei sicuramente le tue opinioni sul mio aggiornamento in cima alla domanda. –

0

La soluzione sopra cade rapidamente dal letto con valori come 1200 dove il numero di cifre significative è 2 ma la funzione restituisce 4. Forse esiste un modo coerente per gestire una situazione del genere, ovvero verificare che il valore di ritorno non sia non includere gli zero finali nell'intera porzione numerica senza una parte frazionale.