简体   繁体   English

将尾数和指数转换为双精度

[英]Convert mantissa and exponent into double

In a very high performance app we find the the CPU can calculate long arithmetic significantly faster then with doubles. 在一个非常高性能的应用程序中,我们发现CPU可以比使用double更快地计算长时间算术。 However, in our system it was determined that we never need more then 9 decimal places of precision. 但是,在我们的系统中,确定我们永远不需要多于9个小数位的精度。 So we using longs for all floating point arithmetic with a 9 point precision understood. 因此,我们对所有浮点算术使用long值,并且理解为9点精度。

However, in certain parts of the system it is more convenient due to readability to work with doubles. 但是,在系统的某些部分中,由于具有可读性,因此使用double可以更方便。 So we have to convert between the long value that assumes 9 decimal places into double. 因此,我们必须在假设9个小数位的long值之间转换为double。

We find the simply taking the long and dividing by 10 to the power of 9 or multiplying by 1 divided by 10 to the power of 9 gives imprecise representations in a double. 我们发现简单地将长整数除以10等于9的幂,或者乘以1除以10等于9的幂就可以得到不精确的双精度表示。

To solve that we using the Math.Round(value,9) to give the precise values. 为了解决这个问题,我们使用Math.Round(value,9)给出精确值。

However, Math.Round() is horrifically slow for performance. 但是, Math.Round()的性能非常差。

So our idea at the moment is to directly convert the mantissa and exponent to the binary format of a double since--in that way, there will be zero need for rounding. 因此,目前我们的想法是将尾数和指数直接转换为double的二进制格式,因为这样一来,四舍五入就不需要了。

We have learned online how to examine bits of a double to get the mantissa and exponent but it's confusing to figure out how to reverse that to take a mantissa and exponent and fabricate a double by using the bits. 我们已经在线学习了如何检查双精度数以获得尾数和指数,但是弄清楚如何反转以获取尾数和指数并通过使用数位来制造双精度数令人困惑。

Any suggestions? 有什么建议么?

[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);

}

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

As you've already realised as per the other answer, doubles work by floating-point binary rather than floating-point decimal, and therefore the initial approach doesn't work. 正如您已经在其他答案中认识到的那样,通过浮点二进制而不是浮点十进制来加倍工作,因此初始方法不起作用。

It's also not clear if it could work with a deliberately simplified formula, because it's not clear what the maximum range you need is, so rounding becomes inevitable. 还不清楚是否可以使用故意简化的公式,因为尚不清楚您需要的最大范围是多少,因此舍入变得不可避免。

The problem of doing so quickly but precisely is well-studied and often supported by CPU instructions. 快速而精确地执行此操作的问题已得到充分研究,并且通常由CPU指令支持。 Your only chance of beating the built-in conversions is either: 击败内置转换的唯一机会是:

  1. You hit a mathematical breakthrough that's worthy of some serious papers being written about it. 您实现了数学上的突破,值得撰写一些严肃的论文。
  2. You exclude enough cases that won't occur in your own examples that while the built-ins are better generally yours is optimised for your own use. 您排除了在您自己的示例中不会发生的足够多的情况,尽管通常情况下内置更好,但您的情况已针对您自己的使用进行了优化。

Unless the range of values you use is very limited, the potential for short-cutting on conversion between double-precision IEEE 754 and long integer becomes smaller and smaller. 除非您使用的值的范围非常有限,否则在双精度IEEE 754和长整数之间进行转换的捷径会越来越小。

If you're at the point where you have to cover most of the cases IEEE 754 covers, or even a sizable proportion of them, then you'll end up making things slower. 如果您必须要涵盖IEEE 754涵盖的大多数情况,甚至其中相当一部分,那么您最终将使速度变慢。

I'd recommend either staying with what you have, moving the cases where double is more convenient to stick with long anyway despite the inconvenience, or if necessary using decimal . 我建议要么继续使用自己拥有的东西,要么在不方便的情况下移动double更方便长期坚持的情况,或者在必要时使用decimal You can create a decimal from a long easily with: 您可以使用以下命令轻松地从long decimal创建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);
}

Now, decimal is magnitudes slower to use in arithmetic than double (precisely because it implements an approach similar to yours in the opening question, but with a varying exponent and larger mantissa). 现在, decimal在算术中使用的幅度要比double精度要慢(确切的原因是,它采用与您在开头的问题中类似的方法,但是指数变化且尾数较大)。 But if you need just a convenient way to obtain a value for display or rendering to string, then hand-hacking the conversion to decimal has advantages over hand-hacking the conversion to double , so it could be worth looking at. 但是,如果你需要的只是一个方便的方式获得用于显示的值或渲染字符串,然后用手黑客转换为decimal具有优于手工黑客转换为double ,所以它可能是值得考虑的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM