Как работает настройка точности с плавающей точкой

Я видел много ответов о том, как установить точность для чисел с плавающей запятой, и везде, где мы делаем что-то вроде ниже:

double RoundDouble(double doValue,int nPrecision)
{
return (floor((doValue*pow(10,nPrecision)+0.5))/pow(10,nPrecision));
}

Я не мог понять, как умножение и деление на почти равные числа будут правильно устанавливать точность? Кто-нибудь может объяснить подробно

2

Решение

Это просто использование округления целых чисел для достижения уловки, которую мы все выучили в школе, для отсечения в некотором количестве десятичных разрядов: возьмите первые 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;
}
0

Другие решения

Я не реализовал это выше, но если мы отладим его с помощью некоторого примера ввода, произойдет нечто подобное:

// 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*/
}
0

Давайте сделаем это шаг за шагом.

  1. x = doValue * pow(10, nPrecision)nPrecision цифры сдвинуты в неотъемлемую часть, остальные остаются во фрактальной части;
  2. y = floor(x + 0.5) — округлить до неотъемлемой части (если x неотрицательно);
  3. z = y / pow(10, nPrecision) — сдвиг nPrecision цифры обратно к фрактальной части.
0

Это не так. Плавающая точка не имеет десятичных разрядов. У него есть двоичные разряды, и они несоизмеримы с десятичными разрядами. Все, что он делает, это обеспечивает приближение.

Для доказательства см. Вот.

Если вы хотите точно знать десятичные разряды, вы должны использовать десятичное число.

0
По вопросам рекламы [email protected]