C# 使用与提供的解包功能相反的方式将浮点值打包为1字节?

C# 使用与提供的解包功能相反的方式将浮点值打包为1字节?,c#,floating-point,C#,Floating Point,我有一个函数,它将存储在字节中的浮点值解包,并将其转换为浮点值。函数如下所示: private double unpack(byte count) { byte xor = (byte)(count ^ 0xFF); ushort mantissa = (ushort)(xor & 0xF8); int exponent = xor & 0x07; if (xor == 0x08) { return 0; }

我有一个函数,它将存储在字节中的浮点值解包,并将其转换为浮点值。函数如下所示:

private double unpack(byte count)
{
    byte xor = (byte)(count ^ 0xFF);

    ushort mantissa = (ushort)(xor & 0xF8);
    int exponent = xor & 0x07;

    if (xor == 0x08)
    {
        return 0;
    }
    else if (exponent == 0)
    {
        return xor / 512.0;
    }
    else
    {
        return ((mantissa | 0x100) << (exponent - 1)) / 512.0;
    }
}
private双重解包(字节计数)
{
字节xor=(字节)(计数^0xFF);
ushort尾数=(ushort)(xor&0xF8);
int指数=xor&0x07;
如果(xor==0x08)
{
返回0;
}
如果(指数=0),则为else
{
返回xor/512.0;
}
其他的
{
return((尾数| 0x100)这里有一个C(我不知道C#)的解决方案,带有注释和一个测试程序。
Encode
例程应该舍入到最接近的可表示值,但程序只测试完全可表示的值

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


typedef unsigned char byte;
typedef uint16_t ushort;


//  This is the original decoding algorithm with comments inserted.
static double unpack(byte count)
{
    //  Complement the bits.  The purpose for this is unknown.
    byte xor = (byte)(count ^ 0xFF);

    //  Separate the significand encoding and the exponent encoding.
    ushort mantissa = (ushort)(xor & 0xF8);
    int exponent = xor & 0x07;

    /*  Special case:  0xf7 (0x08 after complement) represents zero.  This is
        strange, as there is another encoding (0xff) that represents zero.
    */
    if (xor == 0x08)
        return 0;

    //  If the exponent encoding is zero, the number is subnormal.
    else if (exponent == 0)
    {
        /*  In a subnormal number, the implicit bit of the significand is zero,
            and the exponent is the minimal value supported, so the number
            represented is merely the significand encoding scaled for the
            minimum exponent.
        */
        return xor / 512.0;
    }

    //  Otherwise, the number is normal.
    else
    {
        /*  In a normal number, the implicit bit of the significand is one.
            ORing with 0x100 adds this bit.

            Then we scale by the exponent.
        */
        return ((mantissa | 0x100) << (exponent - 1)) / 512.0;
    }
}


/*  Convert x to the encoding of the nearest representable value in the scheme
    above.  This routine requires IEEE 754 arithmetic.
*/
byte Encode(double x)
{
    /*  The encoding arbitrarily sets 0xf7 (0x08 after XOR) to represent zero.
        If not for this exception, 0xf7 would represent 8/512.  Because of it,
        the smallest positive value that can be encoded is 16/512.  In order to
        ensure that conversion rounds to the nearest representable value, we
        set a threshold midway between that and zero.  Numbers at or below the
        midpoint are rounded to zero.

        If not for this exception, Clip could be set to zero.
    */
    static const double Clip = 16./512 / 2;

    /*  Encode zero and negative numbers.

        The encoding appears to use both 0xf7 and 0xff to represent 0, and it
        is not clear which ought to be returned in any particular circumstance.
    */
    if (x <= Clip)
        return 0xff;

    /*  Encode subnormal numbers.  In a subnormal number, the significand is
        scaled by a fixed amount, and the exponent encoding is zero.
    */
    if (x < .5)
        return (byte) (round(64*x) * 8) ^ 0xff;

    /*  If the number exceeds the maximum representable value, return the
        maximum representable value.
    */
    if (63 < x)
        return 0;

    //  Encode normal numbers.

    /*  Use Dekker's algorithm to round the number to six significand bits.
        (The value 0x1p47 requires that the "double" type have 53 significand
        bits (53-47 = 6), as it does in the common IEEE 754 basic 64-bit binary
        format.
    */
    double d = x * (0x1p47 + 1);
    x = d - (d - x);

    /*  Split the number into a fraction f in [1/2, 1) and a power of two e.
    */
    int e;
    double f = frexp(x, &e);

    //  Encode the exponent by adding a bias.
    e = e + 1;

    //  Remove the explicit leading 1 bit from the fraction.
    f -= .5;

    /*  Shift the fraction to the fraction field, insert the exponent encoding,
        and complement.
    */
    return ((byte) (512*f) | e) ^ 0xff;
}


int main(void)
{
    for (unsigned i = 0; i < 0x100; ++i)
    {
        double x = unpack(i);
        byte code = Encode(x);
        printf("0x%x -> 0x%x -> %.99g -> 0x%x.\n",
            i, i ^ 0xff, x, (unsigned) code);
        if (x != unpack(code))
        {
            fprintf(stderr, "Bug found trying to encode %.99g.\n", x);
            exit(EXIT_FAILURE);
        }
    }
}
#包括
#包括
#包括
#包括
typedef无符号字符字节;
类型定义uint16\u t ushort;
//这是插入注释的原始解码算法。
静态双重解包(字节计数)
{
//补位。其目的未知。
字节xor=(字节)(计数^0xFF);
//将有效位编码和指数编码分开。
ushort尾数=(ushort)(xor&0xF8);
int指数=xor&0x07;
/*特殊情况:0xf7(补码后0x08)表示零。这是
奇怪,因为还有另一种表示零的编码(0xff)。
*/
如果(xor==0x08)
返回0;
//如果指数编码为零,则数字低于正常值。
如果(指数=0),则为else
{
/*在次正常数中,有效位的隐式位为零,
指数是支持的最小值,所以数字
所表示的仅是针对
最小指数。
*/
返回xor/512.0;
}
//否则,数字是正常的。
其他的
{
/*在正常数字中,有效位的隐式位为1。
使用0x100进行ORing将添加此位。
然后我们按指数进行缩放。
*/

return((尾数| 0x100)似乎该字节包含一个无符号的5位尾数和3位指数。行
字节xor=(字节)(计数^0xFF);
可以简单地替换为
字节xor=(字节)~count;
哇,干得好!我现在正想把它翻译成C#,但基于你的完整代码(包括测试),我毫不怀疑它会工作。我确实有一个关于0x1p47的问题。这是我不熟悉的东西,谷歌搜索也没什么用处。这个值有什么名字我可以用谷歌搜索一下吗获取更多信息?@user1930728:搜索“Veltkamp-Dekker分割算法”了解更多信息。
0x1p47
是可调参数,不是固定值;二的幂(47)要删除的位数。由于公共
双精度
中有53位,删除47只剩下6位。通过乘以
0x1p47+1
,算法强制在算术中的所需点进行舍入。只需将输入舍入到最接近的可表示值即可。如果已知输入是精确的y表示值,不需要此步骤。解释其工作原理的详细信息是为了另一个问题。再次感谢您提供的信息。我在0x1p47值上查找的关键字是“十六进制浮点文字”。这对我来说是一个新概念,但现在有了意义。不幸的是,虽然这些都是C++17的一部分,但它们不是Visual Studio 2017。这意味着我无法使用此解决方案,但我仍要将其标记为答案,因为这是一个正确的答案,并且很好地解释了必要的步骤。@user1930827:您可以生成0x1p47在其他方面。它只是47的2倍。一个确定的方法是
scalbn(1,47)
。希望编译器将其优化为编译时常量。但是,如果没有,您可以尝试140737488355328(或者,因为我们正在添加一个,140737488355329)。但是,我希望确保编译器正确地转换它,而不会出现任何舍入错误。(不幸的是,Microsoft没有最好的浮点运算实现。)