【发布时间】:2011-03-09 13:07:32
【问题描述】:
在 C# 中,负零 (-0) 是否等同于零 (0)?
【问题讨论】:
标签: c#
在 C# 中,负零 (-0) 是否等同于零 (0)?
【问题讨论】:
标签: c#
对于整数,没有二进制表示可以区分 0 和 -0,因此它们在定义上是相等的。
对于 IEEE 浮点数,有负零和正零之分。我做了一些测试(.NET Framework 2.0 的 CLR,C# 3),似乎它们被认为是相等的,这实际上是根据 IEEE 754 标准预期的行为。
这是我的测试代码以显示:
double minusOne = -1.0;
double positiveZero = 0.0;
double negativeZero = minusOne*positiveZero;
Console.WriteLine("{0} == {1} -> {2}", positiveZero, negativeZero, positiveZero == negativeZero);
Console.WriteLine("Binary representation is equal: {0}", BitConverter.DoubleToInt64Bits(positiveZero) == BitConverter.DoubleToInt64Bits(negativeZero));
返回:
0 == 0 -> True
Binary representation is equal: False
【讨论】:
double negativeZero = -0.0 也产生相同的结果,这意味着常量 -0.0 被正确解析为双精度的“负零”值。
对于小数,至少有 4 种类型的零:
Decimal zero = Decimal.Zero;
Decimal negativeZero1 = new Decimal(0, 0, 0, true, 0);
Decimal negativeZero2 = -0.0m;
Decimal negativeZero3 = Decimal.Negate(Decimal.Zero);
虽然所有都相等并打印为"0",但它们具有不同的位表示:
zero: {0x00000000 00000000 00000000 00000000 }
negativeZero1: {0x00000000 00000000 00000000 80000000 }
negativeZero2: {0x00000000 00000000 00000000 80010000 }
negativeZero3: {0x00000000 00000000 00000000 80000000 }
【讨论】:
negativeZero1 和 negativeZero3 在我看来几乎一样?
decimal 中实际上有 256 种 0 表示(给予或接受围栏),其中大约一半设置了符号位。跨度>
听起来您正在寻找它们不可互换的边缘情况,所以这里有一些示例。
object.Equals structs> struct SomeStruct { public double X; }
> var a = new SomeStruct { X = 0d };
> var b = new SomeStruct { X = -0d };
> a.Equals(b)
false
>
> 1/0d
∞
> 1/-0d
-∞
>
当然,任何类型的显式逐字节或逐位解构,或类型双关语。这些示例来自 PC:
> BitConverter.GetBytes(0d)
byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 }
> BitConverter.GetBytes(-0d)
byte[8] { 0, 0, 0, 0, 0, 0, 0, 128 }
>
Math.Sign尽管您可能期望什么,Math.Sign 确实不区分负零和正零。它只告诉你一个数字是等于、大于还是小于 0。
> Math.Sign(-0f)
0
Math.Min 和 Math.Max
一些算术运算有 -0 的边缘情况。一个有趣的是Math.Min(等效于最大值),即在比较有符号零时,它返回第二个。所以:
> BitConverter.GetBytes(Math.Min(0.0, -0.0))
byte[8] { 0, 0, 0, 0, 0, 0, 0, 128 }
> BitConverter.GetBytes(Math.Min(-0.0, 0.0))
byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 }
>
decimal 表示的说明decimal 类型本身没有负 0,但它确实有多个零的二进制表示:
> new decimal(new int[4] { 0, 0, 0, 0 })
0
> new decimal(new int[4] { 0, 0, 0, -2147483648 })
0
>
第二个例子可以被认为是一个负零,因为除了设置了否定位之外,它与常规零按位相同。但就格式化程序而言,它只是一个零。实际上,decimal 有几十个零表示,用于不同的小数点移位,所有这些在算术上都是等价的并显示为 0:
> new decimal(new int[4] { 0, 0, 0, 131072 })
0.00
> new decimal(new int[4] { 0, 0, 0, 1835008 })
0.0000000000000000000000000000
> new decimal(new int[4] { 0, 0, 0, 65536 })
0.0
也就是说,您只能通过二进制比较或二进制转换方式将它们彼此区分开来。从实验来看,上面的struct 技巧似乎对它们不起作用。 Math.Min 返回第二个给出的零。
float 和 double 类型中的某些位模式表示 subnormal(又名非正规)值。我不会深入讨论它们是什么——请参阅链接——但重要的是要知道 CLI 规范明确声明它们的操作是特定于实现的。我不知道是否有平台将它们视为0,但可能存在。另一方面,C# 编程语言说它们“被认为是有效的非零值”。
【讨论】: