非规范化数字C#

我最近遇到了非规范化的定义,我理解有些数字不能用标准化的forms表示,因为它们太小而不适合它的相应类型。 据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)); } 

我的解释是否正确?

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

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

至于它为何起作用……指数以偏移表示法存储。 指数的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 

资料来源:
在c#中从double中提取尾数和指数
https://en.wikipedia.org/wiki/IEEE_floating_point
https://en.wikipedia.org/wiki/Denormal_number
http://csharpindepth.com/Articles/General/FloatingPoint.aspx

代码改编自:
在c#中从double中提取尾数和指数

根据我的理解,在某些情况下,非规范化数字可用于帮助下溢(参见对非规范化数字的回答- IEEE 754浮点数 )。

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

长话短说,如果你在checked中运行并尝试下溢并且如果你想在该上下文中运行,则不unchecked考虑。

编辑

我想更新我的答案,因为评论感觉不够实际。 首先,我删除了关于已checked上下文的注释,因为这仅适用于非浮点计算(如int )而不是floatdouble float 。 那是我的错误。

非规范化数字的问题是它们在CLI中不一致。 注意我是如何使用“CLI”而不是“C#”的,因为我们需要比C#更低级别来理解问题。 从公共语言基础设施注释标准分区I第12.1.3节第二个注释(本书第125页)它指出:

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

因此,在CLI级别,非规范化数字的处理是故意留给具体实现的。 此外,如果你查看float.Epsilon (在这里找到)的文档,这是浮点数可以表示的最小正数,你将在大多数机器上获得与文档中列出的相匹配的非规范化数字(大约是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的最小的正标准化数字下进行,这是荒谬的。