Округление double
От: SL  
Дата: 10.06.15 08:05
Оценка:
здравствуйте такой вопрос мне нужно округлить тип double до двух знаков, вот моя реализация

 double round(double dValue, int nCount)
 {
    double dSign = dValue < 0 ? -1 : 1;
    double dPow = pow((double)10., nCount);
    return  dSign * (floor((fabs(dValue)* dPow) + 0.5) / dPow);
  }


но происходит ошибка например число

    double d =  4.725;
    double d1 = Round(d, 2);
    std::cout << d << "  round " << d1;


округляется до 4.72 и вроде понятна причина число 4.725 на самом деле представляет собой 4.729999999999999, но не могу понять как побороть.
Re: Округление double
От: SL  
Дата: 10.06.15 08:22
Оценка:
Здравствуйте, SL, Вы писали:

зашел с "другой" стороны
double round(double dValue, int nCount)
 {
    double dSign = dValue < 0 ? -1 : 1;
    double dPow = pow((double)10., nCount);
    return  dSign * (ceil((fabs(dValue)* dPow)  - 0.49 ) / dPow);
  }


пока не нашел значений на которых не правильно округляется.
Re: Округление double
От: Muxa  
Дата: 10.06.15 08:25
Оценка: +1
SL>но не могу понять как побороть.
Как побороть что?
Сделать так чтобы 4.7249999999999999 округлялось до 4.73 или я тебя неправильно понял?
Re[2]: Округление double
От: _DAle_ Беларусь  
Дата: 10.06.15 08:56
Оценка:
Здравствуйте, SL, Вы писали:

round(0.00491, 2)
Re[2]: Округление double
От: SL  
Дата: 10.06.15 09:05
Оценка:
Здравствуйте, Muxa, Вы писали:

SL>>но не могу понять как побороть.

M>Как побороть что?
M>Сделать так чтобы 4.7249999999999999 округлялось до 4.73 или я тебя неправильно понял?

Ну в общем то да что бы 4.7249999999999999999 округлялось в 4.73, а 4.72499 в 4.72. а точнее что бы

результат например (2625.0 * 0.18)/100 был равен 4.73
Re[3]: Округление double
От: SL  
Дата: 10.06.15 09:05
Оценка:
Здравствуйте, _DAle_, Вы писали:

_DA>Здравствуйте, SL, Вы писали:


_DA>round(0.00491, 2)


Да согласен
Re[3]: Округление double
От: _DAle_ Беларусь  
Дата: 10.06.15 09:10
Оценка: +1
Здравствуйте, SL, Вы писали:

SL>результат например (2625.0 * 0.18)/100 был равен 4.73


У тебя округление изначально написано, в целом, правильно. Может можно поаккуратнее как-то, но основная проблема не в этом, а в том, что в round данные уже приходят с погрешностью. Надо выяснить, какова природа твоих чисел и что нужно с ними сделать.
Re: Округление double
От: bnk СССР http://unmanagedvisio.com/
Дата: 10.06.15 09:15
Оценка: +1
Здравствуйте, SL, Вы писали:

SL>здравствуйте такой вопрос мне нужно округлить тип double до двух знаков, вот моя реализация


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

Для этого существует decimal.

Это я в предположении что твои цифры — это рубли/копейки, соответственно.
Re[3]: Округление double
От: Muxa  
Дата: 10.06.15 09:20
Оценка:
SL>результат например (2625.0 * 0.18)/100 был равен 4.73
Судя по примеру работаешь с финансами. Я прав?
Если да то используй Fixed point floating арифметику, наверняка есть готовые классы, либо напиши велосипед.
Re[4]: Округление double
От: SL  
Дата: 10.06.15 09:24
Оценка:
Здравствуйте, _DAle_, Вы писали:

_DA>Здравствуйте, SL, Вы писали:


SL>>результат например (2625.0 * 0.18)/100 был равен 4.73


_DA>У тебя округление изначально написано, в целом, правильно. Может можно поаккуратнее как-то, но основная проблема не в этом, а в том, что в round данные уже приходят с погрешностью. Надо выяснить, какова природа твоих чисел и что нужно с ними сделать.


Условно говоря у меня есть число 2625, от него мне нужно посчитать значение 0.18% и его округлить до 2 знаков.
Делаю так
double d =  (2625.0  *  0.18)/100;
double d1 = round(d, 2);


и вместо 4.725, в d получаю 4.7249999999999996, тут так то все понятно так устроен double, но ведь можно же это как то учесть.
Re: Округление double
От: uzhas Ниоткуда  
Дата: 10.06.15 09:30
Оценка:
Здравствуйте, SL, Вы писали:

SL>округляется до 4.72 и вроде понятна причина число 4.725 на самом деле представляет собой 4.729999999999999, но не могу понять как побороть.


вам нужно определить степень отклонения (eps), которое считается допустимым. для этого проще всего накидать примеры входных значений и выходных (можно как раз сразу в автотесты засунуть)

пример:
exact input value, nCount, expected result

4.725, 2, 4.73
4.7248, 2, ?
4.7249, 2, ?
4.72499, 2, ?
4.7249999, 2, ?
4.72499999999, 2, ?

теперь увеличим nCount:

4.7225, 3, 4.723
4.72248, 3, ?
4.72249, 3, ?
4.722499, 3, ?
4.72249999, 3, ?
4.722499999999, 3, ?

итоговый eps может быть либо конкретным числом, либо формулой от dValue и count. я пока возьму eps = 0.1 ^ (nCount + 3) (вам нужно анализировать свои данные, чтобы принять решение о погрешности). если у dValue большой диапазон возможных значений (от 1e-9 до 1e+9), то скорее всего и его надо учитывать (ведь надо учитывать, что кол-во десятичных цифр в double ограниченно : знаки из мантиссы вытесняют знаки в дробной части)
в тестах желательно покрыть все граничные условия: (положительный/отрицательный) x (минимальный/максимальный по модулю) x (count / value)
double round(double dValue, int nCount)
{
  double dSign = dValue < 0 ? -1 : 1;
  double dPow = pow((double)10., nCount); //лучше иметь предвычисленные значения
  double eps = 0.001;
  return  dSign * (floor(fabs(dValue) * dPow + 0.5 + eps) / dPow);
}

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

пример: http://ideone.com/W7J3ES
Re[5]: Округление double
От: _DAle_ Беларусь  
Дата: 10.06.15 09:33
Оценка: +2
Здравствуйте, SL, Вы писали:

SL>и вместо 4.725, в d получаю 4.7249999999999996, тут так то все понятно так устроен double, но ведь можно же это как то учесть.


Если у тебя изначально числа с фиксированной разрядностью, то надо стараться использовать fixed point арифметику. То есть в твоем примере надо умножать не на 0.18, а на 18, и затем использовать целочисленные операции:
int d =  2625 * 18;
d = ((d + 50) / 100);
cout << d / 100 << '.' << setfill('0') << setw(2) << d % 100;
Отредактировано 10.06.2015 9:38 _DAle_ . Предыдущая версия . Еще …
Отредактировано 10.06.2015 9:35 _DAle_ . Предыдущая версия .
Re[4]: Округление double
От: SL  
Дата: 10.06.15 09:34
Оценка:
Здравствуйте, Muxa, Вы писали:

SL>>результат например (2625.0 * 0.18)/100 был равен 4.73

M>Судя по примеру работаешь с финансами. Я прав?
M>Если да то используй Fixed point floating арифметику, наверняка есть готовые классы, либо напиши велосипед.

В целом да с финансами, насчет Fixed point floating это понятно, но интересно Excel тоже использует Fixed point floating арифметику, потому что если в нем в трех ячейках вбить цифры 2625, 0.18, 100 и в четвертой указать операцию с тремя ячейками то есть (2625.0 * 0.18)/100 и указать в формате два знака после запятой, то Excel выводит 4.73.
Re[2]: Округление double
От: uzhas Ниоткуда  
Дата: 10.06.15 09:34
Оценка:
Здравствуйте, bnk, Вы писали:

bnk>Здравствуйте, SL, Вы писали:


SL>>здравствуйте такой вопрос мне нужно округлить тип double до двух знаков, вот моя реализация


bnk>Не надо использовать double для денежных рассчетов.

bnk>В некоторых местах за такие художества можно и канделябром огрести.

bnk>Для этого существует decimal.


я бы не был столь категоричен
я проводил анализ публичных библиотек для работы с decimal и оказалось, что банальный 1 * 1 выполняется на порядки медленнее, чем для double
лично я использую double для работы с деньгами именно из-за требований к производительности
Re[3]: Округление double
От: K13 http://akvis.com
Дата: 10.06.15 10:09
Оценка:
U>я проводил анализ публичных библиотек для работы с decimal и оказалось, что банальный 1 * 1 выполняется на порядки медленнее, чем для double
U>лично я использую double для работы с деньгами именно из-за требований к производительности

а профайлером прогоняли? неужели математика над "деньгами" занимают заметную долю?
обычно в основном тормозит доступ к БД, а не вычисление процентов.
думаю, decimal вместо double в худшем случае увеличит время работы на 1%.
Re: Округление double
От: PM  
Дата: 10.06.15 10:35
Оценка: +1
Здравствуйте, SL, Вы писали:

SL>здравствуйте такой вопрос мне нужно округлить тип double до двух знаков, вот моя реализация


Для чего вообще нужно округление, ведь это дополнительная потеря точности на и так уже плавающей запятой? Может быть, на самом деле нужно всего лишь выводить на экран/преобразовывать число в строку с заданным количеством знаков после запятой?

Для printf() это достигается флагами наподобие "%f.2", в IOstreams — комбинацией манипуляторов std::fixed << std::setprecision(2)

#include <iostream>
#include <iomanip>

void print(double d, unsigned digits)
{
    std::cout << "round(" << std::defaultfloat << d << ", " << digits << "): " << std::fixed << std::setprecision(digits) << d << '\n';
}

int main()
{
    print(4.725, 2);
    print(1.112222, 3);
    print(0.00001, 2);
    print(12345.6789, 5);

}


Выводит


round(4.725, 2): 4.72
round(1.1, 3): 1.112
round(1e-05, 2): 0.00
round(1.2e+04, 5): 12345.67890
Re: Округление double
От: __kot2  
Дата: 10.06.15 14:45
Оценка:
Здравствуйте, SL, Вы писали:
SL>здравствуйте такой вопрос мне нужно округлить тип double до двух знаков, вот моя реализация
когда разговор заходит про ньюансы округления, то это обычно финансы. деньги. специально для этого есть тип decimal, просто забудьть про double и все.
Re[2]: Округление double
От: Кодт Россия  
Дата: 10.06.15 15:14
Оценка: +1
Здравствуйте, __kot2, Вы писали:

__>когда разговор заходит про ньюансы округления, то это обычно финансы. деньги. специально для этого есть тип decimal, просто забудьть про double и все.


Можно просто считать в копейках.
Перекуём баги на фичи!
Re[4]: Округление double
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 10.06.15 15:20
Оценка:
Здравствуйте, K13, Вы писали:

U>>я проводил анализ публичных библиотек для работы с decimal и оказалось, что банальный 1 * 1 выполняется на порядки медленнее, чем для double

U>>лично я использую double для работы с деньгами именно из-за требований к производительности

K13>а профайлером прогоняли? неужели математика над "деньгами" занимают заметную долю?

K13>обычно в основном тормозит доступ к БД, а не вычисление процентов.
K13>думаю, decimal вместо double в худшем случае увеличит время работы на 1%.

Различия между double и decimal

Не зависит от процессора и приблизительно в 10 раз медленнее чем double

и солнце б утром не вставало, когда бы не было меня
Re[5]: Округление double
От: _DAle_ Беларусь  
Дата: 10.06.15 15:31
Оценка: +1
Здравствуйте, Serginio1, Вы писали:

S>

S>Не зависит от процессора и приблизительно в 10 раз медленнее чем double


Речь же о том, что зачастую математика в таком софте не является узким местом, поэтому хоть в 10, хоть в 100, это может вообще не повлиять на скорость работы, зато повлияет на правильность вычислений.
Да и что за ссылка вообще на decimal из С#, реализации fixed point вычислений могут быть разные в зависимости от задачи. Иногда можно и обычным long обойтись.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.