C / C ++ округляет десятичные числа с определенной точностью, эффективно - PullRequest
3 голосов
/ 16 июля 2009

Я пытаюсь оптимизировать следующее. Код ниже делает это:

Если а = 0,775 и мне нужна точность 2 дп, то а => 0,78

Обычно, если последняя цифра 5, она округляет вверх следующую цифру, в противном случае это не так.

Моя проблема заключалась в том, что 0,45 не округляется до 0,5 с 1 десятичной точкой, так как значение сохраняется как 0,44999999343 .... и установленная точность округляет его до 0,4.

Вот почему setprecision вынуждено быть выше setprecision(p+10), а затем, если оно действительно заканчивается на 5, добавить небольшую сумму, чтобы правильно округлить.

После этого он сравнивает a со строкой b и возвращает результат. Проблема в том, что эта функция вызывается несколько миллиардов раз, заставляя программу сканироваться. Какие-нибудь лучшие идеи о том, как переписать / оптимизировать это и какие функции в коде настолько загружены на машине?

bool match(double a,string b,int p) { //p = precision no greater than 7dp

    double t[] = {0.2, 0.02, 0.002, 0.0002, 0.00002, 0.000002, 0.0000002, 0.00000002};

    stringstream buff;
    string temp;

    buff << setprecision(p+10) << setiosflags(ios_base::fixed) << a; // 10 decimal precision
    buff >> temp;

    if(temp[temp.size()-10] == '5')  a += t[p]; // help to round upwards

    ostringstream test;
    test << setprecision(p) << setiosflags(ios_base::fixed) << a;
    temp = test.str();

    if(b.compare(temp) == 0) return true;

    return false;
}

Ответы [ 10 ]

2 голосов
/ 16 июля 2009

Вы можете сохранить некоторые основные циклы в своем опубликованном коде, просто сделав это значение double t [] статическим, чтобы оно не выделялось снова и снова.

2 голосов
/ 16 июля 2009

Я думаю, что вы можете просто добавить 0,005 для точности к сотым, 0,0005 для тысяч и т. Д. Снимите результат с чем-то вроде "% 1.2f" (сотые, 1,3f тысячные и т. Д.) И сравнить строки. Вы должны иметь возможность вносить в таблицу или параметризировать эту логику.

2 голосов
/ 16 июля 2009

Я написал подпрограмму с целочисленным квадратным корнем, содержащую всего пару десятков строк ASM, без каких-либо вызовов API - и все же он мог делать только около 50 миллионов SqRoots / сек (это было около пяти лет назад ...) ,

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

Но если вы действительно хотите приложить усилия, чтобы ускорить его, удалите как можно больше использования API. Это может потребовать, чтобы вы выполняли задачи API вручную, а не позволяли библиотекам делать это за вас. В частности, удалите любой тип потоковой операции. Это медленнее, чем грязь в этом контексте. Возможно, вам действительно придется импровизировать там.

Единственное, что осталось сделать после этого, это заменить столько строк C ++, сколько вы можете, на пользовательские ASM, но вам придется быть перфекционистом в этом. Убедитесь, что вы в полной мере используете каждый цикл и регистр ЦП, а также каждый байт кэша ЦП и место в стеке.

Вы можете рассмотреть возможность использования целочисленных значений вместо плавающих точек, поскольку они гораздо более дружественны к ASM и намного более эффективны. Вам нужно было бы умножить число на 10 ^ 7 (или 10 ^ p, в зависимости от того, как вы решили сформировать свою логику), чтобы переместить десятичную дробь полностью вправо. Тогда вы можете спокойно конвертировать число с плавающей точкой в ​​основное целое число.

Вам придется полагаться на аппаратные средства компьютера, чтобы сделать все остальное.

<--Microsoft Specific-->
Я также добавлю, что идентификаторы C ++ (включая статические, как упоминал Донни Дебоер) доступны напрямую из блоков ASM, вложенных в ваш код C ++. Это делает встроенный ASM легким.
<--End Microsoft Specific-->

2 голосов
/ 16 июля 2009

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

1 голос
/ 16 июля 2009

Попробуйте вместо этого:

#include <cmath>

double setprecision(double x, int prec) {
    return 
        ceil( x * pow(10,(double)prec) - .4999999999999)
        / pow(10,(double)prec);
}

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

Пример того, как это работает:

2.345* 100 (10 to the 2nd power) = 234.5
234.5 - .4999999999999 = 234.0000000000001
ceil( 234.0000000000001 ) = 235
235 / 100 (10 to the 2nd power) = 2.35

.4999999999999 был выбран из-за точности для двойного c ++ в 32-битной системе. Если вы работаете на 64-битной платформе, вам, вероятно, понадобится больше девяток. Если вы увеличиваете девятки в 32-битной системе, она переполняется и округляется, а не вверх, т.е. е. 234,00000000000001 усекается до 234 в двойной (моей) 32-битной среде.

1 голос
/ 16 июля 2009

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

Вот статья по теме: http://www.theregister.co.uk/2006/08/12/floating_point_approximation/

0 голосов
/ 20 июля 2009

Старинные трюки разработчиков из темных веков фунтов, шиллингов и пенсов в старой стране.

Хитрость заключалась в том, чтобы хранить значение как целое число для полпенни. (Или какой бы ни была ваша самая маленькая единица). Тогда вся ваша последующая арифметика является простой целочисленной арифметикой, а округление и т. Д. Позаботятся о себе.

Так что в вашем случае вы храните свои данные в единицах по 200 от того, что вы считаете, делайте простые целочисленные вычисления для этих значений и делите на 200 в переменную с плавающей точкой всякий раз, когда вы хотите отобразить результат.

Я полагаю, что Boost делает библиотеку "BigDecimal" в наши дни, но ваше требование скорости выполнения, вероятно, исключило бы это в остальном превосходное решение.

0 голосов
/ 20 июля 2009

Насколько я вижу, вы проверяете, равен ли округленный по p пункт b.

Добавлена ​​замена на строку, сделать другой путь и изменить строку на удвоение - (только умножения и добавления или только дополнения, используя маленький стол) - затем вычтите оба числа и проверьте, находится ли вычитание в правильном диапазоне (если p == 1 => abs (p-a) <0,05) </p>

0 голосов
/ 17 июля 2009

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

double string_to_double(const std::string &s)
{
    std::stringstream buffer(s);
    double d = 0.0;
    buffer >> d;
    return d;
}

bool match(const std::string &guess, double answer, int precision)
{
    const static double thresh[] = { 0.5, 0.05, 0.005, 0.0005, /* etc. */ };
    const double g = string_to_double(guess);
    const double delta = g - answer;
    return -thresh[precision] < delta && delta <= thresh[precision];
}

Другая возможность - сначала округлить ответ (пока он все еще числовой) ДО преобразования его в строку.

bool match2(const std::string &guess, double answer, int precision)
{
    const static double thresh[] = {0.5, 0.05, 0.005, 0.0005, /* etc. */ };
    const double rounded = answer + thresh[precision];
    std::stringstream buffer;
    buffer << std::setprecision(precision) << rounded;
    return guess == buffer.str();
}

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

0 голосов
/ 16 июля 2009

Похоже, то, что вы пытаетесь сделать, не является настоящим округлением. 0,45 - это действительно 0,45 в двоичной записи, а 0,44999999343 - это не одно и то же.

Может быть, вам нужно сделать несколько округлений - сначала сказать 3 десятичных знака, затем до двух, затем до одного.

Вопрос в том, чего вы пытаетесь достичь? Если ваш критерий соответствия равен

abs(a-b) < 10 ** -p

вместо

...