我有一个关于Section 7.4 of Beej's Guide to Network Programming中定义的pack754()函数的问题。
此函数将浮点数f转换为其ieee 754表示形式,其中bits是表示该数的总位数,expbits是仅表示指数的位数。
我只关心单精度浮点数,所以对于这个问题,bits被指定为32expbits被指定为8。这意味着23位用于存储有效位(因为一位是符号位)。
我的问题是关于这行代码。

    significand = fnorm * ((1LL<<significandbits) + 0.5f);

+ 0.5f在这段代码中的作用是什么?
下面是使用此函数的完整代码。
#include <stdio.h>
#include <stdint.h> // defines uintN_t types
#include <inttypes.h> // defines PRIx macros

uint64_t pack754(long double f, unsigned bits, unsigned expbits)
{
    long double fnorm;
    int shift;
    long long sign, exp, significand;
    unsigned significandbits = bits - expbits - 1; // -1 for sign bit

    if (f == 0.0) return 0; // get this special case out of the way

    // check sign and begin normalization
    if (f < 0) { sign = 1; fnorm = -f; }
    else { sign = 0; fnorm = f; }

    // get the normalized form of f and track the exponent
    shift = 0;
    while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
    while(fnorm < 1.0) { fnorm *= 2.0; shift--; }
    fnorm = fnorm - 1.0;

    // calculate the binary form (non-float) of the significand data
    significand = fnorm * ((1LL<<significandbits) + 0.5f);

    // get the biased exponent
    exp = shift + ((1<<(expbits-1)) - 1); // shift + bias

    // return the final answer
    return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}

int main(void)
{
    float f = 3.1415926;
    uint32_t fi;

    printf("float f: %.7f\n", f);

    fi = pack754(f, 32, 8);
    printf("float encoded: 0x%08" PRIx32 "\n", fi);

    return 0;
}

+ 0.5f在这段代码中有什么作用?

最佳答案

该代码是一个不正确的舍入尝试。

long double fnorm;
long long significand;
unsigned significandbits
...
significand = fnorm * ((1LL<<significandbits) + 0.5f);  // bad code

不正确的第一条线索是f0.5f,这表明float,是在带有floatlong double f的例程中指定fnorm的无意义介绍。float数学在函数中没有应用。
然而,添加0.5f并不意味着代码仅限于float中的数学。请参见(1LL<<significandbits) + 0.5f,它可能允许更高精度的中间结果,并且在测试中欺骗了代码作者。
舍入尝试确实有意义,因为参数FLT_EVAL_METHOD并且目标表示更窄。添加long double是一种常见的方法-但这里并没有这样做。在国际海事组织,作者在这里没有对0.5发表评论,这暗示其意图是“明显的”——不是微妙的,尽管是不正确的。
commented时,移动0.5f更接近于取整的正确值,但可能会误导某些人认为加法是用0.5数学完成的(这是float数学将long double乘积添加到long double导致float首先被提升为0.5f)。
// closer to rounding but may mislead
significand = fnorm * (1LL<<significandbits) + 0.5f;

// better
significand = fnorm * (1LL<<significandbits) + 0.5L; // or 0.5l or simply 0.5

若要舍入,不调用首选的long double舍入例程(如<math.h>),添加显式类型0.5仍然是舍入的弱尝试。它很弱,因为它在很多情况下都是不正确的。+0.5技巧依赖于精确的总和。
考虑
long double product = fnorm * (1LL<<significandbits);
long long significand = product + 0.5;  // double rounding?

rintl(), roundl(), nearbyintl(), llrintl()本身可能在截断/赋值到product + 0.5之前经过舍入-实际上double rounding
最好在标准库函数的c库中使用正确的工具。
significand = llrintl(fnorm * (1ULL<<significandbits));

在这种舍入的情况下仍然存在一个角点,此时long long太大,需要调整。正如@Nayuki所指出的,代码也有其他不足之处。而且,它在significand上失败。

关于c - 为什么我们将标准化分数乘以0.5以获得IEEE 754表示的有效位数?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40099056/

10-11 21:09