Как вычислить 32-разрядный Эпсилон с плавающей запятой?

в книге Архитектура Игрового Движка : "... предположим, мы используем переменную с плавающей запятой для отслеживания абсолютного времени игры в секундах. Как долго мы можем запускать нашу игру, прежде чем величина нашей переменной часов станет настолько большой, что добавление 1/30 секунды к ней больше не изменит ее значение? Ответ примерно 12,9 дней." Почему 12,9 дня, как его рассчитать ?

3 ответов


когда результат вычисления с плавающей запятой не может быть представлен точно, он округляется до ближайшего значения. Таким образом, вы хотите найти наименьшее значение x такое, что приращение f = 1/30 меньше половины ширины h между x и следующий по величине поплавок, что означает, что x+f будет вернуться к x.

поскольку зазор одинаков для всех элементов в одном и том же Бина известно, что x должен быть наименьший элемент в Бина, которая является степенью числа 2.

если x = 2k, потом h = 2к-23 С плавание имеет 24-битное значение. Поэтому нам нужно найти наименьшее целое число k такое, что

2к-23/2 > 1/30

что означает k > 19.09, следовательно k = 20 и x = 220 = 1048576 (в секундах).

отметим, что x / (60 × 60 × 24) = 12.14 (дней), что немного меньше того, что предлагает ваш ответ, но проверено опытным путем: В Юлия

julia> x = 2f0^20
1.048576f6

julia> f = 1f0/30f0
0.033333335f0

julia> x+f == x
true

julia> p = prevfloat(x)
1.04857594f6

julia> p+f == p
false

UPDATE: хорошо, так откуда взялся 12.9? 12.14 находится в игровом времени, а не в реальном времени: они будут расходиться из - за ошибки округления, связанной с плавающей точкой (особенно ближе к концу, когда ошибка округления на самом деле довольно велика относительно f). Насколько я знаю, нет способа вычислить это напрямую, но на самом деле довольно быстро перебирать 32-битные поплавки.

снова, в Юлии:

julia> function timestuff(f)
           t = 0
           x = 0f0
           while true
               t += 1
               xp = x
               x += f
               if x == xp
                   return (t,x)
               end
           end
       end
timestuff (generic function with 1 method)

julia> t,x = timestuff(1f0/30f0)
(24986956,1.048576f6)

x соответствует нашему результату, который мы рассчитали ранее, и t часы времени в 30-х годов второго. Преобразование в дни:

julia> t/(30*60*60*24)
9.640029320987654

что еще дальше. Так что я не знаю, откуда взялись 12,9. от...

обновление 2: я предполагаю, что 12.9 исходит из расчета

y = 4 × f / ε = 1118481.125 (в секундах)

где ε-стандартная машина Эпсилон (разрыв между 1 и следующим по величине числом с плавающей запятой). Масштабирование до дней дает 12.945. Это обеспечивает верхнюю границу на x, но это не правильный ответ, как описано выше.


Это связано с зон expressibility в плавающей представление точки. Проверьте лекция из моего универа.

по мере увеличения показателя скачок на действительно числовой линии между фактически представленными значениями увеличивается; когда показатель низкий, плотность представления высока. Чтобы привести пример, представьте десятичные числа с конечным числом значений места. дано 1.00011 и 1.0002e1 разница составляет 0.0001 между двумя значениями. Но если экспонента увеличивает 1.0001-10 1.0002-10 разница между двумя 0.000100135. Очевидно, что это становится больше по мере увеличения степени. В случае, о котором вы говорите, возможно, что скачок становится настолько большим, что увеличение не способствует округлению увеличения наименее значительного бита

интересно, что к пределам представлений точность большего типа поплавка хуже! Просто потому, что увеличение битового рисунка в мантиссе сильно скачет далее по числовой строке, когда для экспоненты доступно больше битов; как в случае double, over float


#include <iostream>
#include <iomanip>

/*
https://en.wikipedia.org/wiki/Machine_epsilon#How_to_determine_machine_epsilon
*/

typedef union
{
    long  i32;
    float f32;
} flt_32;

float float_epsilon(float nbr)
{
    flt_32 flt;
    flt.f32 = nbr;
    flt.i32++;
    return (flt.f32 - nbr);
}

int main()
{
    // How to calculate 32-bit floating-point epsilon?

    const float one{ 1 }, ten_mills{ 10e6 };
    std::cout << "epsilon for number " << one << " is:\n"
        << std::fixed << std::setprecision(25)
        << float_epsilon(one)
        << std::defaultfloat << "\n\n";

    std::cout << "epsilon for number " << ten_mills << " is:\n"
        << std::fixed << std::setprecision(25)
        << float_epsilon(ten_mills)
        << std::defaultfloat << "\n\n";


    // In book Game Engine Architecture : "..., let’s say we use a
    // floating-point variable to track absolute game time in seconds.
    // How long can we run our game before the magnitude of our clock
    // variable gets so large that adding 1/30th of a second to it no
    // longer changes its value? The answer is roughly 12.9 days."
    // Why 12.9 days, how to calculate it ?

    const float one_30th{ 1.f / 30 }, day_sec{ 60 * 60 * 24 };
    float time_sec{}, time_sec_old{};

    while ((time_sec += one_30th) > time_sec_old)
    {
        time_sec_old = time_sec;
    }

    std::cout << "We can run our game for "
        << std::fixed << std::setprecision(5)
        << (time_sec / day_sec)
        << std::defaultfloat << " days.\n";


    return EXIT_SUCCESS;
}

выводит

epsilon for number 1 is:
0.0000001192092895507812500

epsilon for number 10000000 is:
1.0000000000000000000000000

We can run our game for 12.13630 days.