Неэкспоненциальный форматированный float - PullRequest
3 голосов
/ 14 ноября 2009

У меня есть файл данных в формате UTF-8, который содержит тысячи чисел с плавающей запятой. В то время как это было разработано, разработчики решили опустить 'e' в экспоненциальной нотации для экономии места. Поэтому данные выглядят так:

 1.85783+16 0.000000+0 1.900000+6-3.855418-4 1.958263+6 7.836995-4
-2.000000+6 9.903130-4 2.100000+6 1.417469-3 2.159110+6 1.655700-3
 2.200000+6 1.813662-3-2.250000+6-1.998687-3 2.300000+6 2.174219-3
 2.309746+6 2.207278-3 2.400000+6 2.494469-3 2.400127+6 2.494848-3
-2.500000+6 2.769739-3 2.503362+6 2.778185-3 2.600000+6 3.020353-3
 2.700000+6 3.268572-3 2.750000+6 3.391230-3 2.800000+6 3.512625-3
 2.900000+6 3.750746-3 2.952457+6 3.872690-3 3.000000+6 3.981166-3
 3.202512+6 4.437824-3 3.250000+6 4.542310-3 3.402356+6 4.861319-3

Проблема в float.Parse () не будет работать с этим форматом. Промежуточный раствор, который у меня был, был

    protected static float ParseFloatingPoint(string data)
    {

        int signPos;
        char replaceChar = '+';

        // Skip over first character so that a leading + is not caught
        signPos = data.IndexOf(replaceChar, 1);

        // Didn't find a '+', so lets see if there's a '-'
        if (signPos == -1)
        {
            replaceChar = '-';
            signPos = data.IndexOf('-', 1);
        }

        // Found either a '+' or '-'
        if (signPos != -1)
        {
            // Create a new char array with an extra space to accomodate the 'e'
            char[] newData = new char[EntryWidth + 1];

            // Copy from string up to the sign
            for (int i = 0; i < signPos; i++)
            {
                newData[i] = data[i];
            }

            // Replace the sign with an 'e + sign'
            newData[signPos] = 'e';
            newData[signPos + 1] = replaceChar;

            // Copy the rest of the string
            for (int i = signPos + 2; i < EntryWidth + 1; i++)
            {
                newData[i] = data[i - 1];
            }

            return float.Parse(new string(newData), NumberStyles.Float, CultureInfo.InvariantCulture);
        }
        else
        {
            return float.Parse(data, NumberStyles.Float, CultureInfo.InvariantCulture);
        }
    }

Я не могу вызвать простую String.Replace (), потому что она заменит любые ведущие отрицательные знаки. Я мог бы использовать подстроки, но потом я делаю МНОГО дополнительных строк и беспокоюсь о производительности.

У кого-нибудь есть более элегантное решение для этого?

Ответы [ 5 ]

3 голосов
/ 14 ноября 2009
string test = "1.85783-16";
char[] signs = { '+', '-' };

int decimalPos = test.IndexOf('.');
int signPos = test.LastIndexOfAny(signs); 

string result = (signPos > decimalPos) ?
     string.Concat(
         test.Substring(0, signPos), 
         "E", 
         test.Substring(signPos)) : test;

float.Parse(result).Dump();  //1.85783E-16

Идеи, которые я здесь использую, гарантируют, что десятичная дробь предшествует знаку (что позволяет избежать проблем при отсутствии показателя степени), а также использование LastIndexOf () для работы сзади (гарантирует, что у нас есть показатель степени, если таковой существует) , Если есть возможность префикса «+», то первый, если бы нужно было включить || signPos < decimalPos.

Другие результаты:

"1.85783" => "1.85783"; //Missing exponent is returned clean
"-1.85783" => "-1.85783"; //Sign prefix returned clean
"-1.85783-3" => "-1.85783e-3" //Sign prefix and exponent coexist peacefully.

Согласно комментариям, тест этого метода показывает только 5% -ое снижение производительности (после избегания String.Format (), который я должен был помнить, было ужасно). Я думаю, что код намного понятнее: нужно принять только одно решение.

2 голосов
/ 14 ноября 2009

С точки зрения скорости, ваше оригинальное решение - самое быстрое, что я пробовал до сих пор (@ Godeke's - очень близкая секунда). @ Godeke's обладает высокой читабельностью, за незначительное снижение производительности. Добавьте некоторые проверки надежности, и это может быть долгосрочным путем. С точки зрения надежности, вы можете добавить это к себе так:

static char[] signChars = new char[] { '+', '-' };

static float ParseFloatingPoint(string data)
{
    if (data.Length != EntryWidth)
    {
        throw new ArgumentException("data is not the correct size", "data");
    }
    else if (data[0] != ' ' && data[0] != '+' && data[0] != '-')
    {
        throw new ArgumentException("unexpected leading character", "data");
    }

    int signPos = data.LastIndexOfAny(signChars);

    // Found either a '+' or '-'
    if (signPos > 0)
    {
        // Create a new char array with an extra space to accomodate the 'e'
        char[] newData = new char[EntryWidth + 1];

        // Copy from string up to the sign
        for (int ii = 0; ii < signPos; ++ii)
        {
            newData[ii] = data[ii];
        }

        // Replace the sign with an 'e + sign'
        newData[signPos] = 'e';
        newData[signPos + 1] = data[signPos];

        // Copy the rest of the string
        for (int ii = signPos + 2; ii < EntryWidth + 1; ++ii)
        {
            newData[ii] = data[ii - 1];
        }

        return Single.Parse(
            new string(newData),
            NumberStyles.Float,
            CultureInfo.InvariantCulture);
    }
    else
    {
        Debug.Assert(false, "data does not have an exponential? This is odd.");
        return Single.Parse(data, NumberStyles.Float, CultureInfo.InvariantCulture);
    }
}

Тесты на моем X5260 (включая время, необходимое для получения отдельных точек данных):

Code                Average Runtime  Values Parsed
--------------------------------------------------
Nothing (Overhead)            13 ms              0
Original                      50 ms         150000
Godeke                        60 ms         150000
Original Robust               56 ms         150000
1 голос
/ 16 ноября 2009

Спасибо Годеке за ваши постоянно улучшающиеся правки.

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

    protected static float ParseFloatingPoint(char[] data)
    {
        int decimalPos = Array.IndexOf<char>(data, '.');
        int posSignPos = Array.LastIndexOf<char>(data, '+');
        int negSignPos = Array.LastIndexOf<char>(data, '-');

        int signPos = (posSignPos > negSignPos) ? posSignPos : negSignPos;

        string result;
        if (signPos > decimalPos)
        {
            char[] newData = new char[data.Length + 1];
            Array.Copy(data, newData, signPos);
            newData[signPos] = 'E';
            Array.Copy(data, signPos, newData, signPos + 1, data.Length - signPos);
            result = new string(newData);
        }
        else
        {
            result = new string(data);
        }

        return float.Parse(result, NumberStyles.Float, CultureInfo.InvariantCulture);
    }

Я изменил ввод для функции со строки на char [], потому что я хотел отойти от ReadLine (). Я предполагаю, что это будет работать лучше, чем создавать много строк. Вместо этого я получаю фиксированное количество байтов из файла данных (так как он ВСЕГДА будет иметь данные шириной 11 символов), преобразовывая byte [] в char [], а затем выполняя вышеуказанную обработку для преобразования в число с плавающей точкой.

0 голосов
/ 14 ноября 2009

Почему бы просто не написать простой скрипт для переформатирования файла данных один раз, а затем использовать float.Parse()?

Вы сказали «тысячи» чисел с плавающей запятой, поэтому даже очень наивный подход закончится довольно быстро (если бы вы сказали «триллионы», я бы больше колебался), и код, который вам нужно будет запустить только один раз, будет (почти) никогда не критикуйте производительность. Конечно, для запуска потребуется меньше времени, чем для отправки вопроса в SO, и вероятность ошибки значительно меньше.

0 голосов
/ 14 ноября 2009

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

Некоторая информация здесь о подходящих выражениях:

http://www.regular -expressions.info / floatingpoint.html

...