我最近遇到了非规范化定义,并且我理解有些数字无法以规范化形式表示,因为它们太小而无法适应其对应的类型。根据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));
}
我的解释是否正确?
答案 0 :(得分:4)
我认为更好的方法是检查这些位。归一化或非规范化是二进制表示的特征,而不是值本身。因此,您将能够以这种方式更可靠地检测它,并且您可以在没有潜在危险的浮点比较的情况下进行检测。
我为您整理了一些可运行的代码,以便您可以看到它的工作原理。我从关于双打的类似问题中改编了这段代码。检测非正规比完全切除指数和有效数要简单得多,所以我能够大大简化代码。
至于它为何起作用......指数以偏移表示法存储。指数的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
答案 1 :(得分:2)
根据我的理解,在某些情况下,非规范化数字可用于帮助下溢(请参阅Denormalized Numbers - IEEE 754 Floating Point的答案)。
因此,要获得非规范化数字,您需要显式创建它,否则会导致下溢。在第一种情况下,似乎不太可能在代码中指定文字的非规范化数字,即使有人尝试过它,我也不确定.NET会允许它。 在第二种情况下,只要你处于 checked
上下文中,你就应该在算术计算中得到OverflowException
任何溢出或下溢,这样就可以防止出现非规范化的可能性数。在unchecked
上下文中,我不确定下溢是否会将您带到非规范化数字,但您可以尝试查看是否要在unchecked
中运行计算。
长话短说,如果您在 checked
中运行并尝试下溢并在unchecked
中查看是否要在该上下文中运行,则不必担心。
修改强>
我想更新我的回答,因为评论感觉不够实际。首先,我删除了我对checked
上下文的评论,因为这仅适用于非浮点计算(如int
),而不适用于float
或double
。那是我的错误。
非规范化数字的问题是它们在CLI中不一致。注意我是如何使用“CLI”而不是“C#”的,因为我们需要比C#更低级别才能理解问题。从公共语言基础设施注释标准分区I第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
中的最小的正标准化数字下进行,这是非常小的。