Очевидное несоответствие с использованием std::floor

Я заметил нечто очень странное при использовании std::floor. Я собираю C++ на Ubuntu. Я пытаюсь найти двойное с точностью до сотых, и я проверяю случаи, когда входное значение уже округлено до ближайшей сотой, и я получаю противоречивые результаты.

Мой код:

double hundredthFloor(double value){
  double hundredth = 0.01;
  return hundredth * std::floor( value / hundredth);
}

В большинстве случаев это работает,

value = 99.87 gives output 99.87

value = 99.879 gives output 99.87

value = 0.39 gives output 0.39

Тем не мение:

value = 0.29 gives value 0.28

value = 0.47 gives value 0.46

Я перепробовал много входных значений, и пока только 0,29 и 0,47, похоже, выводят значение на один десятичный знак ниже, чем я ожидал.

Кто-нибудь видел что-нибудь подобное?

3 ответа

Причина того, что вы видите, заключается в том, что некоторые значения в вашем примере, такие как 0.01 а также 0.29, не может быть представлен точно с использованием float или же double,

Вы можете получить лучшее представление о том, что происходит, если вы напечатаете результат со многими значащими цифрами:

cout << fixed << setprecision(15) << (value / hundredth) << endl;

когда value является 0.29это печатает

28.999999999999996

Это число очень близко к 29, но floor все равно обрезает до 28.

Одним из способов решения этой проблемы является добавление эпсилона к результату деления:

return hundredth * std::floor( value / hundredth + numeric_limits<double>::epsilon());

Попробуйте следующее:

cout.precision(17);
cout << fixed << 0.29 << endl;
cout << hundredthFloor(0.29) << endl;

Вы получите что-то вроде:

0.28999999999999998
0.28000000000000003

Это потому что double не всегда может точно представлять десятичное число, подробности смотрите в этом ответе. Это означает, что иногда число будет немного больше или меньше, чем вы ожидаете; Пол не будет принимать это во внимание, и, следовательно, 0.29 будет внутренне представлен как 0.28999999... и округляется до 0.28,

Простое решение заключается в добавлении очень маленького числа, такого как numeric_limits<double>::epsilon() перед звонком std::floor:

double hundredthFloor(double value){
  double hundredth = 0.01;
  return hundredth * std::floor( value / hundredth + std::numeric_limits<double>::epsilon() );
}

Какова ваша стоимость EPS? Я всегда использую значения EPS, чтобы сначала убедиться, что мои вычисления неверны или неточности компьютера:

http://www.cplusplus.com/reference/cfloat/

Другие вопросы по тегам