"Раунд (37,785,0.01) должен дать вам 37,79, а не 37,78."
Во-первых, нет единого мнения, что 37,79 вместо 37,78 является "правильным" ответом здесь? Тай-брейки всегда немного жесткие. Хотя в случае ничьих всегда используется округление, это, конечно, не единственный подход.
Во-вторых, это не является ситуацией разрыва связи. Числовое значение в формате IEEE binary64 с плавающей запятой равно 37,784999999999997 (приблизительно). Есть много способов получить значение 37,784999999999997, кроме того, что человек набрал значение 37,785 и получилось преобразовать его в это представление с плавающей запятой. В большинстве случаев правильный ответ - 37,78, а не 37,79.
Добавление
Рассмотрим следующие формулы Excel:
=ROUND(37785/1000,2)
=ROUND(19810222/2^19+21474836/2^47,2)
Обе ячейки будут отображать одинаковое значение, 37,79. Существует законный аргумент в пользу того, следует ли округлять 37785/1000 до 37,78 или 37,79 с точностью до двух мест. Как иметь дело с этими угловыми случаями немного произвольно, и нет единого ответа. В Microsoft даже нет единого ответа: « функция Round () не реализована согласованным образом в различных продуктах Microsoft по историческим причинам. » (http://support.microsoft.com/kb/196652) Учитывая бесконечность точная машина, Microsoft VBA будет округлять с 37,785 до 37,78 (банковский круг), в то время как Excel даст 37,79 (симметричный арифметический раунд).
Нет аргументов в пользу округления последней формулы. Это строго меньше, чем 37,785, поэтому он должен округляться до 37,78, а не 37,79. Тем не менее, Excel округляет это. Почему?
Причина в том, как реальные числа представлены в компьютере. Microsoft, как и многие другие, использует 64-битный формат IEEE с плавающей запятой. Число 37785/1000 страдает от потери точности при выражении в этом формате. Эта потеря точности не возникает при 19810222/2 ^ 19 + 21474836/2 ^ 47; это «точное число».
Я специально построил это точное число, чтобы оно имело такое же представление с плавающей запятой, что и неточное 37785/1000. То, что Excel округляет это точное значение вверх, а не вниз, является ключом к определению того, как работает функция ROUND()
в Excel: это вариант симметричного арифметического округления. Он округляется на основе сравнения с представлением углового случая с плавающей запятой.
Алгоритм на С ++:
#include <cmath> // std::floor
// Compute 10 to some positive integral power.
// Dealing with overflow (exponent > 308) is an exercise left to the reader.
double pow10 (unsigned int exponent) {
double result = 1.0;
double base = 10.0;
while (exponent > 0) {
if ((exponent & 1) != 0) result *= base;
exponent >>= 1;
base *= base;
}
return result;
}
// Round the same way Excel does.
// Dealing with nonsense such as nplaces=400 is an exercise left to the reader.
double excel_round (double x, int nplaces) {
bool is_neg = false;
// Excel uses symmetric arithmetic round: Round away from zero.
// The algorithm will be easier if we only deal with positive numbers.
if (x < 0.0) {
is_neg = true;
x = -x;
}
// Construct the nearest rounded values and the nasty corner case.
// Note: We really do not want an optimizing compiler to put the corner
// case in an extended double precision register. Hence the volatile.
double round_down, round_up;
volatile double corner_case;
if (nplaces < 0) {
double scale = pow10 (-nplaces);
round_down = std::floor (x * scale);
corner_case = (round_down + 0.5) / scale;
round_up = (round_down + 1.0) / scale;
round_down /= scale;
}
else {
double scale = pow10 (nplaces);
round_down = std::floor (x / scale);
corner_case = (round_down + 0.5) * scale;
round_up = (round_down + 1.0) * scale;
round_down *= scale;
}
// Round by comparing to the corner case.
x = (x < corner_case) ? round_down : round_up;
// Correct the sign if needed.
if (is_neg) x = -x;
return x;
}