【问题标题】:Denormalized numbers C#非规范化数字 C#
【发布时间】:2016-02-25 01:11:44
【问题描述】:

我最近遇到了非规范化定义,我了解到有些数字无法以规范化形式表示,因为它们太小而无法适应其相应的类型。根据 IEEE

所以我试图做的是在非规范化数字作为参数传递时捕获,以避免使用这些数字进行计算。如果我理解正确,我只需要查找非规范化范围内的数字

private bool IsDenormalizedNumber(float number)
{
    return Math.Pow(2, -149) <= number && number<= ((2-Math.Pow(2,-23))*Math.Pow(2, -127)) ||
           Math.Pow(-2, -149) <= number && number<= -((2 - Math.Pow(2, -23)) * Math.Pow(2, -127));
}

我的解释正确吗?

【问题讨论】:

  • 似乎是接近它的正确方法。但是,您的公式与定义不匹配:return Math.Pow(2, -149) &lt;= number &amp;&amp; number&lt;= ((1-Math.Pow(2,-23))*Math.Pow(2, -126)) || Math.Pow(-2, -149) &gt;= number &amp;&amp; number &gt;= -((1 - Math.Pow(2, -23)) * Math.Pow(2, -126))
  • 您使用double 计算和&lt;= 运算符进行比较对我来说是错误的。我认为比钓鱼嘉年华还有更多的选角,这可能会使这些比较失败。
  • 那么,您认为我会因为 double 和 float 的不同格式而得到错误的比较吗?
  • 当您的数学模型开始生成无意义的不准确的数字时,等待是没有意义的。如果你正在计算一个物理属性,比如长度,那么你在它小于原子直径之前就按下了紧急停止按钮。这是1E-10,没有意义让一直到1E-38。如果您不知道数学模型的实用 范围,请询问创建它的人来添加它们。就像您需要知道计算的准确性一样。 float 只有 6 位有效数字,如果计算复杂,则可能没有剩余。
  • @Nandhi 我询问字节序的原因是BitConverter 将假定传入字节的特定字节序,可以通过检查BitConverter.IsLittleEndian 找到。在转换为 .NET 类型之前,您需要确保注意不要让字节顺序错误,否则您会得到错误的答案(不确定您是否已经这样做了)。直接位掩码字节而不涉及 .NET 类型系统及其复杂性会更简单、更高效。

标签: c#


【解决方案1】:

我认为更好的方法是检查这些位。规范化或非规范化是二进制表示的特征,而不是值本身的特征。因此,您将能够以这种方式更可靠地检测到它,并且您可以在没有潜在危险的浮点比较的情况下这样做。

我为您整理了一些可运行的代码,以便您可以看到它的工作原理。我从关于双打的类似问题中改编了这段代码。检测非正规比完全去除指数和有效数要简单得多,因此我能够大大简化代码。

至于它为什么起作用...指数以偏移表示法存储。指数的 8 位可以取值 1 到 254(0 和 255 保留用于特殊情况),然后将它们偏移调整 -127 产生 -126 (1-127) 到 127 (254-127) 的归一化范围)。在非正规情况下,指数设置为 0。我认为这只是必需的,因为.NET 不会将前导位存储在有效位上。根据 IEEE 754,它可以以任何一种方式存储。似乎 C# 选择放弃它以支持符号位,尽管我没有任何具体的细节来支持这一观察。

无论如何,实际的代码是相当简单的。所需要的只是切除存储指数的8位并测试0。0附近有一个特殊情况,下面处理。

注意:根据评论讨论,此代码依赖于特定于平台的实现细节(此测试用例中为 x86_64)。正如@ChiuneSugihara 指出的那样,CLI 不能确保这种行为,并且在其他平台上可能会有所不同,例如 ARM。

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("-120, denormal? " + IsDenormal((float)Math.Pow(2, -120)));
            Console.WriteLine("-126, denormal? " + IsDenormal((float)Math.Pow(2, -126)));
            Console.WriteLine("-127, denormal? " + IsDenormal((float)Math.Pow(2, -127)));
            Console.WriteLine("-149, denormal? " + IsDenormal((float)Math.Pow(2, -149)));
            Console.ReadKey();
        }

        public static bool IsDenormal(float f)
        {
            // when 0, the exponent will also be 0 and will break
            // the rest of this algorithm, so we should check for
            // this first
            if (f == 0f)
            {
                return false;
            }
            // Get the bits
            byte[] buffer = BitConverter.GetBytes(f);
            int bits = BitConverter.ToInt32(buffer, 0);
            // extract the exponent, 8 bits in the upper registers,
            // above the 23 bit significand
            int exponent = (bits >> 23) & 0xff;
            // check and see if anything is there!
            return exponent == 0;
        }
    }
}

输出是:

-120, denormal? False
-126, denormal? False
-127, denormal? True
-149, denormal? True

来源:
extracting mantissa and exponent from double in c#
https://en.wikipedia.org/wiki/IEEE_floating_point
https://en.wikipedia.org/wiki/Denormal_number
http://csharpindepth.com/Articles/General/FloatingPoint.aspx

代码改编自:
extracting mantissa and exponent from double in c#

【讨论】:

  • 我认为您的答案是特定于实现的。有关详细信息,请参阅我的答案的更新。
  • 确实是@ChiuneSugihara。我没有考虑便携性。谢谢你叫出来。
  • 没问题,我觉得你需要一个博士学位。在这些疯狂的事情中了解他们所发生的一切。我很好奇需要检测这些复杂的数值计算独角兽的用例是什么。似乎他们增加了很多复杂性,我认为对于他们试图解决的任何问题都必须有一个更简单的答案。如果他们真的需要检测到它们,我认为只要不影响可移植性,您的解决方案就会有效。
  • @KevinBurdett 谢谢。实际上是分析格式本身的更好方法。
【解决方案2】:

据我了解,在某些情况下,非规范化数字可以帮助解决下溢问题(请参阅Denormalized Numbers - IEEE 754 Floating Point 的回答)。

因此,要获得非规范化数字,您需要显式创建它,否则会导致下溢。在第一种情况下,似乎不太可能在代码中指定文字的非规范化数字,即使有人尝试过,我也不确定 .NET 是否会允许它。 在第二种情况下,只要您处于checked 上下文中,您就应该在算术计算中针对任何溢出或下溢抛出OverflowException,这样可以防止获得非规范化数字的可能性。在unchecked 上下文中,我不确定下溢是否会将您带到非规范化数字,但您可以尝试一下,看看您是否要在unchecked 中运行计算。

长话短说,如果您在 checked 中运行,则不必担心,如果您想在该上下文中运行,请尝试下溢并查看 unchecked

编辑

我想更新我的答案,因为评论感觉不够充实。首先,我删除了我对checked 上下文所做的评论,因为它仅适用于非浮点计算(如int),不适用于floatdouble。那是我的错误。

非规范化数字的问题在于它们在 CLI 中不一致。注意我是如何使用“CLI”而不是“C#”的,因为我们需要比 C# 更低的级别来理解这个问题。 The Common Language Infrastructure Annotated Standard Partition I Section 12.1.3 第二个注释(本书的第 125 页)指出:

本标准未指定非规范化浮点数的算术运算行为,也未指定何时或是否应创建此类表示。这符合 IEC 60559:1989。此外,该标准没有指定如何访问创建的 NaN 的确切位模式,也没有指定在 32 位和 64 位表示之间转换 NaN 时的行为。 所有这些行为都是故意留给实现特定的

因此,在 CLI 级别,非规范化数字的处理故意留给实现特定的。此外,如果您查看float.Epsilon 的文档(找到here),它是浮点数可以表示的最小正数,您将在大多数机器上获得与文档中列出的内容相匹配的非规范化数字(大约1.4e-45)。这是@Kevin Burdett 在他的回答中最有可能看到的。话虽如此,如果您在页面上向下滚动,您将在“平台说明”下看到以下引用

在 ARM 系统上,Epsilon 常数的值太小而无法检测,因此它等于零。您可以改为定义一个等于 1.175494351E-38 的替代 epsilon 值。

因此,当您手动处理非规范化数字时,即使仅针对 .NET CLR(它是 CLI 的一种实现),也会出现可移植性问题。事实上,这个 ARM 特定值有点有趣,因为它看起来是一个标准化的数字(我使用了 @Kevin Burdett 的函数和 IsDenormal(1.175494351E-38f),它返回 false)。在 CLI 本身中,问题更加严重,因为根据 CLI 标准的注释,它们的处理没有标准化。所以这就留下了关于 Mono 或 Xamarin 上的相同代码会发生什么的问题,例如,这是 CLI 与 .NET CLR 的不同实现。

最后,我回到了我之前的建议。只是不要担心非规范化的数字,它们会默默地帮助你,很难想象你为什么需要专门挑选它们。同样正如@HansPassant 提到的那样,您很可能甚至都不会遇到。很难想象你会如何处理double 中最小的正归一化数字,这个数字小得离谱。

【讨论】:

  • 我不认为这是准确的@ChiuneSugihara。我能够使用文字和操作(特别是从双精度转换)来创建非规范化的浮点数。我没有输入未经检查的上下文就这样做了。
  • 无论您是否指定任何内容,您始终处于选中或未选中状态。选中仅意味着将发出 IL 代码并检查溢出,而未选中则意味着将发出 IL 代码但不会。通过存在或不存在 \checked 编译器开关,构建集有一个全局设置。此外,就非规范化浮点数而言,我注意到您能够创建它们。我并没有说你不能,只是我不确定并且认为你可能不能,尽管在某些情况下显然你可以。这是特定于实现的。
  • 内容丰富!我没有考虑便携性。这就解释了为什么我找不到记录浮动二进制布局的官方来源。我认为您的建议也很合理,通常最好让 CLR 来处理这些类型的抽象。
猜你喜欢
  • 1970-01-01
  • 2013-08-21
  • 2016-05-13
  • 2018-06-06
  • 2017-01-14
  • 2011-07-18
  • 2013-12-11
  • 2013-01-18
  • 2013-02-14
相关资源
最近更新 更多