【问题标题】:Convert mantissa and exponent into double将尾数和指数转换为双精度
【发布时间】:2012-02-14 03:44:12
【问题描述】:

在一个非常高性能的应用程序中,我们发现 CPU 计算长算法的速度明显快于双精度数。但是,在我们的系统中,我们确定我们永远不需要超过 9 个小数位的精度。所以我们对所有精度为 9 点的浮点运算都使用 long。

但是,在系统的某些部分,由于使用双打的可读性,它更方便。因此,我们必须将假定小数点后 9 位的 long 值转换为 double。

我们发现简单地取 long 并除以 10 的 9 次方或乘以 1 除以 10 的 9 次方会在 double 中给出不精确的表示。

为了解决我们使用Math.Round(value,9) 给出精确值的问题。

但是,Math.Round() 的性能非常慢。

所以我们目前的想法是直接将尾数和指数转换为双精度的二进制格式——这样就不需要四舍五入了。

我们已经在网上学习了如何检查双精度数的位以获取尾数和指数,但弄清楚如何反转它以获取尾数和指数并使用这些位制造双精度是令人困惑的。

有什么建议吗?

[Test]
public unsafe void ChangeBitsInDouble()
{
    var original = 1.0D;
    long bits;
    double* dptr = &original;
    //bits = *(long*) dptr;
    bits = BitConverter.DoubleToInt64Bits(original);
    var negative = (bits < 0);
    var exponent = (int) ((bits >> 52) & 0x7ffL);
    var mantissa = bits & 0xfffffffffffffL;
    if( exponent == 0)
    {
        exponent++;
    }
    else
    {
        mantissa = mantissa | (1L << 52);
    }
    exponent -= 1075;

    if( mantissa == 0)
    {
        return;
    }

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

    Console.WriteLine("Mantissa " + mantissa + ", exponent " + exponent);

}

【问题讨论】:

标签: c# floating-point double exponent mantissa


【解决方案1】:

您不应使用 10^9 的比例因子,而应使用 2^30。

【讨论】:

  • 感谢我们刚刚意识到上面的代码并不完整。并且学习以位表示的双指数使用二进制指数而不是十进制指数。所以你的回答很有意义。
  • 这将使长表示不可读,但会大大提高性能,并且转换回双精度会很快。所以这是一个很好的权衡。谢谢!
  • 嗯,这似乎合乎逻辑,但在除以因子以转换回双倍后进行测试时,结果不精确: var convert = 1
  • 啊,更多测试... 2^30 使 long 转换为 double 回 long 产生确切的 long 值。但是,将 double 转换为 long back to double 与原始 double 不匹配。你有更多的建议来解决这个问题吗?
【解决方案2】:

正如您在其他答案中已经意识到的那样,双精度数通过浮点二进制而不是浮点十进制工作,因此初始方法不起作用。

也不清楚它是否可以使用故意简化的公式,因为不清楚你需要的最大范围是多少,所以舍入变得不可避免。

快速但精确地执行此操作的问题已得到充分研究,并且通常得到 CPU 指令的支持。您击败内置转换的唯一机会是:

  1. 您取得了数学上的突破,值得写一些关于它的严肃论文。
  2. 您排除了在您自己的示例中不会出现的足够多的情况,虽然内置插件更好,但通常您的插件已针对您自己的使用进行了优化。

除非您使用的值范围非常有限,否则在双精度 IEEE 754 和长整数之间进行转换的可能性会越来越小。

如果您必须涵盖 IEEE 754 涵盖的大多数情况,甚至是其中相当大的一部分,那么您最终会让事情变得更慢。

我建议要么保留现有的东西,将double 的情况转移到更方便的情况下,尽管存在不便,但仍然坚持很长时间,或者在必要时使用decimal。您可以使用以下方法从long 轻松创建decimal

private static decimal DivideByBillion (long l)
{
  if(l >= 0)
   return new decimal((int)(l & 0xFFFFFFFF), (int)(uint)(l >> 32), 0, false, 9);
  l = -l;
  return new decimal((int)(l & 0xFFFFFFFF), (int)(uint)(l >> 32), 0, true, 9);
}

现在,decimal 在算术中的使用速度比 double 慢很多(正是因为它实现了与您在开场问题中的方法类似的方法,但指数不同,尾数更大)。但是,如果您只需要一种方便的方法来获取显示或呈现为字符串的值,那么手动转换为decimal 比手动转换为double 具有优势,所以它可以值得一看。

【讨论】:

  • 性能在使用双精度的代码中仍然非常重要。只是它们是用户插件,用户期望使用浮点数。使用小数对用户来说当然是实用的,但对于他们使用它们所做的任何事情的性能却令人失望。由于我们所有的输入都来自双精度数(来自以二进制形式记录的字符串或双精度数),因此我正在考虑提取尾数...用它进行数学运算...然后简单地将尾数替换为输出。这有望避免四舍五入和缺乏精度。
猜你喜欢
  • 2015-09-13
  • 1970-01-01
  • 1970-01-01
  • 2011-07-07
  • 2017-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多