Проверьте, если двойные даны с заданной точностью c - PullRequest
0 голосов
/ 18 февраля 2020

У меня есть вектор double. Эти числа даны с точностью, такой как 0.001, что означает, что числа даны с точностью до тысячных единиц. Например, я ожидаю иметь 123456789.012, но не 123456789.01231. На практике из-за арифметики с плавающей точкой c действительные числа больше похожи на 123456789.01199999452, что является действительным и наиболее близким представлением 123456789.012.

. на самом деле дано с точностью там должно быть. Например, я хотел бы предупредить, если у меня есть 123456789.01231, который не соответствует точности 0.001. Другой правильный вариант для меня - это найти точность заданного числа.

Пока я проверял, является ли (x - offset)/accuracy целым числом. Смещение защищает от целочисленного переполнения. На практике из-за арифметики с плавающей точкой c она не может быть целым числом, поэтому я добавил произвольный порог. Это работает, но, на мой взгляд, это не надежное решение.

dx = (x[i]-offset)/accuracy;
ix = std::round(dx);
if (std::abs(dx - ix) > 1e-5)
  throw exception("...");

В этот процесс не вовлечена ни одна строка. Числа взяты из двоичных файлов или из вычислений во время выполнения. Точность задается пользователем, и ожидается, что числа будут соответствовать этой точности. В моем предыдущем примере 123456789.012 я знаю, что это число на самом деле не существует, но 123456789.01199999452 допустимо, потому что это его лучшее представление с double. Но 123456789.01231 (на самом деле 123456789.01230999827) недопустимо, потому что это не лучшее представление округленного значения из 3 цифр.

Ответы [ 2 ]

1 голос
/ 19 февраля 2020

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

/*  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 необходимо, но я не пытался доказать, что это не так, поэтому я покидаю эту границу, если нет дальнейший запрос.

1 голос
/ 18 февраля 2020

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

#include <cmath>
#include <iostream>

bool is_number_accurate(double n, double accuracy)
{
    bool accurate = false;

    std::cout << "n: " << n << ", ";

    n = std::abs(n);
    if (n > accuracy)
    {
        // Epsilon to account for precision loss from the calculations below,
        // as well as floating point representation error.
        const double epsilon = 0.00001;

        // Remainder from quantizing n to accuracy precision
        // (i.e. quantization error)
        double rem = std::fmod(n, accuracy);

        // Normalize to 1.0. Here 0 means n exactly matches the quantized value,
        // and slightly greater than 0 means that n is slightly greater than the
        // quantized value.
        // Values close to 1.0 also mean n is close to match the quantized value,
        // but is slightly less than it.
        double error = rem / accuracy;
        std::cout << "error: " << error << ", ";

        accurate = error < epsilon || (error <= 1.0 && error > (1.0 - epsilon));
    }
    else
    {
        accurate = n == 0.0 || n == accuracy;
    }

    std::cout << "accurate: " << accurate << std::endl;

    return accurate;
}

int main()
{
    is_number_accurate(0.0, 0.001);
    is_number_accurate(0.0000000000001, 0.001);
    is_number_accurate(0.001, 0.001);
    is_number_accurate(0.01, 0.001);
    is_number_accurate(123456789.012, 0.001);
    is_number_accurate(123456789.0121, 0.001);
    is_number_accurate(123456789.0125, 0.001);
    is_number_accurate(123456789.0129, 0.001);
    is_number_accurate(123456789.013, 0.001);
    is_number_accurate(123456789.01231, 0.001);
}

Выходные данные:

n: 0, accurate: 1
n: 1e-13, accurate: 0
n: 0.001, accurate: 1
n: 0.01, error: 0, accurate: 1
n: 1.23457e+08, error: 0.999992, accurate: 1
n: 1.23457e+08, error: 0.0999936, accurate: 0
n: 1.23457e+08, error: 0.5, accurate: 0
n: 1.23457e+08, error: 0.899992, accurate: 0
n: 1.23457e+08, error: 0.999994, accurate: 1
n: 1.23457e+08, error: 0.309996, accurate: 0
...