Добрый день! Округление double достаточно широко обсуждается в сети, но тем не менее не смог найти объяснение результатам: Код (Text): int main() { double a=4.975; double b; b=round(a*100); //ROUNDING cout <<b<<endl; return 1; } выводит на экран 497, в то время как Код (Text): round(497.5) вернет 498 Понятно, что double не хранит точного значения, но все равно непонятно как такое получается. Спасибо, если кто натолкнет на мысль.
Pahan Что значит "непонятно". Как раз исходя из этого и понятно. 4,975 не является суммой целых степеней двойки, следовательно непредставимо в виде конечной двоичной дроби. Соответственно реально в a будет храниться не 4,975, а 4,974999... . Согласно правилам округления 497,4999... округляется до 497. А вот 497,5 является суммой целых степеней двойки: 2^8+2^7+2^6+2^5+2^4+2^0+2^-1. Поэтому будет храниться в виде точного значения. Согласно правилам округления должно быть округлено до 498.
l_inc Не только на мысль натолкнули, но и ответили подробно. Спасибо. Этот вопрос возник в результате поиска функции/алгоритма округления double до n-ого знака после запятой. Но так и не получилось найти/придумать что-то чтобы double округлялся по мат. правилам и для положительных и для отрицательных. Код c форума cplusplus.com через потоки не заработал. Все найденные мат. алгоритмы падают на отрицательных числах. Могли бы Вы и на этот вопрос подсказать ответ?
Pahan Округление именно double до n-го именно десятичного знака после запятой возможно только с определённой точностью. Если Вы допускаете ошибку, например, 10^-10 для чисел в переделах +-10^10 (только пример, т.к. лень считать интервалы для double), то вполне сойдёт способ с умножением на 10^n и последующим округлением. Тогда то же 4,975+-10^-10 вполне правомерно может быть округлено до 2 знака с результатом 4,98 равно как и с результатом 4,97. Если же нет, то Вам придётся отказаться от double и использовать собственный формат хранения десятичных дробей. Удобнее всего, наверное, будет использовать bcd с фиксированной запятой: массив десятичных цифр (по байту на каждую, где начиная, например, с десятого байта идёт дробная часть) плюс переменную, хранящую знак.
l_inc С точностью double все более менее ясно и когда нужна повышенная точность я предпочитаю использовать библиотеку gmp и ее встроенные типы. Конечно, можно сказать, что в данной задаче особая точность не нужна, но тем не менее интересно округлить число 4.975 до 2-го знака и получить 4.98. Ведь именно это число (ввожу с клавиатуры) я пытаюсь обработать и хочется получить результат совпадающий с округлением на листочке. Пока на листочке написал код: Код (Text): double roundto(double num, int to) { EPS=10e-7; // выбираем какую-то погрешность, подходящую для нашего класса задач if (num>=0) { int n = floor(num*pow(10,to)); // выделяем из числа часть нужного порядка double dlt = num*pow(10,to+1) - n*10; // получить что-то типа разницы чисел в нужном порядке. //Например, для num=4.975 dlt= 4.999999998.. if ((fabs(dlt-5)<2*EPS ) || (dlt>5))// dlt >=5 n++; res=n/pow(10,to); } else { int n = ceil(num*pow(10,to)); // выделяем из числа часть нужного порядка double dlt = num*pow(10,to+1) - n*10; // dlt=-dlt; //делаем разницу положительной. Дальше аналогично if ((fabs(dlt-5)<2*EPS ) || (dlt>5))// dlt >=5 n--; res=n/pow(10,to); } } Могли бы Вы выразить свое мнение по поводу его валидности, пожалуйста?
Pahan Я недавно тоже осмысливал похожий вопрос - числа оканчивающиеся на цифру 5 - особый случай "неустойчивого равновесия" когда микроошибка перекинет округление в ту или другую сторону, а как верно заметил l_inc погрешность преобразования между двоичной и десятичной системой в общем случае неизбежна.
Микроошибка это не основная причина. Показываю фокус, угадайте что выведет (если что, 4.5 и 7.5 представимы абсолютно точно): Код (Text): #include<stdio.h> int main(void) { int n1, n2; float x1=4.5f, x2=7.5f; _asm{ fld dword ptr [x1] fld dword ptr [x2] frndint fistp dword ptr [n2] frndint fistp dword ptr [n1] } printf("N1=%d, N2=%d.\n", n1, n2); return 0; } Просто по умолчанию в FPU (и не только) если дробная часть 0.5 - чётные округляются вниз, а нечётные вверх.
Pahan Не понятно, зачем придавать "особый статус" именно значению dlt=5. Если ты задаешь некое значение погрешности EPS, полагая, что все различия <= EPS обусловлены не суперточностью ввода числа юзером, а погрешностями преобразования, то достаточно просто добавить это EPS в стандартный (не "банковский") алгоритм округления: Код (Text): double d = Pow(10, to); if (num >=0) { return floor(num*d+0.5+EPS)/d; } else { return ceil(num*d-0.5-EPS)/d; }
leo Меня в этом способе интересует следующее: Пусть есть число a равное 0.7 и пусть оно хранится как меньше a+EPS. Например: 0.7000...0001 (как верхняя граница возможной записи числа. Может 0.7 и хранится как 0.6999999999..., неважно, главное что принципиально такая ситуация возможна). Тогда 0.700000001 + EPS = 0.7+2EPS - а это уже будет следующее "различимое" число. Тогда получается, что при подборе EPS нужно считать не что обусловлены погрешностью хранения чисел. , а все различия <= 2*EPS обусловлены погрешностью хранения чисел.
1*EPS или 2*EPS - это все "казуистика", т.к. в данном сл.можно исходить только из того, с каким макс.числом дробных знаков могут быть у тебя входные числа и соотв-но брать значение EPS с запасом, чтобы не ломать голову над 1*EPS или 2*EPS. Ну а если ты ничего не можешь сказать о макс.точности вх.чисел (т.е. если она может быть сравнима с точностью double), то и смысла во всех этих "хитроумных" округлениях - нет.
cppasm Ну знаете ли фокусы FPU — это вообще не причина. Есть стандарт языка. И его абсолютно не заботит, что там за режим округления у какого-то x87 FPU.
l_inc А в стандарте C\C++ разве есть round ? Вроде только ceil и floor, которые трактуются однозначно, а round может потому и нет, что неизвестно, чьи "фокусы" лучше - банковские\IEEEшные\FPUшные или школьные\арифметические
Мда, странно - значит в "крутом" С\С++ утвердили "школьные" правила округления, а в "школьных" дельфи\паскале, VB и т.п. наоборот - "крутые" банковские ?!
Его не заботит, если использовать round() из C99, для которого чётко сказано что округление идёт от нуля. Тут же самописный round(), и приколы FPU очень даже могут влиять.