Я видел много ответов о том, как установить точность для чисел с плавающей запятой, и везде, где мы делаем что-то вроде ниже:
double RoundDouble(double doValue,int nPrecision)
{
return (floor((doValue*pow(10,nPrecision)+0.5))/pow(10,nPrecision));
}
Я не мог понять, как умножение и деление на почти равные числа будут правильно устанавливать точность? Кто-нибудь может объяснить подробно
Это просто использование округления целых чисел для достижения уловки, которую мы все выучили в школе, для отсечения в некотором количестве десятичных разрядов: возьмите первые N цифр после десятичной дроби и округлите самую правую одну вверх или вниз.
Если вы подумаете об этом при округлении 12,3456 до 2 десятичных знаков, вы, естественно, ожидаете, что результатом будет 12,35, потому что «4» — это самая правая из двух цифр и округляется до «5», что следует.
Теперь, чтобы достичь этого с математикой мы используем floor
для достижения округления (на самом деле вы можете использовать std::round
вместо). Но это приведет нас к целому числу, и мы потеряем всю дробную часть.
Чтобы избежать этого, сначала умножим на 100, переместив все интересные части в целое число:
1234.56
Если вы округлите это число до ближайшего целого значения либо std::floor(x+0.5)
или же std::round(x)
, тогда вы получите:
1235.0
Наконец, разделив это на 100, вы получите округленное число (да, помните, мы округлили его) до двух десятичных знаков:
12.35
Надеюсь, теперь вы видите, что происходит с призывом pow
, Подняв 10 к власти nPrecision
мы получаем коэффициент масштабирования, который обеспечит столько десятичных знаков после округления при использовании этого трюка. В этом случае мы хотели 2, и pow(10,2)
это 100
Я взял на себя смелость очистить вашу функцию для удобства чтения:
double RoundDouble(double doValue, int nPrecision)
{
double scale_factor = pow(10.0, static_cast<double>(nPrecision));
return std::round(doValue * scale_factor) / scale_factor;
}
Я не реализовал это выше, но если мы отладим его с помощью некоторого примера ввода, произойдет нечто подобное:
// say we have 5.89162 and we want it to 2 decimal places 5.89 so
RoundDouble(5.89162,2)
{
return (floor(5.89162*pow(10,2)+0.5))/pow(10,2);
/* which will look like
floor((5.89162x100+0.5)/100)
floor(589.662/100)
floor(5.89662)
and floor function will bound it to 5 it means the output will be 5 instead of 5.89*/
}
Давайте сделаем это шаг за шагом.
x = doValue * pow(10, nPrecision)
— nPrecision
цифры сдвинуты в неотъемлемую часть, остальные остаются во фрактальной части;y = floor(x + 0.5)
— округлить до неотъемлемой части (если x
неотрицательно);z = y / pow(10, nPrecision)
— сдвиг nPrecision
цифры обратно к фрактальной части.Это не так. Плавающая точка не имеет десятичных разрядов. У него есть двоичные разряды, и они несоизмеримы с десятичными разрядами. Все, что он делает, это обеспечивает приближение.
Для доказательства см. Вот.
Если вы хотите точно знать десятичные разряды, вы должны использовать десятичное число.