C / C ++ округление десятичных дробей с определенной точностью, эффективно

StackOverflow https://stackoverflow.com/questions/1139424

Вопрос

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

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

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

Моя проблема заключалась в том, что 0.45 не округляется до 0.5 с 1 десятичной запятой, так как значение сохраняется как 0.44999999343....и setprecision округляет его до 0.4.

Вот почему точность настройки вынуждена быть выше 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;
}
Это было полезно?

Решение

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

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

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

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

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

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

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

Другие советы

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

Я думаю, вы можете просто добавить 0,005 для точности до сотых, 0,0005 для тысяч и т. д.snprintf результат с чем-то вроде «%1.2f» (сотые, 1.3f тысячные и т. д.) и сравните строки.Вы должны иметь возможность таблизировать или параметризовать эту логику.

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

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

#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-битной среде.

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

Вот соответствующая статья:http://www.theregister.co.uk/2006/08/12/floating_point_approximation/

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

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

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

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

Вместо изменения a на string, сделайте другой способ и измените string на double - (просто умножения и добавления или только добавления с использованием маленькой таблицы) - затем вычтите оба числа и проверьте, находится ли вычитание в правильном диапазоне (если p ==1 => abs (p-a) < 0.05)

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

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

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

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

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

Возможно, вам нужно сделать многократное округление — сначала до трех знаков после запятой, затем до двух, затем до одного.

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

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

вместо?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top