Форматирование десятичных знаков в строки для производительности - PullRequest
5 голосов
/ 15 сентября 2009

Я пишу приложение, которое должно выводить десятичные дроби различной длины и разного масштаба в строки без десятичной точки для записи в плоский файл в качестве ввода в другую систему. например,

 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

Функция, которую я написал для обработки этого, приведена ниже и будет вызываться сотни тысяч раз, поэтому я хочу убедиться, что нет лучшего, более эффективного способа сделать это. Я искал способы, чтобы заставить DecimalFormat делать больше для меня, но я не вижу, чтобы он обрабатывал мою потребность в форматировании с десятичными знаками, но без десятичной точки.

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

Ответы [ 3 ]

8 голосов
/ 15 сентября 2009

Моя реализация с улучшенной производительностью ниже. Это примерно в 4,5 раза быстрее, чем решение на основе DecimalFormatter: работа на моей машине с использованием Eclipse с приличным домашним тестовым жгутом, результаты:

  • Старый способ занял 5421 мс, чтобы сделать 600 000 звонков (в среднем 0,009035 мс на звонок)
  • Новый способ занял 1219 мс, чтобы сделать 600 000 звонков (в среднем 0,002032 мс на звонок)

Некоторые заметки:

  • Мое решение использует блок нулей фиксированного размера для заполнения. Если вы ожидаете, что вам понадобится больше отступов с той или иной стороны, чем тридцать с чем-то, что я использовал, вам придется увеличить размер ... очевидно, вы можете увеличить его динамически, если потребуется.
  • Ваши комментарии выше не совсем соответствуют коду. В частности, если символ знака был возвращен, возвращаемая длина на единицу больше запрашиваемой (ваши комментарии говорят иначе). Я решил верить код, а не комментарии.
  • Я сделал мой метод статическим, поскольку он не требует состояния экземпляра. Это вещь личного вкуса - мммв.

Также: для того, чтобы имитировать поведение оригинала (но не приведенное в комментариях), это:

Если во входящем значении больше дробных цифр, чем в масштабе, генерируется исключение ArithmeticException Если во входящем значении больше целых цифр, чем вписывается (len-scale), возвращаемая строка длиннее len. Если подписано true, возвращаемая строка будет на одну длиннее len
  • Однако : если len отрицательное, оригинал возвращает строку, разделенную запятыми. Это бросает 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();
   }
4 голосов
/ 15 сентября 2009

Если для начала вы вводите данные в виде строки, зачем вам нужно конвертировать их в BigDecimal и обратно?

Похоже, было бы намного быстрее найти положение десятичной точки, сравните это с длиной / масштабом и добавьте строку соответственно.

2 голосов
/ 15 сентября 2009

Я согласен с ChssPly76 в отношении ручных манипуляций со строками.

Однако, если вы собираетесь идти по маршруту BigDecimal / DecimalFormat, вы можете подумать о том, чтобы поделиться своими DecimalFormat s вместо создания нового с каждой итерацией. Обратите внимание, что эти классы не являются поточно-ориентированными, поэтому, если вы используете несколько потоков для обработки, вам может понадобиться что-то вроде ThreadLocal хранилища для поддержки форматера для Thread.

Кстати, вы проверили это и обнаружили, что производительность неприемлема, или вы просто ищете наиболее эффективное из возможных решений? Обратите внимание, что говорит Дональд Кнут по теме ранней оптимизации .

...