尾数标准化C#double

时间:2013-07-24 08:44:25

标签: c# encoding floating-point

编辑:现在让它工作,在规范化mantiss时,首先设置隐式位是很重要的,当解码隐式位时则不必添加。 我将标记的答案保留为正确,因为那里的信息确实有帮助。

我目前正在实现一种编码(专有编码规则),并且在编码双值时会遇到一些问题。

所以,我可以使用以下方法从c#中的double中取出符号,指数和尾数:

 // get parts
 double value = 10.0;
 long bits = BitConverter.DoubleToInt64Bits(value);
 // Note that the shift is sign-extended, hence the test against -1 not 1
 bool negative = (bits < 0);
 int exponent = (int)((bits >> 52) & 0x7ffL);
 long mantissa = bits & 0xfffffffffffffL;

(使用here中的代码)。 这些值可以编码,过程的简单反转将使我恢复原来的双倍。

但是,DER编码规则指定应该对尾数进行标准化:

  

在   规范了规范编码规则和可分辨编码规则规范化,并且尾数(除非它是0)需要   反复移位直到最低有效位为1。

(见here in section 8.5.6.5)。

使用以下方式手动完成:

 while ((mantissa & 1) == 0)
 {
     mantissa >>= 1;
     exponent++;
 }

不起作用,给我奇怪的价值观。 (即使使用Jon Skeet在上述链接中发布的整个功能)。

我似乎在这里遗漏了一些东西,如果我首先能够规范化双重的手段并获得“位”,那将是最简单的。但是,我也无法真正理解为什么手动标准化将无法正常工作。

感谢您的帮助,

丹尼

编辑:显示我的mantiss规范化问题的实际工作问题:

 static void Main(string[] args)
    {
        Console.WriteLine(CalculateDouble(GetBits(55.5, false))); 
        Console.WriteLine(CalculateDouble(GetBits(55.5, true)));
        Console.ReadLine();
    }

    private static double CalculateDouble(Tuple<bool, int, long> bits)
    {
        double result = 0;
        bool isNegative = bits.Item1;
        int exponent = bits.Item2;
        long significand = bits.Item3;

        if (exponent == 2047 && significand != 0)
        {
            // special case
        }
        else if (exponent == 2047 && significand == 0)
        {
            result = isNegative ? double.NegativeInfinity : double.PositiveInfinity;
        }
        else if (exponent == 0)
        {
            // special case, subnormal numbers
        }
        else
        {
            /* old code, wont work double actualSignificand = significand*Math.Pow(2,                   
               -52) + 1; */
            double actualSignificand = significand*Math.Pow(2, -52);
            int actualExponent = exponent - 1023;
            if (isNegative)
            {
                result = actualSignificand*Math.Pow(2, actualExponent);
            }
            else 
            {
                result = -actualSignificand*Math.Pow(2, actualExponent);**strong text**
            }
        }
        return result;

    }


    private static Tuple<bool, int, long> GetBits(double d, bool normalizeSignificand)
    {
        // Translate the double into sign, exponent and mantissa.
        long bits = BitConverter.DoubleToInt64Bits(d);
        // Note that the shift is sign-extended, hence the test against -1 not 1
        bool negative = (bits < 0);
        int exponent = (int)((bits >> 52) & 0x7ffL);
        long significand = bits & 0xfffffffffffffL;

        if (significand == 0)
        {
            return Tuple.Create<bool, int, long>(false, 0, 0);
        }
        // fix: add implicit bit before normalization
        if (exponent != 0)
        {
            significand = significand | (1L << 52);
        }
        if (normalizeSignificand)
        {
            //* Normalize */
            while ((significand & 1) == 0)
            {
                /*  i.e., Mantissa is even */
                significand >>= 1;
                exponent++;
            }
        }
        return Tuple.Create(negative, exponent, significand);

    }
    Output:
    55.5
    2.25179981368527E+15

1 个答案:

答案 0 :(得分:3)

当您使用BitConverter.DoubleToInt64Bits时,它会为您提供已经以IEEE 754格式编码的double值。这意味着有效数据用隐式前导位编码。 (“有效”是浮点值的小数部分的首选术语,用于IEEE 754.有效数是线性的。尾数是对数的。“尾数”源于人们不得不使用对数和纸的日子。和粗略计算的函数表。)要恢复未编码的有效数,你必须恢复隐含位。

这并不难。一旦你将符号位,编码的指数(作为整数)和编码的有效数字(作为整数)分开,那么对于64位二进制浮点:

  • 如果编码的指数是其最大值(2047)并且编码的有效数字不为零,则该值为NaN。关于NaN是否发信号以及其他用户或实现定义的信息,还有其他重要信息。
  • 如果编码的指数是其最大值且编码的有效数为零,则该值为无穷大(+或 - 根据符号)。
  • 如果编码指数为零,则隐含位为零,实际有效位数为编码有效位数乘以2 -52 ,实际指数为1减去偏差(1023)(所以-1022)。
  • 否则,隐含位为1,实际有效位数是编码的有效位数,首先乘以2 -52 然后加1,实际指数是编码指数减去偏差(1023) )。

(如果你想使用整数而不是有效数的分数,你可以省略乘法2 -52 并将-52添加到指数中。在最后一种情况下,有效数据被添加到2 52 而不是一个。)

有一种替代方法可以避免BitConverter和IEEE-754编码。如果你可以从C#调用frexp例程,它将以数学方式返回分数和指数而不是编码。首先,分别处理零,无穷大和NaN。然后使用:

int exponent;
double fraction = frexp(value, &exponent);

这会将fraction设置为幅度为[½,1)和exponent的值,以便fraction•2 exponent 等于{ {1}}。 (请注意,value仍有符号;您可能希望将其分开并使用绝对值。)

此时,您可以根据需要缩放fraction(并相应地调整fraction)。要缩放它以使它是一个奇数,你可以重复乘以它,直到它没有小数部分。