C# double 的尾数规范化

Posted

技术标签:

【中文标题】C# double 的尾数规范化【英文标题】:Mantissa Normalization of C# double 【发布时间】:2013-07-24 08:44:24 【问题描述】:

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

我目前正在实现一种编码(可区分编码规则)并且在编码双精度值时遇到了一点问题。

所以,我可以通过以下方式从 c# 中的双精度数中取出符号、指数和尾数:

 // 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 编码规则指定尾数应该被归一化:

在 Canonical Encoding Rules 和 Distinguished Encoding Rules 规范化被指定并且尾数(除非它是 0)需要 重复移位,直到最低有效位为 1。

(见here in section 8.5.6.5)。

手动使用:

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

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

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

感谢您的帮助,

丹尼

编辑:显示螳螂规范化问题的实际工作问题:

 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

【问题讨论】:

DoubleToInt64Bits() 不会为您提供尾数,它会为您提供已应用指数的值。那个代码是错误的,扔掉它。 哦,好吧,我不知道,但是,你有什么建议可以让我得到我想要的吗?我想我现在尝试一个联合(嗯,一个具有显式字段布局的结构) Jon Skeet 支持该答案,请在此处发表评论。联合不起作用,这些位不会落在字节边界上。 我看到这些位不属于字节边界,但对于 doubleToInt64Bits 部分,我认为我可以在同一个结构中放置一个 long 和一个 double 。好吧,我照你说的做了,并在那里发表了评论。 我恐怕对区分编码规则一无所知... 【参考方案1】:

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

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

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

(如果您想使用整数并且没有分数作为有效数,您可以省略乘以 2–52 并改为将 –52 添加到指数。在最后一种情况下,有效位被添加到 252 而不是 1。)

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

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

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

此时,您可以根据需要缩放fraction(并相应地调整exponent)。要将其缩放为奇数,您可以将其重复乘以 2,直到它没有小数部分。

【讨论】:

非常感谢 Eric 提供这些广泛的信息。关于significand vs mantiss的不错的侧面信息,我想我的脑海里有uni这个词。我会接受这个作为答案,因为它包含所有需要的信息,其余的取决于我 嗨,埃里克,根据您的信息,我现在至少现在正在做什么,但是螳螂规范化仍然无法正常工作,我想我可能在错误的地方做,我编辑了我的问题显示一个例子... @Daniel: CalculateDouble 获取 IEEE-754 编码的位并将它们转换为 double。当您“标准化”GetBits 中的有效数字时,您正在创建不同的编码。 GetBits 的结果是一个符号、一个指数和一个有效数字,其中有效数字已被 DER 规则“标准化”,但指数仍包含 IEEE-754 偏差。如果 DER 偏差不同,则需要减去 IEEE-754 偏差 (1023) 并添加 DER 偏差。您还需要减去 52 以说明有效数字是整数而不是开头的二进制点。

以上是关于C# double 的尾数规范化的主要内容,如果未能解决你的问题,请参考以下文章

如何在浮点二进制中规范化 110.110?

单精度与双精度

计算机组成原理——浮点数表示方法

MongoDB、C# 和 NoRM + 非规范化

谁有C#的命名规范

编码规范篇| C#编码规范 代码规范总结,包括命名规范,代码规范 注释规范等