【问题标题】:Is minus zero (-0) equivalent to zero (0) in C#负零 (-0) 等于 C# 中的零 (0)
【发布时间】:2011-03-09 13:07:32
【问题描述】:

在 C# 中,负零 (-0) 是否等同于零 (0)?

【问题讨论】:

标签: c#


【解决方案1】:

对于整数,没有二进制表示可以区分 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 被正确解析为双精度的“负零”值。
【解决方案2】:

对于小数,至少有 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 }

来源:Decimal Negative Zero Representation

【讨论】:

  • +1 - 但是:你的二进制文件 negativeZero1negativeZero3 在我看来几乎一样?
  • 这还不是全部:在decimal 中实际上有 256 种 0 表示(给予或接受围栏),其中大约一半设置了符号位。跨度>
【解决方案3】:

听起来您正在寻找它们不可互换的边缘情况,所以这里有一些示例。

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.MinMath.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 返回第二个给出的零。

红利:非正常数字

floatdouble 类型中的某些位模式表示 subnormal(又名非正规)值。我不会深入讨论它们是什么——请参阅链接——但重要的是要知道 CLI 规范明确声明它们的操作是特定于实现的。我不知道是否有平台将它们视为0,但可能存在。另一方面,C# 编程语言说它们“被认为是有效的非零值”。

【讨论】:

  • 只是一个有趣的花絮:我不小心遇到了 object.Equals / ValueType.Equals 在包含浮点数的结构方面的不同行为(根据您的第一个代码示例)同时使用在线 .NET 沙箱。 (1/2)
  • (2/2) 似乎使用 Mono 的 Ideone 不遵守 MS documentation 中指定的 ValueType.Equals 的行为(代码示例:ideone.com/7iWule)。 DotNetFiddle 在选择 .NET Core 2.2 编译器时也会表现出不同的行为 (dotnetfiddle.net/Se1U3n)。但是,将 DotNetFiddle 中的编译器切换到 .NET 4.7.2 或 Roslyn 2.0 将产生与 ValueType.Equals 的记录行为一致的结果。 :-)
  • 来自@Dono 的另一条 Stack Overflow 评论,看起来该问题已在 PR 13164Core 中修复
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-18
  • 1970-01-01
  • 2017-06-06
  • 1970-01-01
  • 1970-01-01
  • 2011-12-19
  • 2012-07-02
相关资源
最近更新 更多