Перебрать все возможные значения с плавающей точкой, начиная с самого низкого - PullRequest
0 голосов
/ 09 февраля 2019

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

Из-за махинаций IEEE нельзя увеличивать типы с плавающей запятой (++) на их конечностях.См. этот вопрос для более подробной информации.Этот ответ гласит:

можно добавить только кратные 2 ^ (nN)

, но никогда не упоминает, что такое n.

AРешение для перебора всех возможных значений от +0.0 до + бесконечности дано в этом великом блоге .Техника предполагает использование объединения с int для обхода различных значений float.Это работает из-за следующих свойств, объясненных в посте, хотя они действительны только для положительных чисел.

  1. Смежные числа с плавающей точкой имеют смежные целочисленные представления
  2. Увеличение целочисленного представленияfloat перемещается к следующему представимому float, удаляясь от нуля

Его решение от +0,0 до + бесконечности (0.f до std::numeric_limits<float>::max()):

union Float_t {
    int32_t RawExponent() const { return (i >> 23) & 0xFF; }
    int32_t i;
    float f;
};

Float_t allFloats;
allFloats.f = 0.0f;
while (allFloats.RawExponent() < 255) {
    allFloats.i += 1;
}

Есть ли решение для -infinity до +0,0 (std::numeric_limits<float>::lowest() до 0.f)?

Я протестировал std::nextafter и std::nexttoward и не смог получитьих на работу.Может быть, это проблема MSVC?

Я бы согласился с любым видом взлома, поскольку это юнит-тест.Спасибо!

Ответы [ 3 ]

0 голосов
/ 09 февраля 2019

Паскаль Куок правильно указывает, что std::nextafter - правильное решение.У меня была проблема в другом месте в моем коде.Извините за ненужный вопрос.

#include <cassert>
#include <cmath>
#include <limits>

float i = std::numeric_limits<float>::lowest();
float hi = std::numeric_limits<float>::max();
float new_i = std::nextafterf(i, hi);
assert(i != new_i);

double d = std::numeric_limits<double>::lowest();
double hi_d = std::numeric_limits<double>::max();
double new_d = std::nextafter(d, hi_d);
assert(d != new_d);

long double ld = std::numeric_limits<long double>::lowest();
long double hi_ld = std::numeric_limits<long double>::max();
long double new_ld = std::nextafterl(ld, hi_ld);
assert(ld != new_ld);


for (float d = std::numeric_limits<float>::lowest();
        d < std::numeric_limits<float>::max();
        d = std::nextafterf(
                d, std::numeric_limits<float>::max())) {
    // Wait a lifetime?
}
0 голосов
/ 10 февраля 2019

Итерация по всем значениям float может быть выполнена с простым пониманием представления с плавающей точкой:

  • Расстояние между последовательными субнормальными значениями - это минимальное нормальное значение, умноженное на «эпсилон».Просто переберите все субнормалы, используя это расстояние в качестве приращения.
  • Расстояние между нормальными значениями наименьшего показателя равно.Пройдите через них с одинаковым шагом.
  • Для каждого показателя расстояние увеличивается в соответствии с основанием с плавающей запятой.Просто умножьте приращение на основание и пройдитесь по всем значениям для следующего показателя степени.
  • Повторяйте, пока не будет достигнута бесконечность.

Обратите внимание, что внутренний цикл в приведенном ниже коде просто:

for (; x < Limit; x += Increment)
    Test(x);

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

Этот код просматривает только неотрицательные числа.Отрицательные числа могут быть проверены отдельно таким же образом или могут разделить этот код, вставив вызов Test(-x).

#include <limits>


static void Test(float x)
{
    //  Insert unit test for value x here.
}


int main(void)
{
    typedef float T;

    static const int Radix = std::numeric_limits<T>::radix;
    static const T Infinity = std::numeric_limits<T>::infinity();

    /*  Increment is the current distance between floating-point numbers.  We
        start it at distance between subnormal numbers.
    */
    T Increment =
        std::numeric_limits<T>::min() * std::numeric_limits<T>::epsilon();

    /*  Limit is the next boundary where the distance between floating-point
        numbers changes.  We will increment up to that limit and then adjust
        the limit and increment.  We start it at the top of the first set of
        normals, which allows the first loop to increment first through the
        subnormals and then through the normals with the lowest exponent.
        (These two sets have the same step size between adjacent values.)
    */
    T Limit = std::numeric_limits<T>::min() * Radix;

    /*  Start with zero and continue until we reach infinity.
        We execute an inner loop that iterates through all the significands of
        one floating-point exponent.  Each time it completes, we step up the
        limit and increment.
    */
    for (T x = 0; x < Infinity; Limit *= Radix, Increment *= Radix)

        //  Increment x through all the significands with the current exponent.
        for (; x < Limit; x += Increment)

            //  Test with the current value of x.
            Test(x);

    //  Also test infinity.
    Test(Infinity);
}

(Этот код предполагает, что тип с плавающей запятой имеет субнормалы, и что они не являютсясбрасывается в ноль. Код также может быть легко скорректирован для поддержки этих альтернатив.)

0 голосов
/ 09 февраля 2019

Вы можете пройти все 32-битные представления, используя все значения 32-битной unsigned int.Затем вы пройдете действительно все представления, положительные и отрицательные, включая оба нуля (их два), а также все не числа представлений (NaN).Вы можете или не можете отфильтровывать представления NaN, или просто отфильтровывать сигнальные представления и оставлять не сигнализирующие. Это зависит от вашего варианта использования.

Пример:

for (uint32_t i = 0;;)
{
    float f;
    // Type punning: Force the bit representation of i into f.
    // Type punning is hard because mostly undefined in C/C++. 
    // Using memcpy() usually avoids any type punning warning.
    memcpy(&f, &i, sizeof(f));

    // Use f here.
    // Warning: Using signaling NaNs may throw exceptions or raise signals.

    i++;
    if (i == 0)
        break;
}

Вместо этого вы также можете пройти 32-битный int от -2 ** 31 до + (2 ** 31-1).Это не имеет значения.

...