2009-07-16 5 views
3

Я пытаюсь оптимизировать следующее. Код ниже делает это:C/C++ округление десятичных знаков с определенной точностью, эффективно

Если а = 0,775 и мне нужно Прецизионный 2 дп, то а => 0,78

В принципе, если последняя цифра 5, он округляется вверх следующая цифра, иначе она не ,

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

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

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

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; 
} 
+0

Я не уверен, что я понимаю ваши примеры наверху. Как вы получаете 0,66, округляя 0,6652 до 2 десятичных знаков? Почему вы округливаете, основываясь на последней цифре числа вместо цифры после точки прецизионного отсечения? –

+0

Правда, удалил этот пример, это было, как вы сказали. – Milan

+0

Я думаю, что проблема в постановке задачи нуждается в уточнении. Вы говорите об округлении, но код соответствует сопоставлению строкового представления с приблизительным значением с плавающей запятой. Что вы на самом деле пытаетесь сделать? Кроме того, это не похоже на любое округление, которое я видел раньше. Обычно вы округляете, если следующая цифра (превышающая желаемую точность) составляет 5 или более. Кажется, вы проверяете ровно 5. –

ответ

2

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

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

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

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

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

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

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

+0

Хотя я изначально предполагал, что умножение на 10^p будет работать, оно не (по крайней мере, не в общем). Числа с плавающей запятой по своей сути являются неточными. Когда вы храните значение в виде плавающей запятой, вы теряете информацию об исходном значении. Если Ф.П. значение 0.4449999, вы не можете разумно предположить, что это 0,45. Поэтому умножение на перемещение цифр вокруг фактически не «фиксирует» ничего - оно вводит дополнительную неточность (например, умножение на 100, которое фактически может быть сохранено как 100.0124), которое получается похоже на то, что вы ожидали, но это не общее решение , –

+0

Хорошее мышление! Смотрите мой комментарий на второй ответ Донни ДеБоера. – Giffyguy

2

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

2

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

+0

Но это сделало бы его округленным, несмотря ни на что. Я думаю, он пытается округлить, когда это уместно. – Giffyguy

+0

На самом деле, я думаю, что это работает (не думайте, что это будет быстро, хотя). Например, округлить до ближайшей сотой, 1.014 будет округлять до 1.01 (1.014 + .005 = 1.019, усечь до двух знаков после запятой), но 1.017 округлятся до 1.02 (1.017 + .005 = 1.022, усекаем до 2 десятичных знаков места). –

2

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

+0

Это отличный совет. Статические вары могут определенно ускорить работу при правильном использовании. – Giffyguy

+0

Фактически, массив является идеальным кандидатом на «статический const» – Giffyguy

+2

static const приведет к тому, что ваш массив вообще не будет существовать в конечном коде. Компилятор будет напрямую вводить отдельные значения констант, когда они используются, вместо необходимости разыменовывать указатель и выполнять арифметику указателя для доступа к массиву. – Giffyguy

-1

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

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

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

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

вместо этого?

+0

Я не думаю, что 0.45 может быть выражено в двоичной нотации. Это 9/20, а 20 - 2 * 2 * 5. Если вы не можете заставить знаменатель быть силой два после сокращения, вы не можете выразить его в двоичном формате. –

+0

Возможно. Но printf с% .10f производит 0.4500000000 за 0.45, поэтому 0.4499999934 должен быть другим. – Arkadiy

+0

Последнее слово: (gdb) p 0,45 $ 33 = 0.45000000000000001 – Arkadiy

1

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

Вот родственный статья: http://www.theregister.co.uk/2006/08/12/floating_point_approximation/

+0

Еще один отличный момент. Это то, что мы должны учитывать, учитывая, что операции с плавающей запятой нужно избегать, если код будет работать быстрее. Я вообще считаю, что это тот момент, когда термин «приближение» действительно имеет смысл. Каковы шансы на то, что число будет настолько близко к .5, что это может быть неправильным? Числа с плавающей запятой не являются точными на 100%, и вообще нет возможности обойти этот факт.Я считаю, что в этот момент мы просто должны предположить, что он будет округлять правильно 99,9% времени и будет удовлетворен этой статистикой. – Giffyguy

1

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

#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 

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

0

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

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

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

Insted изменения а к строке, сделайте другой путь и изменить строки в два раза - (только умножений и addion или только additoins используя небольшую таблицу) - то вычитать оба номера и проверьте, вычитание находится в надлежащем диапазоне (если р == 1 => abs (pa) < 0.05)

0

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

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

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

I beleive Boost делает библиотеку «BigDecimal» в наши дни, но ваше требование к скорости во время выполнения, вероятно, исключает это отличное решение.