C# 双精度的尾数规范化
本文关键字:规范化 双精度 | 更新日期: 2023-09-27 18:24:33
编辑:让它现在工作,在规范化螳螂时,首先设置隐式位很重要,在解码隐式位时,不必添加。我留下了标记的答案是正确的,因为那里的信息确实有帮助。
我目前正在实施编码(可分辨编码规则(,并且在编码双精度值时遇到一个小问题。
因此,我可以使用以下方法从 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;
(使用此处的代码(。这些值可以编码,简单的过程逆转将使我恢复原始的双精度。
但是,DER 编码规则指定尾数应规范化:
在 指定了规范编码规则和可分辨编码规则规范化,并且尾数(除非为 0(需要 反复移位,直到最低有效位为 1。
(请参阅此处的第 8.5.6.5 节(。
手动执行此操作:
while ((mantissa & 1) == 0)
{
mantissa >>= 1;
exponent++;
}
行不通,给我奇怪的价值观。(即使使用上述链接中发布的整个功能Jon Skeet(。
我似乎在这里错过了一些东西,如果我首先可以规范化替身的尾钛并获取"位",那将是最简单的。但是,我也不明白为什么手动规范化无法正常工作。
感谢您的任何帮助,
丹尼
编辑:实际工作问题显示我的螳螂规范化问题:
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
当你使用 BitConverter.DoubleToInt64Bits
时,它会给你已经以 IEEE 754 格式编码的double
值。这意味着有效位数使用隐式前导位进行编码。("有效"是浮点值分数部分的首选术语,在 IEEE 754 中使用。有效数是线性的。尾数是对数的。"尾数"源于人们不得不使用对数、纸张和函数表进行粗略计算的日子。若要恢复未编码的有效位数,必须还原隐式位。
这并不难。分离符号位、编码指数(整数(和编码有效位数(整数(后,对于 64 位二进制浮点数:
- 如果编码指数是其最大值 (2047(,并且编码有效数不为零,则该值为 NaN。在有效值中还有其他关于 NaN 是否发出信号的信息,以及其他用户或实现定义的信息。
- 如果编码指数是其最大值,编码有效数为零,则该值为无穷大(+ 或 – 根据符号(。 如果编码指数为零,
- 则隐式位为零,实际有效数是编码有效数乘以 2–52,实际指数是 1 减去偏差 (1023((so –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/2, 1( 的值,并且exponent
使得 fraction
•2 exponent
等于 value
。(请注意,fraction
仍然有符号;您可能希望将其分开并使用绝对值。
此时,您可以根据需要缩放fraction
(并相应地调整exponent
(。要缩放它以使其成为奇数整数,您可以反复将其乘以 2,直到它没有小数部分。