为什么该十进制32值没有预期的位模式?

时间:2019-07-13 07:22:34

标签: c gcc decimal-float

我试图在amd64盒上的GCC(7.4.0)中解构一个32位十进制浮点值,但没有得到预期的结果。我已按照https://en.wikipedia.org/wiki/Decimal32_floating-point_format的说明进行操作,并在https://github.com/gcc-mirror/gcc/tree/master/libdecnumber

处验证了代码

但是由于某些原因,我一直得到奇怪的结果:

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

static uint8_t exponent_msbs[] =
{
    0, 0, 0, 0, 0, 0, 0, 0,
    1, 1, 1, 1, 1, 1, 1, 1,
    2, 2, 2, 2, 2, 2, 2, 2,
    0, 0, 1, 1, 2, 2, 255, 255
};

static uint8_t coefficient_msbs[] =
{
    0, 1, 2, 3, 4, 5, 6, 7,
    0, 1, 2, 3, 4, 5, 6, 7,
    0, 1, 2, 3, 4, 5, 6, 7,
    8, 9, 8, 9, 8, 9, 255, 255
};


static void print_bits(uint32_t value)
{
    printf("Binary:    ");
    for(int i = 31; i >= 0; i--)
    {
        printf("%d", (value>>i)&1);
    }
    printf("\n");
}

#define shift_sign 31
#define shift_combination 26
#define width_combination 5
#define shift_exponent 20
#define width_exponent 6
#define width_coefficient 20
#define bias 101

static const uint32_t mask_combination = (1<<width_combination) - 1;
static const uint32_t mask_exponent = (1<<width_exponent) - 1;
static const uint32_t mask_coefficient = (1<<width_coefficient) - 1;


void print_decimal32(_Decimal32 dvalue)
{
    uint32_t uvalue = 0;
    memcpy((uint8_t*)&uvalue, (uint8_t*)&dvalue, sizeof(uvalue));

    uint32_t combination = (uvalue>>shift_combination) & mask_combination;
    uint32_t sign = uvalue>>shift_sign;
    uint32_t exponent_hi = exponent_msbs[combination];
    uint32_t coefficient_hi = coefficient_msbs[combination];
    uint32_t exponent_lo = (uvalue>>shift_exponent)&mask_exponent;
    uint32_t coefficient_lo = uvalue & mask_coefficient;

    int32_t exponent = (exponent_hi << width_exponent) + exponent_lo - bias;
    uint32_t coefficient =  (coefficient_hi << width_coefficient) + coefficient_lo;

    printf("Hex:       %08x\n", uvalue);
    print_bits(uvalue);
    printf("s %x, comb %x, eh %x, el %x, ch %x, cl %x, e %x, c %x\n",
        sign, combination, exponent_hi, exponent_lo,
        coefficient_hi, coefficient_lo, exponent, coefficient);
    printf("Result: %d x 10^%d\n", coefficient, exponent);
}

#define PRINT_DECIMAL32(VALUE) \
    printf("Decimal32: " #VALUE "\n"); \
    print_decimal32(VALUE)

int main()
{
    PRINT_DECIMAL32(1);
    return 0;
}

运行此命令将产生以下输出:

Decimal32: 1
Hex:       32800001
Binary:    00110010100000000000000000000001
s 0, comb c, eh 1, el 28, ch 4, cl 1, e 3, c 400001
Result: 4194305 x 10^3

所以指数偏离3,系数MSB为4而不是0。我错过了什么吗?

编辑:为清楚起见,在DPD和BID中的十进制浮点值1应该被编码为0 [01 000] 100101 00000000000000000001(0x22500001)。指数应为0(减去101偏差后),系数应为1(而非0x400001)。

1 个答案:

答案 0 :(得分:4)

1。

static const uint32_t mask_combination = (2<<width_combination) - 1;
static const uint32_t mask_exponent = (2<<width_exponent) - 1;
static const uint32_t mask_coefficient = (2<<width_coefficient) - 1;

这些无效。 mask_combination应该是0b11111,而不是0b111111。您正在做

(0b10 << 5) - 0b1 =
0b1000000 - 0b1 =
^^^^^^^^^^ this is seven bits, not six
0b111111
^^^^^^^^ - this is six bits, should be 5 for combination

您应该:

static const uint32_t mask_combination = (1<<width_combination) - 1;
static const uint32_t mask_exponent = (1<<width_exponent) - 1;
static const uint32_t mask_coefficient = (1<<width_coefficient) - 1;
  1. 旁注:使用uint32_t打印说明符打印%d可能是未定义的行为。

  2. 我将在这里发布我的代码,也许会对其他人有所帮助:

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

static uint8_t exponent_msbs[] =
{
    0, 0, 0, 0, 0, 0, 0, 0,
    1, 1, 1, 1, 1, 1, 1, 1,
    2, 2, 2, 2, 2, 2, 2, 2,
    0, 0, 1, 1, 2, 2, 255, 255
};

static uint8_t coefficient_msbs[] =
{
    0, 1, 2, 3, 4, 5, 6, 7,
    0, 1, 2, 3, 4, 5, 6, 7,
    0, 1, 2, 3, 4, 5, 6, 7,
    8, 9, 8, 9, 8, 9, 255, 255
};


static void print_bits(const char *pre, 
  uint32_t value, int len, const char *post)
{
  printf("%s", pre);
  for(int i = (len - 1); i >= 0; i--) {
    printf("%d", (value>>i)&1);
  }
  printf("%s", post);
}

#define shift_sign 31
#define shift_combination 26
#define width_combination 5
#define shift_exponent 20
#define width_exponent 6
#define width_coefficient 20
#define bias 101

static const uint32_t mask_combination = (1<<width_combination) - 1;
static const uint32_t mask_exponent = (1<<width_exponent) - 1;
static const uint32_t mask_coefficient = (1<<width_coefficient) - 1;

void print_decimal32(_Decimal32 dvalue)
{
    uint32_t uvalue = 0;
    memcpy((uint8_t*)&uvalue, (uint8_t*)&dvalue, sizeof(uvalue));

    uint32_t combination = (uvalue>>shift_combination) & mask_combination;
    uint32_t sign = uvalue>>shift_sign;
    uint32_t exponent_hi = exponent_msbs[combination];
    uint32_t coefficient_hi = coefficient_msbs[combination];
    uint32_t exponent_lo = (uvalue>>shift_exponent)&mask_exponent;
    uint32_t coefficient_lo = uvalue & mask_coefficient;

    int32_t exponent = (exponent_hi << width_exponent) + exponent_lo - bias;
    uint32_t coefficient =  (coefficient_hi << width_coefficient) + coefficient_lo;

    printf("Hex:       %08x\n", uvalue);
    print_bits("Binary: ", uvalue, 32, "\n");
    printf("s %x, comb %x, eh %x, el %x, ch %x, cl %x, e %x, c %x\n",
        sign, combination, exponent_hi, exponent_lo,
        coefficient_hi, coefficient_lo, exponent, coefficient);
    printf("Result: %d x 10^%d\n", coefficient, exponent);
}

void BIS_print_decimal32(_Decimal32 dvalue)
{
    uint32_t uvalue = 0;
    // static_assert(sizeof(_Decimal32) == sizeof(uint32_t), "");
    memcpy(&uvalue, &dvalue, sizeof(uvalue));

    uint32_t sign = (uvalue >> 31) & 1;
    uint32_t combination = (uvalue >> (20 + 6)) & 0b11111;
    uint32_t exponent = (uvalue >> (20)) & 0b111111;
    uint32_t coefficient = uvalue & 0b111111111111111111;

    // binary integer significant field
    uint32_t two_bits_after_the_sign_bit = (uvalue >> (32 - 3)) & 3;
    uint32_t significand = 0;
    // If the 2 bits after the sign bit are "00", "01", or "10",
    if (two_bits_after_the_sign_bit != 0b11) {
        // then the exponent field consists of the 8 bits following the sign bit,
        exponent = (uvalue >> (32 - 9)) & 0xff;
        exponent -= 101;
        // and the significand is the remaining 23 bits, with an implicit leading 0 bit: 
        significand = uvalue & 0b111111111111111111111;
    } else {
        // TODO:
        assert(0);
        abort();
        // If the 2 bits after the sign bit are "11", 
        // then the 8-bit exponent field is shifted 2 bits to the right
        // (after both the sign bit and the "11" bits thereafter), 
        // and the represented significand is in the remaining 21 bits. 
        // In this case there is an implicit (that is, not stored) leading 3-bit sequence "100" 
        // in the true significand. 
    }

    // TODO: handle NAN and INF
    uint32_t four_bits_after_the_sign_bit = (uvalue >> (32 - 5)) & 0xf;
    if (four_bits_after_the_sign_bit == 0b1111) {
        assert(0);
        abort();
    }

    printf("value = %+d * 10^%d * %d\n",
        (int)sign,
        (int)exponent,
        (int)significand
    );
}

int declet_to_decimal(uint32_t declet) {
    uint32_t b3 = (declet >> 3) & 1;
    if (b3 == 0) {
        uint32_t b9b8b7 = (declet >> 7) & 0b111;
        uint32_t b6b5b4 = (declet >> 4) & 0b111;
        uint32_t b2b1b0 = (declet >> 0) & 0b111;
        return b9b8b7 * 100 + b6b5b4 * 10 + b2b1b0;
    }
    // TODO: Densely packed decimal encoding rules[4]
    assert(0);
    abort();
    return 0;
}

void DPD_print_decimal32(_Decimal32 dvalue)
{
    uint32_t uvalue = 0;
    // static_assert(sizeof(_Decimal32) == sizeof(uint32_t), "");
    memcpy(&uvalue, &dvalue, sizeof(uvalue));

    uint32_t sign = (uvalue >> 31) & 1;
    uint32_t combination = (uvalue >> (20 + 6)) & 0b11111;
    uint32_t exponent = (uvalue >> (20)) & 0b111111;
    uint32_t coefficient = uvalue & 0b111111111111111111;

    // TODO: handle NAN and INF
    uint32_t four_bits_after_the_sign_bit = (uvalue >> (32 - 5)) & 0xf;
    if (four_bits_after_the_sign_bit == 0b1111) {
        assert(0);
        abort();
    }

    // Densely packed decimal significand field
    uint32_t two_bits_after_the_sign_bit = (uvalue >> (32 - 3)) & 3;
    uint32_t leading_decimal_digit = 0;
    // If the 2 bits after the sign bit are "00", "01", or "10",
    if (two_bits_after_the_sign_bit != 0b11) {
        // If the first two bits after the sign bit are "00", "01", or "10", 
        // then those are the leading bits of the exponent,
        exponent = (two_bits_after_the_sign_bit << 6) | exponent;
        exponent -= 101;
        // and the three bits after that are interpreted as the leading decimal digit (0 to 7):
        uint32_t three_bits_after_the_two_bits_after_sign_bit = (uvalue >> (32 - 6)) & 0b111; 
        leading_decimal_digit = three_bits_after_the_two_bits_after_sign_bit;
        printf("leading_decimal_digit: %d\n", leading_decimal_digit);
    } else {
        // TODO:
        assert(0);
        abort();
    }

    uint32_t significand = coefficient;
    uint32_t declet1 = (significand >> 10) & 0b1111111111;
    uint32_t declet2 = significand & 0b1111111111;

    uint32_t decimal1 = declet_to_decimal(declet1);
    uint32_t decimal2 = declet_to_decimal(declet2);

    uint32_t truesignificand = 
        leading_decimal_digit * 1000000 + 
        decimal1 * 1000 + 
        decimal2;


    printf("value = %+d * 10^%d * %d\n",
        (int)sign,
        (int)exponent,
        (int)truesignificand
    );
}

#define PRINT_DECIMAL32(VALUE)  do{ \
    printf("==> Decimal32: " #VALUE " <== \n"); \
    print_decimal32(VALUE); \
    printf("# BIS_print_decimal32\n"); \
    BIS_print_decimal32(VALUE); \
    printf("# DPD_print_decimal32\n"); \
    DPD_print_decimal32(VALUE); \
    printf("\n\n"); \
    }while(0)

int main()
{
    PRINT_DECIMAL32(1);
    PRINT_DECIMAL32(2);
    PRINT_DECIMAL32(3);
    return 0;
}
  1. 在对gcc实现进行了一些试验之后,gcc使用"Binary integer significand field"格式,而不是“密集包装的十进制有效字段”格式。这就是您的计算不正确的原因。但是,我找不到任何有关它的参考或文档,因此它是基于经验数据的。如果以后的读者发现可以确保gcc对_Decimal32数字使用此实现的参考,请随时编辑此帖子。