Округление до точности IEEE 754, но с сохранением двоичного формата - PullRequest
0 голосов
/ 21 апреля 2019

Если я преобразую десятичное число 3120.0005 в плавающее (32-разрядное) представление, число округляется до 3120.00048828125.

Предположим, что мы используем число с фиксированной точкой со шкалой 10 ^ 12, затем 1000000000000 = 1,0 и 3120000500000000 = 3120.0005.

Какой будет формула / алгоритм для округления до ближайшей точности IEEE 754 для получения 3120000488281250? Мне также нужен способ получить результат округления (3120000732421875).

Ответы [ 2 ]

2 голосов
/ 21 апреля 2019

Если вы поделите на десятичный коэффициент масштабирования, вы найдете ближайшее представимое число с плавающей точкой. Для округления в другом направлении можно использовать std::nextafter:

#include <float.h>
#include <math.h>
#include <stdio.h>

long long scale_to_fixed(float f)
{
    float intf = truncf(f);
    long long result = 1000000000000LL;
    result *= (long long)intf;
    result += round((f - intf) * 1.0e12);
    return result;
}

/* not needed, always good enough to use (float)(n / 1.0e12) */
float scale_from_fixed(long long n)
{
    float result = (n % 1000000000000LL) / 1.0e12;
    result += n / 1000000000000LL;
    return result;
}

int main()
{
    long long x = 3120000500000000;
    float x_reduced = scale_from_fixed(x);
    long long y1 = scale_to_fixed(x_reduced);
    long long yfloor = y1, yceil = y1;
    if (y1 < x) {
        yceil = scale_to_fixed(nextafterf(x_reduced, FLT_MAX));
    }
    else if (y1 > x) {
        yfloor = scale_to_fixed(nextafterf(x_reduced, -FLT_MAX));
    }

    printf("%lld\n%lld\n%lld\n", yfloor, x, yceil);
}

Результаты:

3120000488281250

3120000500000000

3120000732421875

1 голос
/ 21 апреля 2019

Для обработки значений как float, масштабированных на 1e12, и вычисления следующей большей степени двух, например, "rounding up (3120000732421875)", ключом является понимание того, что вы ищете следующую большую степень двух из 32-битного представления x / 1.0e12. Хотя вы можете математически получить это значение, значение union между float и unsigned (или uint32_t) обеспечивает прямой способ интерпретации сохраненного 32-разрядного значения для числа с плавающей запятой в качестве значения без знака. 1

Простым примером использования объединения prev для хранения уменьшенного значения x и отдельного экземпляра next, содержащего значение без знака (+1), может быть:

#include <stdio.h>
#include <inttypes.h>

int main (void) {

    uint64_t x = 3120000500000000;
    union {                         /* union between float and uint32_t */
        float f;
        uint32_t u;
    } prev = { .f = x / 1.0e12 },   /* x reduced to float, pwr of 2 as .u */
      next = { .u = prev.u + 1u };  /* 2nd union, increment pwr of 2 by 1 */

    printf ("prev : %" PRIu64 "\n   x : %" PRIu64 "\nnext : %" PRIu64 "\n", 
            (uint64_t)(prev.f * 1e12), x, (uint64_t)(next.f * 1e12));
}

Пример использования / Вывод

$ ./bin/pwr2_prev_next
prev : 3120000488281250
   x : 3120000500000000
next : 3120000732421875

Примечания:

1. В качестве альтернативы вы можете использовать указатель на char для хранения адреса типа с плавающей запятой и интерпретации 4-байтового значения, хранящегося в этом месте как unsigned без нарушения C11 Standard - §6.5 Выражения (p6,7) ( «Строгое правило алиасинга» ), но использование union является предпочтительным .

...