2016-06-05 42 views
10

ho notato decimal.Parse(number, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture) è di circa il 100% più lento di metodo parse decimale personalizzato basato sul codice di Jeffrey Sax da Faster alternative to Convert.ToDoubleveloce alternativa al decimal.Parse

public static decimal ParseDecimal(string input) { 
    bool negative = false; 
    long n = 0; 

    int len = input.Length; 
    int decimalPosition = len; 

    if (len != 0) { 
     int start = 0; 
     if (input[0] == '-') { 
      negative = true; 
      start = 1; 
     } 

     for (int k = start; k < len; k++) { 
      char c = input[k]; 

      if (c == '.') { 
       decimalPosition = k +1; 
      } else { 
       n = (n *10) +(int)(c -'0'); 
      } 
     } 
    } 

    return new decimal(((int)n), ((int)(n >> 32)), 0, negative, (byte)(len -decimalPosition)); 
} 

Suppongo che è perché nativo decimal.Parse è stato organizzato per lottare con stile di numero e informazioni sulla cultura.

Tuttavia, il metodo sopra menzionato non utilizza il terzo parametro hi byte in new decimal, quindi non funzionerà con numeri più grandi.

Esiste un'alternativa più veloce a decimal.Parse per convertire stringhe costituite solo da numeri e punti decimali in decimale che funzionerebbero con numeri grandi?

EDIT: Benchmark:

var style = System.Globalization.NumberStyles.AllowDecimalPoint; 
var culture = System.Globalization.CultureInfo.InvariantCulture; 
System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch(); 
s.Reset(); 
s.Start(); 
for (int i=0; i<10000000; i++) 
{ 
    decimal.Parse("20000.0011223344556", style, culture); 
} 
s.Stop(); 
Console.WriteLine(s.Elapsed.ToString()); 

s.Reset(); 
s.Start(); 
for (int i=0; i<10000000; i++) 
{ 
    ParseDecimal("20000.0011223344556"); 
} 
s.Stop(); 
Console.WriteLine(s.Elapsed.ToString()); 

uscita:

00:00:04.2313728 
00:00:01.4464048 

personalizzato ParseDecimal è in questo caso molto più velocemente rispetto decimal.Parse.

+0

I numeri negativi devono ancora essere considerati? –

+0

WillemVanOnsem Van Onsem, nel mio caso di utilizzo corrente non ho bisogno di numeri negativi. Tuttavia, questa è una domanda generale che potrebbe essere utile a chiunque, quindi sarebbe meglio se supportasse numeri negativi. – LukAss741

+12

Decimal.Parse() è veloce quanto deve essere, scritto in C++ e integrato nel sistema operativo negli ultimi 20 anni. Puoi solo accelerarlo tagliando gli angoli. Non sei abbastanza esplicito su quale tipo di bug trovi accettabile. –

risposta

5

Grazie per tutti i vostri commenti che mi hanno dato un po 'più di intuizione. Alla fine l'ho fatto come segue. Se l'input è troppo lungo, separa la stringa di input e analizza la prima parte usando long e il resto con int che è ancora più veloce di decimal.Parse.

Questo è il mio codice di produzione finale:

public static int[] powof10 = new int[10] 
{ 
    1, 
    10, 
    100, 
    1000, 
    10000, 
    100000, 
    1000000, 
    10000000, 
    100000000, 
    1000000000 
}; 
public static decimal ParseDecimal(string input) 
{ 
    int len = input.Length; 
    if (len != 0) 
    { 
     bool negative = false; 
     long n = 0; 
     int start = 0; 
     if (input[0] == '-') 
     { 
      negative = true; 
      start = 1; 
     } 
     if (len <= 19) 
     { 
      int decpos = len; 
      for (int k = start; k < len; k++) 
      { 
       char c = input[k]; 
       if (c == '.') 
       { 
        decpos = k +1; 
       }else{ 
        n = (n *10) +(int)(c -'0'); 
       } 
      } 
      return new decimal((int)n, (int)(n >> 32), 0, negative, (byte)(len -decpos)); 
     }else{ 
      if (len > 28) 
      { 
       len = 28; 
      } 
      int decpos = len; 
      for (int k = start; k < 19; k++) 
      { 
       char c = input[k]; 
       if (c == '.') 
       { 
        decpos = k +1; 
       }else{ 
        n = (n *10) +(int)(c -'0'); 
       } 
      } 
      int n2 = 0; 
      bool secondhalfdec = false; 
      for (int k = 19; k < len; k++) 
      { 
       char c = input[k]; 
       if (c == '.') 
       { 
        decpos = k +1; 
        secondhalfdec = true; 
       }else{ 
        n2 = (n2 *10) +(int)(c -'0'); 
       } 
      } 
      byte decimalPosition = (byte)(len -decpos); 
      return new decimal((int)n, (int)(n >> 32), 0, negative, decimalPosition) *powof10[len -(!secondhalfdec ? 19 : 20)] +new decimal(n2, 0, 0, negative, decimalPosition); 
     } 
    } 
    return 0; 
} 

Codice di riferimento:

const string input = "[inputs are below]"; 
var style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowLeadingSign; 
var culture = System.Globalization.CultureInfo.InvariantCulture; 
System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch(); 
s.Reset(); 
s.Start(); 
for (int i=0; i<10000000; i++) 
{ 
    decimal.Parse(input, style, culture); 
} 
s.Stop(); 
Console.WriteLine(s.Elapsed.ToString()); 

s.Reset(); 
s.Start(); 
for (int i=0; i<10000000; i++) 
{ 
    ParseDecimal(input); 
} 
s.Stop(); 
Console.WriteLine(s.Elapsed.ToString()); 

risultati sul mio i7 920:

ingresso: 123,456789

00:00:02.7292447 
00:00:00.6043730 

di ingresso: 9999999999 99999123.456789

00:00:05.3094786 
00:00:01.9702198 

immissione: 1.0

00:00:01.4212123 
00:00:00.2378833 

ingresso: 0

00:00:01.1083770 
00:00:00.1899732 

ingresso: -3,3333333333333333333333333333333

00:00:06.2043707 
00:00:02.0373628 

Se l'ingresso consiste solo di 0-9,. e facoltativamente - all'inizio questa funzione personalizzata è significativamente più veloce per l'analisi della stringa in decimale.

1

Il metodo di Sax è veloce per due motivi. Il primo, lo sai già. Il secondo, è perché è in grado di sfruttare il molto efficiente tipo di dati a 8 byte per n. Capire l'uso di questo metodo del lungo può anche spiegare perché (sfortunatamente) non è attualmente possibile utilizzare un metodo simile per numeri molto grandi.

I primi due parametri: lo e mid nel costruttore decimale utilizzano 4 byte ciascuno. Insieme questa è la stessa quantità di memoria della lunga. Ciò significa che non vi è più spazio per continuare ad andare avanti una volta raggiunto il valore massimo per un lungo.

Per utilizzare un metodo simile, è necessario un tipo di dati di 12 byte al posto del lungo. Ciò fornirebbe i quattro byte aggiuntivi necessari per utilizzare il parametro hi.

Il metodo di Sax è molto intelligente, ma fino a quando qualcuno non scrive un tipo di dati da 12 byte, devi semplicemente fare affidamento sul decimale.Parola.