提问者:小点点

为什么我们将归一化分数与0.5相乘以获得IEEE754表示的意义?


我对Beej的网络编程指南第7.4节中定义的pack754()函数有一个问题。

此函数将浮点数f转换为其IEEE754表示,其中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在这段代码中有什么用途?


共2个答案

匿名用户

该代码是不正确的四舍五入尝试。

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

不正确的第一个线索是0.5ff,它表示flo,是在一个带有long double ff不规范的例程中指定flo的荒谬介绍。flo数学在函数中没有应用。

然而,添加0.5f并不意味着代码仅限于浮点(1LL

四舍五入的尝试确实有意义,因为参数是long double,目标表示更窄。添加0.5是一种常见的方法——但这里没有这样做。IMO,作者在这里没有评论0.5f暗示意图是“明显的”——不是微妙的,尽管是不正确的。

正如评论的那样,移动0.5更接近于四舍五入的正确性,但可能会错误地导致一些人认为加法是通过flo数学完成的,(它是long double数学在flo中添加一个long double乘积会导致0.5f首先被提升为long double)。

// 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 product = fnorm * (1LL<<significandbits);
long long significand = product + 0.5;  // double rounding?

产品0.5本身在截断/赋值到long long之前可能会经过四舍五入-实际上是双重四舍五入。

最好在标准库函数的C棚中使用正确的工具。

significand = llrintl(fnorm * (1ULL<<significandbits));

这种舍入的一个极端情况是意义现在太伟大了,意义,exp需要调整。正如@奈雪的茶所确定的那样,代码也有其他缺点。此外,它在-0.0上失败。

匿名用户

0.5f在代码中没有任何用途,并且可能是有害或误导的。

表达式(1LL

0.5f替换为0.0f会导致相同的行为。见鬼,完全删除该术语,因为f不规范将导致*的右侧参数无论如何都被转换为long double。这将是重写该行的更好方法:long long有意义=f规范*(long double)(1LL

旁注:pack754()的这种实现正确地处理了零(并将负零折叠为正零),但错误地处理了次常态数(错误位)、无穷大(无限循环)和NaN(错误位)。最好不要将其视为参考模型函数。