Domanda

Sto scrivendo un'app che deve generare decimali di varie lunghezze e scala variabile in stringhe senza il punto decimale per scrivere su un file piatto come input su un altro sistema. per es.

 12345  -> Length:10, Scale:2              -> 0001234500
 123.45 -> Length:10, Scale:2              -> 0000012345
 123.45 -> Length:10, Scale:3              -> 0000123450
-123.45 -> Length:10, Scale:3, signed:true -> -000123450
 123.45 -> Length:10, Scale:3, signed:true -> +000123450

La funzione che ho scritto per gestirlo è sotto e verrà chiamata centinaia di migliaia di volte, quindi voglio assicurarmi che non ci sia un modo migliore e più efficiente per farlo. Ho esaminato i modi in cui DecimalFormat può fare di più per me, ma non riesco a vederlo gestendo il mio bisogno di formattare con posizioni decimali ma senza virgola.

protected String getFormattedDecimal( String value, int len, int scale, Boolean signed ) throws Exception{
    StringBuffer retVal = new StringBuffer();

    //Need a BigDecimal to facilitiate correct formatting
    BigDecimal bd = new BigDecimal( value );

    //set the scale to ensure that the correct number of zeroes 
    //at the end in case of rounding
    bd = bd.setScale( scale );

    //taking it that if its supposed to have negative it'll be part of the num
    if ( ( bd.compareTo( BigDecimal.ZERO ) >= 0 ) && signed ){
        retVal.append( "+" );
    }           

    StringBuffer sbFormat = new StringBuffer();
    for (int i = 0; i < len; i++)
    {
        sbFormat.append('0');
    }

    DecimalFormat df = new DecimalFormat( sbFormat.toString() );

    retVal.append( df.format( bd.unscaledValue() ) );

    return retVal.toString();
}
È stato utile?

Soluzione

La mia implementazione migliorata per le prestazioni è di seguito. È circa 4,5 volte più veloce della soluzione basata su DecimalFormatter: in esecuzione sulla mia macchina, utilizzando Eclipse con un discreto cablaggio di prova preparato in casa, i risultati sono:

  • Il vecchio modo impiegava 5421 ms per effettuare 600.000 chiamate (in media 0,009035 ms per chiamata)
  • Il nuovo modo ha richiesto 1219 ms per effettuare 600.000 chiamate (in media 0,002032 ms per chiamata)

Alcune note:

  • La mia soluzione utilizza un blocco di zero di dimensioni fisse per il riempimento. Se prevedi di aver bisogno di più imbottitura su entrambi i lati rispetto ai circa trenta che ho usato, dovresti aumentare le dimensioni ... chiaramente potresti aumentarle dinamicamente se necessario.
  • I tuoi commenti sopra non corrispondono del tutto al codice. In particolare, se è stato restituito un carattere di segno, la lunghezza restituita è maggiore di quella richiesta (i tuoi commenti dicono diversamente). Ho scelto di credere al codice piuttosto che ai commenti.
  • Ho reso statico il mio metodo, poiché non richiede uno stato di istanza. Questa è una cosa di gusto personale - ymmv.

Inoltre: al fine di imitare il comportamento dell'originale (ma non indicato nei commenti), questo:

  • Se ci sono più cifre frazionarie nel valore in entrata di quelle in scala, genera un'eccezione aritmetica Se ci sono più cifre intere nel valore in entrata che in forma (scala len), la stringa restituita è più lunga di len. Se firmato è vero, la stringa restituita sarà una più lunga di len

    • Tuttavia : se len è negativo, l'originale restituisce una stringa delimitata da virgole. Ciò genera un'eccezione IllegalARgumentException
    package com.pragmaticsoftwaredevelopment.stackoverflow;
    ...
       final static String formatterZeroes="00000000000000000000000000000000000000000000000000000000000";
       protected static String getFormattedDecimal ( String value, int len, int scale, Boolean signed ) throws IllegalArgumentException {
           if (value.length() == 0) throw new IllegalArgumentException ("Cannot format a zero-length value");
           if (len <= 0) throw new IllegalArgumentException ("Illegal length (" + len + ")");
           StringBuffer retVal = new StringBuffer();
           String sign=null;
           int numStartIdx; 
           if ("+-".indexOf(value.charAt(0)) < 0) {
              numStartIdx=0;
           } else {
              numStartIdx=1;
              if (value.charAt(0) == '-')
                 sign = "-";
           }
           if (signed && (value.charAt(0) != '-'))
              sign = "+";
           if (sign==null)
              sign="";
           retVal.append(sign);
    
    
           int dotIdx = value.indexOf('.');
           int requestedWholePartLength = (len-scale);
    
           if (dotIdx < 0) { 
              int wholePartPadLength = (requestedWholePartLength - ((value.length()-numStartIdx)));
              if (wholePartPadLength > 0)
                 retVal.append(formatterZeroes.substring(0, wholePartPadLength));
              retVal.append (value.substring(numStartIdx));
              if (scale > 0)
                 retVal.append(formatterZeroes.substring(0, scale));
           }
           else {
              int wholePartPadLength = (requestedWholePartLength - (dotIdx - numStartIdx));
              if (wholePartPadLength > 0)
                 retVal.append(formatterZeroes.substring(0, wholePartPadLength));
              retVal.append (value.substring(numStartIdx, dotIdx));
              retVal.append (value.substring (dotIdx+1));
              int fractionalPartPadLength = (scale - (value.length() - 1 - dotIdx));
              if (fractionalPartPadLength > 0)
                 retVal.append(formatterZeroes.substring(0, fractionalPartPadLength));
    
    
           }
    
           return retVal.toString();
       }
    
        
  • Altri suggerimenti

    Se stai ricevendo il tuo input come String per cominciare, perché devi convertirlo in BigDecimal e viceversa?

    Sembra che sarebbe molto più veloce trovare la posizione del punto decimale, confrontarlo con lunghezza / scala e riempire la stringa di conseguenza.

    Sono d'accordo con la manipolazione manuale delle stringhe di ChssPly76 wrt.

    Tuttavia, se stai andando a scendere la rotta BigDecimal / DecimalFormat , potresti prendere in considerazione l'idea di condividere i tuoi DecimalFormat invece di crearne uno nuovo ad ogni iterazione. Nota che queste classi non sono thread-safe, quindi se stai usando più thread per eseguire la tua elaborazione, vorrai usare qualcosa come l'archiviazione ThreadLocal per mantenere un formattatore per Thread .

    a proposito, hai provato questo e hai trovato le prestazioni non accettabili o stai solo cercando la soluzione più efficiente possibile? Nota cosa dice Donald Knuth sull'argomento dell'ottimizzazione precoce .

    Autorizzato sotto: CC-BY-SA insieme a attribuzione
    Non affiliato a StackOverflow
    scroll top