Усечение двойной плавающей запятой на определенном количестве цифр

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

double truncate(double number_val, int n)
{
double factor = 1;
double previous = std::trunc(number_val); // remove integer portion
number_val -= previous;
for (int i = 0; i < n; i++) {
number_val *= 10;
factor *= 10;
}
number_val = std::trunc(number_val);
number_val /= factor;
number_val += previous; // add back integer portion
return number_val;
}

Обычно это прекрасно работает … но я обнаружил, что с некоторыми числами, особенно с теми, которые, кажется, не имеют точного представления в двойном, есть проблемы.

Например, если входное значение равно 2,0029, и я хочу обрезать его на пятом месте, внутренне двойник представляется как нечто где-то между 2.0028999999999999996 и 2.0028999999999999999, а усечение этого с пятым десятичным знаком дает 2.00289, что может быть с точки зрения того, как хранится число, но будет выглядеть как неправильный ответ для конечного пользователя.

Если бы я округлял вместо усечения до пятого знака после запятой, все было бы хорошо, конечно, и если бы я дал двойное число, десятичное представление которого имеет больше чем n цифр после запятой, оно тоже работает нормально, но как мне это изменить? процедура усечения, чтобы неточности из-за неточности в типе double и его десятичном представлении не влияли на результат, который видит конечный пользователь?

Я думаю, что мне может понадобиться какой-то гибрид округления / усечения, чтобы сделать эту работу, но я не уверен, как бы я это написал.

Изменить: спасибо за ответы, но, возможно, я должен уточнить, что это значение не производит вывод обязательно, но эта операция усечения может быть частью цепочки многих различных пользовательских действий над числами с плавающей запятой. Ошибки, которые накапливаются с двойной точностью по нескольким операциям, являются хорошими, но ни одна отдельная операция, такая как усечение или округление, не должна давать результат, который отличается от его фактического идеального значения более чем на половину эпсилона, где эпсилон — наименьшая представленная величина двойной точностью с текущим показателем. В настоящее время я пытаюсь переварить ссылку, предоставленную iinspectable ниже, на арифметику с плавающей запятой, чтобы посмотреть, поможет ли это мне понять, как это сделать.

Редактировать: хорошо, ссылка дала мне одну идею, что-то вроде хакерской, но она, вероятно, должна работать, которая должна поставить строку как number_val += std::numeric_limits<double>::epsilon() прямо в верхней части функции, прежде чем я начну делать что-то еще с ней. Не знаю, если есть лучший способ, хотя.

Редактировать: у меня была идея, пока я был в автобусе сегодня, которую я еще не имел возможности полностью протестировать, но она работает, округляя исходное число до 16 значащих десятичных цифр, а затем обрезая это:

double truncate(double number_val, int n)
{
bool negative = false;
if (number_val == 0) {
return 0;
} else if (number_val < 0) {
number_val = -number_val;
negative = true;
}
int pre_digits = std::log10(number_val) + 1;
if (pre_digits < 17) {
int post_digits = 17 - pre_digits;
double factor = std::pow(10, post_digits);
number_val = std::round(number_val * factor) / factor;
factor = std::pow(10, n);
number_val = std::trunc(number_val * factor) / factor;
} else {
number_val = std::round(number_val);
}
if (negative) {
number_val = -number_val;
}
return number_val;
}

Так как число с плавающей запятой двойной точности может иметь в любом случае около 16 цифр точности, это может сработать для всех практических целей, при стоимости не более одной цифры точности, которую в противном случае, возможно, поддержал бы двойное число.

Я хотел бы далее отметить, что этот вопрос отличается от предложенного выше дубликата тем, что a) здесь используется C ++, а не Java … у меня нет вспомогательного класса DecimalFormatter и b) я хочу урезать, а не round, число в данной цифре (в пределах точности, разрешенных двойным типом данных), и c) как я уже говорил ранее, результат этой функции не предполагается, что это печатная строка … это должно быть собственным числом с плавающей запятой, которое конечный пользователь этой функции может выбрать для дальнейшей обработки. Накопленные ошибки за несколько операций из-за неточности в двойном типе являются приемлемыми, но любая отдельная операция должна работать правильно в пределах точности двойного типа данных.

0

Решение

Хорошо, если я правильно понимаю, у вас есть число с плавающей запятой, и вы хотите урезать его до n цифр:

10.099999
^^      n = 2

becomes

10.09
^^

Но ваша функция усекает число до приблизительно близкого значения:

10.08999999
^^

Который затем отображается как 10.08?

Как насчет того, чтобы держать свой truncate формула, которая усекает так хорошо, как может, и использует std::setprecision а также std::fixed округлить усеченное значение до необходимого количества знаков после запятой? (Предполагая, что это std::cout вы используете для вывода?)

#include <iostream>
#include <iomanip>

using std::cout;
using std::setprecision;
using std::fixed;
using std::endl;

int main() {
double foo = 10.08995; // let's imagine this is the output of `truncate`

cout << foo << endl;                             // displays 10.0899
cout << setprecision(2) << fixed << foo << endl; // rounds to 10.09
}

Я установил демо на wandbox за это.

0

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

Я посмотрел на это. Это трудно, потому что у вас есть неточности из-за представления с плавающей запятой, а затем неточности из-за десятичной дроби. 0.1 не может быть представлен точно в двоичной плавающей запятой. Однако вы можете использовать встроенную функцию sprintf с аргументом% g, который должен точно для вас округляться.

 char out[64];
double x = 0.11111111;
int n = 3;
double xrounded;
sprintf(out, "%.*g", n, x);
xrounded = strtod(out, 0);
0

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