Как правильно реализовать умножение для чисел с плавающей точкой (программное обеспечение FP) - PullRequest
0 голосов
/ 06 апреля 2019

Моя программа посвящена методу, которому присваиваются числа с плавающей запятой, и в этом методе я хочу умножить или добавить эти числа с плавающей запятой. Но не умножать, как a * b, я хочу разбить эти числа до их структуры, как бит для знака, 8 бит для показателя степени и остальные биты как мантисса.

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


В заголовке программы есть разбивки:

    #define  SIGN(x) (x>>31);
    #define  MANT(x) (x&0x7FFFFF);
    #define  EXPO(x) ((x>>23)&0xFF);

    #define SPLIT(x, s, m, e) do {  \
    s = SIGN(x);                    \
    m = MANT(x);                    \
    e = EXPO(x);                    \
    if ( e != 0x00 && e != 0xFF ) { \
      m |= 0x800000;                \
    }                               \
    } while ( 0 )  

    #define BUILD(x, s, m, e) do {               \
        x = (s << 31) | (e<<23) | (m&0x7FFFFF);  \
    } while ( 0 )

Основное выглядит следующим образом:

    float f = 2.3; 
    float g = 1.8; 
    float h = foo(&f, &g);

А метод расчета выглядит так:

   float foo(float *a, float *b)  {
   uint32_t ia = *(unsigned int *)a;
   uint32_t ib = *(unsigned int *)b;
   uint32_t result = 0;
   uint32_t signa, signb, signr; 
   uint32_t manta, mantb, mantr; 
   uint32_t expoa, expob, expor; 
   SPLIT(ia, signa, manta, expoa); 
   SPLIT(ib, signb, mantb, expob); 

Я уже попробовал умножение, добавив экспоненты и умножив их мантиссы следующим образом:

   expor = (expoa -127) + (expob -127) + 127;
   mantr = (manta) * (mantb);
   signr = signa ^ signb;

Возврат и восстановление нового поплавка:

   BUILD(result, signr, mantr, expor);
   return *(float *)&result;

Теперь проблема в том, что результат неправильный. мантр даже принимает очень низкий отрицательный номер (в случае, если foo получает 1,5, а 2,4 мантр принимает -838860800, а результат равен 2,0000000).

Ответы [ 3 ]

3 голосов
/ 06 апреля 2019

Вы не можете просто усечь результат умножения мантиссы, вам нужно взять top 24 бита (после использования младшей половины для округления) и перенормировать (откорректировать показатель степени).

Операции с плавающей запятой сохраняют старшие значения и биты. Наиболее значимая часть целочисленного произведения - старшие биты; младшие биты - это последующие места после десятичного числа. (Терминология: это «двоичная точка», а не «десятичная точка», потому что двоичные числа с плавающей запятой используют основание 2 (двоичное), а не 10 (десятичное).)


Для нормализованных входов неявное начальное значение 1 во входных значениях означает, что 32x32 => 64-битный uint64_t продукт, который вы используете для реализации умножения 24 × 24 => 48-битных мантисс, будет иметь старший бит в одно из 2 возможных мест, поэтому вам не нужно сканировать бит, чтобы найти его. Подойдет сравнение или однобитовый тест.

Для субнормальных входов это не гарантируется, поэтому вам необходимо проверить, где находится MSB, например, с GNU C __builtin_clzll. ( Существует множество особых случаев для обработки одного или обоих входов, которые являются ненормальными, и / или выход является ненормальным. )

См. https://en.wikipedia.org/wiki/Single-precision_floating-point_format для получения дополнительной информации о формате двоичного32 IEEE-754, включая подразумеваемое начальное 1 из значимого.

И см. Ответ @ njuffa , чтобы узнать о проверенной и работающей реализации, которая по некоторым причинам выполняет 64-битные операции в виде двух 32-битных половин вместо того, чтобы позволить C делать это эффективно.


Кроме того, return *(float *)&result; нарушает строгий псевдоним . Это безопасно только на MSVC. Используйте union или memcpy для штамповки типов в C99 / C11.

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

Эмуляция умножения двух операндов IEEE-754 (2008) binary32 немного сложнее, чем предполагает вопрос.В общем, мы должны различать следующие классы операндов: нули, субнормалы (0 <| x | <2 <sup>-126 ), нормали (2 126 ≤ | x | <2 <sup>128 ), бесконечности, NaNs.Нормалы используют смещенные показатели в [1, 254], в то время как любой из специальных классов операндов использует смещенные показатели в {0, 255}.Далее предполагается, что мы хотим реализовать умножение с плавающей точкой со всеми замаскированными исключениями с плавающей точкой и с использованием режима округления до ближайшего к четному.

Сначала мы проверяем, принадлежит ли какой-либо из аргументовв специальный класс операндов.Если это так, мы проверяем специальные случаи в последовательности.Если один из аргументов является NaN, мы возвращаем его, превращаем в Nan и возвращаем его.Если один из операндов равен нулю, мы возвращаем соответствующий ноль со знаком, , если , другой аргумент является бесконечностью, и в этом случае мы возвращаем специальный QNaN INDEFINITE, поскольку это недопустимая операция.После этого мы проверяем любой аргумент бесконечности, возвращая соответствующим образом подписанную бесконечность.Это оставляет субнормалы, которые мы нормализуем.В случае, если есть два ненормированных аргумента, нам нужно только нормализовать один из них, поскольку результатом будет либо ненормированный, либо ноль.

Умножение нормалей происходит так, как это предусмотрено в вопросе.Знак результата представляет собой исключающее ИЛИ знаков аргументов, показатель степени результата представляет собой сумму показателей степени аргументов (с поправкой на смещение показателя степени), а значение результата получается из произведениязначимые из аргументов.Нам нужен полный продукт для округления.Мы можем использовать для этого 64-битный тип или представить его с помощью пары 32-битных чисел.В приведенном ниже коде я выбрал последнее представление.Округление до ближайшего или даже четкого простого: если у нас есть связующий случай (результат находится точно посередине между ближайшими двумя binary32 числом), нам нужно округлить, если младший значащий бит мантиссы равен 1В противном случае нам нужно округлить, если старший значащий отброшенный бит (округленный бит) равен 1.

Для результата необходимо рассмотреть три случая на основе показателя степени до .к округлению: экспонента находится в нормальном диапазоне, приводит к переполнению (слишком велико по величине) или к уменьшению (слишком мало по величине).В первом случае результат является нормальным или бесконечным, если во время округления происходит переполнение.Во втором случае результат равен бесконечности.В последнем случае результатом является либо ноль (серьезный недостаток), либо субнормальный, либо наименьший нормальный (если происходит округление).

Следующий код с простой структурой для легкого тестирования с помощью случайных бобовконтрольные примеры и несколько тысяч интересных шаблонов показывают примерную реализацию ISO-C, написанную за пару часов для разумной ясности и разумной производительности.Я позволил тестовой среде работать в течение часа или около того на платформе x64, и об ошибках не сообщалось.Если вы планируете использовать код в производственной среде, вы захотите создать более строгую тестовую среду и, возможно, потребуется дополнительная настройка производительности.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define FLOAT_MANT_BITS    (23)
#define FLOAT_EXPO_BITS    (8)
#define FLOAT_EXPO_BIAS    (127)
#define FLOAT_MANT_MASK    (~((~0) << (FLOAT_MANT_BITS+1))) /* incl. integer bit */
#define EXPO_ADJUST        (1)   /* adjustment for performance reasons */
#define MIN_NORM_EXPO      (1)   /* minimum biased exponent of normals */
#define MAX_NORM_EXPO      (254) /* maximum biased exponent of normals */
#define INF_EXPO           (255) /* biased exponent of infinities */
#define EXPO_MASK          (~((~0) << FLOAT_EXPO_BITS))
#define FLOAT_SIGN_MASK    (0x80000000u)
#define FLOAT_IMPLICIT_BIT (1 << FLOAT_MANT_BITS)
#define RND_BIT_SHIFT      (31)
#define RND_BIT_MASK       (1 << RND_BIT_SHIFT)
#define FLOAT_INFINITY     (0x7f800000)
#define FLOAT_INDEFINITE   (0xffc00000u)
#define MANT_LSB           (0x00000001)
#define FLOAT_QNAN_BIT     (0x00400000)
#define MAX_SHIFT          (FLOAT_MANT_BITS + 2)

uint32_t fp32_mul_core (uint32_t a, uint32_t b)
{
    uint64_t prod;
    uint32_t expoa, expob, manta, mantb, shift;
    uint32_t r, signr, expor, mantr_hi, mantr_lo;

    /* split arguments into sign, exponent, significand */
    expoa = ((a >> FLOAT_MANT_BITS) & EXPO_MASK) - EXPO_ADJUST;
    expob = ((b >> FLOAT_MANT_BITS) & EXPO_MASK) - EXPO_ADJUST;
    manta = (a | FLOAT_IMPLICIT_BIT) & FLOAT_MANT_MASK;
    mantb = (b | FLOAT_IMPLICIT_BIT) & FLOAT_MANT_MASK;
    /* result sign bit: XOR sign argument signs */
    signr = (a ^ b) & FLOAT_SIGN_MASK;
    if ((expoa >= (MAX_NORM_EXPO - EXPO_ADJUST)) || /* at least one argument is special */
        (expob >= (MAX_NORM_EXPO - EXPO_ADJUST))) { 
        if ((a & ~FLOAT_SIGN_MASK) > FLOAT_INFINITY) { /* a is NaN */
            /* return quietened NaN */
            return a | FLOAT_QNAN_BIT;
        }
        if ((b & ~FLOAT_SIGN_MASK) > FLOAT_INFINITY) { /* b is NaN */
            /* return quietened NaN */
            return b | FLOAT_QNAN_BIT;
        }
        if ((a & ~FLOAT_SIGN_MASK) == 0) { /* a is zero */
            /* return NaN if b is infinity, else zero */
            return (expob != (INF_EXPO - EXPO_ADJUST)) ? signr : FLOAT_INDEFINITE;
        }
        if ((b & ~FLOAT_SIGN_MASK) == 0) { /* b is zero */
            /* return NaN if a is infinity, else zero */
            return (expoa != (INF_EXPO - EXPO_ADJUST)) ? signr : FLOAT_INDEFINITE;
        }
        if (((a & ~FLOAT_SIGN_MASK) == FLOAT_INFINITY) || /* a or b infinity */
            ((b & ~FLOAT_SIGN_MASK) == FLOAT_INFINITY)) {
            return signr | FLOAT_INFINITY;
        }
        if ((int32_t)expoa < (MIN_NORM_EXPO - EXPO_ADJUST)) { /* a is subnormal */
            /* normalize significand of a */
            manta = a & FLOAT_MANT_MASK;
            expoa++;
            do {
                manta = 2 * manta;
                expoa--;
            } while (manta < FLOAT_IMPLICIT_BIT);
        } else if ((int32_t)expob < (MIN_NORM_EXPO - EXPO_ADJUST)) { /* b is subnormal */
            /* normalize significand of b */
            mantb = b & FLOAT_MANT_MASK;
            expob++;
            do {
                mantb = 2 * mantb;
                expob--;
            } while (mantb < FLOAT_IMPLICIT_BIT);
        }
    }
    /* result exponent: add argument exponents and adjust for biasing */
    expor = expoa + expob - FLOAT_EXPO_BIAS + 2 * EXPO_ADJUST;
    mantb = mantb << FLOAT_EXPO_BITS; /* preshift to align result signficand */
    /* result significand: multiply argument signficands */
    prod = (uint64_t)manta * mantb;
    mantr_hi = (uint32_t)(prod >> 32);
    mantr_lo = (uint32_t)(prod >>  0);
    /* normalize significand */
    if (mantr_hi < FLOAT_IMPLICIT_BIT) {
        mantr_hi = (mantr_hi << 1) | (mantr_lo >> (32 - 1));
        mantr_lo = (mantr_lo << 1);
        expor--;
    }
    if (expor <= (MAX_NORM_EXPO - EXPO_ADJUST)) { /* normal, may overflow to infinity during rounding */
        /* combine biased exponent, sign and signficand */
        r = (expor << FLOAT_MANT_BITS) + signr + mantr_hi;
        /* round result to nearest or even; overflow to infinity possible */
        r = r + ((mantr_lo == RND_BIT_MASK) ? (mantr_hi & MANT_LSB) : (mantr_lo >> RND_BIT_SHIFT));
    } else if ((int32_t)expor > (MAX_NORM_EXPO - EXPO_ADJUST)) { /* overflow */
        /* return infinity */
        r = signr | FLOAT_INFINITY;
    } else { /* underflow */
        /* return zero, normal, or smallest subnormal */
        shift = 0 - expor;
        if (shift > MAX_SHIFT) shift = MAX_SHIFT;
        /* denormalize significand */
        mantr_lo = mantr_hi << (32 - shift) | (mantr_lo ? 1 : 0);
        mantr_hi = mantr_hi >> shift;
        /* combine sign and signficand; biased exponent known to be zero */
        r = mantr_hi + signr;
        /* round result to nearest or even */
        r = r + ((mantr_lo == RND_BIT_MASK) ? (mantr_hi & MANT_LSB) : (mantr_lo >> RND_BIT_SHIFT));
    }
    return r;
}

uint32_t float_as_uint (float a)
{
    uint32_t r;
    memcpy (&r, &a, sizeof r);
    return r;
}

float uint_as_float (uint32_t a)
{
    float r;
    memcpy (&r, &a, sizeof r);
    return r;
}

float fp32_mul (float a, float b)
{
    return uint_as_float (fp32_mul_core (float_as_uint (a), float_as_uint (b)));
}

/* Fixes via: Greg Rose, KISS: A Bit Too Simple. http://eprint.iacr.org/2011/007 */
static unsigned int z=362436069,w=521288629,jsr=362436069,jcong=123456789;
#define znew (z=36969*(z&0xffff)+(z>>16))
#define wnew (w=18000*(w&0xffff)+(w>>16))
#define MWC  ((znew<<16)+wnew)
#define SHR3 (jsr^=(jsr<<13),jsr^=(jsr>>17),jsr^=(jsr<<5)) /* 2^32-1 */
#define CONG (jcong=69069*jcong+13579)                     /* 2^32 */
#define KISS ((MWC^CONG)+SHR3)

#define ISNAN(x) ((float_as_uint (x) << 1) > 0xff000000)
#define QNAN(x)  (x | FLOAT_QNAN_BIT)

#define PURELY_RANDOM  (0)
#define PATTERN_BASED  (1)

#define TEST_MODE      (PURELY_RANDOM)

uint32_t v[8192];

int main (void)
{
    uint64_t count = 0;
    float a, b, res, ref;
    uint32_t i, j, patterns, idx = 0, nbrBits = sizeof (uint32_t) * CHAR_BIT;

    /* pattern class 1: 2**i */
    for (i = 0; i < nbrBits; i++) {
        v [idx] = ((uint32_t)1 << i);
        idx++;
    }
    /* pattern class 2: 2**i-1 */
    for (i = 0; i < nbrBits; i++) {
        v [idx] = (((uint32_t)1 << i) - 1);
        idx++;
    }
    /* pattern class 3: 2**i+1 */
    for (i = 0; i < nbrBits; i++) {
        v [idx] = (((uint32_t)1 << i) + 1);
        idx++;
    }
    /* pattern class 4: 2**i + 2**j */
    for (i = 0; i < nbrBits; i++) {
        for (j = 0; j < nbrBits; j++) {
            v [idx] = (((uint32_t)1 << i) + ((uint32_t)1 << j));
            idx++;
        }
    }
    /* pattern class 5: 2**i - 2**j */
    for (i = 0; i < nbrBits; i++) {
        for (j = 0; j < nbrBits; j++) {
            v [idx] = (((uint32_t)1 << i) - ((uint32_t)1 << j));
            idx++;
        }
    }
    /* pattern class 6: MAX_UINT/(2**i+1) rep. blocks of i zeros an i ones */
    for (i = 0; i < nbrBits; i++) {
        v [idx] = ((~(uint32_t)0) / (((uint32_t)1 << i) + 1));
        idx++;
    }
    patterns = idx;
    /* pattern class 6: one's complement of pattern classes 1 through 5 */
    for (i = 0; i < patterns; i++) {
        v [idx] = ~v [i];
        idx++;
    }
    /* pattern class 7: two's complement of pattern classes 1 through 5 */
    for (i = 0; i < patterns; i++) {
        v [idx] = ~v [i] + 1;
        idx++;
    }
    patterns = idx;

#if TEST_MODE == PURELY_RANDOM
    printf ("using purely random test vectors\n");
#elif TEST_MODE == PATTERN_BASED
    printf ("using pattern-based test vectors\n");
    printf ("#patterns = %u\n", patterns);
#endif // TEST_MODE

    do {
#if TEST_MODE == PURELY_RANDOM
        a = uint_as_float (KISS);
        b = uint_as_float (KISS);
#elif TEST_MODE == PATTERN_BASED
        a = uint_as_float ((v[KISS%patterns] & 0x7fffff) | (KISS & ~0x7fffff));
        b = uint_as_float ((v[KISS%patterns] & 0x7fffff) | (KISS & ~0x7fffff));
#endif // TEST_MODE
        res = fp32_mul (a, b);
        ref = a * b;
        /* check for bit pattern mismatch between result and reference */
        if (float_as_uint (res) != float_as_uint (ref)) {
            /* if both a and b are NaNs, either could be returned quietened */
            if (! (ISNAN (a) && ISNAN (b) &&
                   ((QNAN (float_as_uint (a)) == float_as_uint (res)) ||
                    (QNAN (float_as_uint (b)) == float_as_uint (res))))) {
                printf ("err: a=% 15.8e (%08x)  b=% 15.8e (%08x)  res=% 15.8e (%08x)  ref=%15.8e (%08x)\n",
                        a, float_as_uint(a), b, float_as_uint (b), res, float_as_uint (res), ref, float_as_uint (ref));
                return EXIT_FAILURE;
            }
        }
        count++;
        if (!(count & 0xffffff)) printf ("\r%llu", count);
    } while (1);
    return EXIT_SUCCESS;
}
0 голосов
/ 06 апреля 2019

Это намного сложнее.Посмотрите на источник библиотеки softmath (например, https://github.com/riscv/riscv-pk/blob/master/softfloat/f64_mul.c). Клонируйте ее и проанализируйте.

...