Проблема, изложенная в вопросе, наводит на мысль о попытке использовать плавающую точку способами, для которых она не была разработана, но недостаточно информации для предложения альтернатив. Итак, с некоторой неохотой, вот решение проблемы, как указано.
/* IsNearestValue(x, A) returns true iff x is the double number nearest to the
nearest multiple of A', where A' is the unit fraction nearest A. (All
negative powers of 10, such as 10**-3, are unit fractions.)
For example, IsNearestValue(x, .001) returns true iff x is the result of
converting some number with three decimal digits after the decimal point
to double.
This routine presumes x/A <= 2**53.
*/
bool IsNearestValue(double x, double A)
{
// Calculate 1/A'. The result has no rounding error.
double reciprocal = std::round(1/A);
/* Find what multiple of A' x must be near. This produces an exact
result. That is, t is an integer such that t * A' = x, with
real-number arithmetic, not floating-point arithmetic.
*/
double t = std::round(x * reciprocal);
// Calculate the double nearest t/A'.
t /= reciprocal;
// Return true iff x is the double nearest t/A'.
return x == t;
}
Концепция здесь довольно проста. Во-первых, мы исправляем проблему, что A
задается как double
, но ни одно из желаемых чисел (.1, .01, .001) не может быть представлено в double
. Тем не менее, их взаимные могут, поэтому мы берем обратную и округления, чтобы получить в точности обратную величину от желаемого числа.
Затем мы умножаем x
на обратную и округляем до ближайшего целого числа. Затем мы делим это целое число на обратное, и это дает нам double
, которое x
должно быть, если это действительно double
, ближайший к некоторому кратному искомому A
.
Я не конечно, ограничение x / A ≤ 2 53 необходимо, но я не пытался доказать, что это не так, поэтому я покидаю эту границу, если нет дальнейший запрос.