C#: 把负整数转换为字节的结果

12

我在查看 一个项目 的源代码时,发现了以下 语句(keyByte和codedByte均为byte类型):

return (byte)(keyByte - codedByte);

我现在试图理解当keyByte小于codedByte时的结果会是什么,这将导致负整数。
经过一些实验以了解将具有值在[-255:-1]范围内的负整数强制转换的结果,我得到了以下结果:
byte result = (byte) (-6);  // result = 250
byte result = (byte) (-50); // result = 206
byte result = (byte) (-17); // result = 239
byte result = (byte) (-20); // result = 236

所以,假设 -256 < a < 0,我可以通过以下方式确定结果:

result = 256 + a;

我的问题是:我是否应该总是期望这种情况发生?

重申一遍:你是完全正确的。因为任何“负数”值都超出了 .Net “byte”的定义域,.Net 会首先将其提升(到大于255的整数值)。 - paulsm4
5个回答

6
是的,这将始终如此(即它不仅取决于您的环境或编译器,而且作为C#语言规范的一部分定义)。请参见http://msdn.microsoft.com/en-us/library/aa691349(v=vs.71).aspx
在未经检查的上下文中,通过丢弃目标类型中不适合的任何高位比特来截断结果。
下一个问题是,如果您从-256到-1之间的负整数中去掉高阶位,并将其读取为字节,您会得到什么?这就是您已经通过实验发现的:它是256 + x。
请注意,字节顺序无关紧要,因为我们丢弃的是高位(或最高有效位),而不是“第一个”24位。因此,无论我们从哪一端取出它,我们都剩下了组成该int的最低有效字节。

出于好奇,如果您的硬件(CPU)使用除二进制补码表示以外的其他内容来表示整数,会怎样呢?为了得到答案,我们需要查看CLR规范而不是C#规范。ECMA335第12.1节将“int”标识为“32位二进制补码有符号值”。这只是理论,在.NET(或mono)当前运行的有限硬件范围内,但它是我们所问问题的最终答案。是的,负值不能给出任何数值上的不同结果,即使在假设的未来硬件上也是如此。 - Jirka Hanika
出于好奇,如果恐龙没有灭绝会怎样呢?我们会骑翼龙飞行而不是发明喷气式飞机吗?为什么不承认Mota的简单事实:对于任何.Net语言,对于有符号整数值-256 < a < 0:结果= 256 + a; - paulsm4

5
是的。请记住,在.NET的“Byte”域中不存在“-”:
引用自:http://msdn.microsoft.com/en-us/library/e2ayt412.aspx 因为Byte是无符号类型,它不能表示负数。如果你在一个求值为Byte类型的表达式上使用一元减运算符(-),Visual Basic首先将表达式转换为Short。(注意:可以将任何CLR/.Net语言替换为“Visual Basic”)
补充说明: 以下是一个示例应用程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestByte
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = -255; i < 256; i++)
            {
                byte b = (byte)i;
                System.Console.WriteLine("i={0}, b={1}", i, b);
            }
        }
    }
}

这里是生成的输出结果:

testbyte|more
i=-255, b=1
i=-254, b=2
i=-253, b=3
i=-252, b=4
i=-251, b=5
...
i=-2, b=254
i=-1, b=255
i=0, b=0
i=1, b=1
...
i=254, b=254
i=255, b=255

1
我给这个点踩了,因为它没有解决他的问题 - 即,将负整数转换为字节是否始终相同。知道这一点很好,但完全不同的是,如果你说-b,其中b是一个byte,它会先将其转换为short - Tim S.
你能提供一个参考吗?ECMA 334(14.7)似乎只承认在“int”、“uint”、“long”和“ulong”上进行算术运算。虽然这在这里并不是非常相关,但我们中的其中一人在细节上是错误的,我很好奇是谁错了。 - Jirka Hanika
我不知道Visual Basic是做什么的,但这并不是C#规范第4.5.1节所说的:“对于一元运算符-,操作数被转换为类型T,其中T是int和long中可以完全表示操作数的所有可能值的第一个。”在这种情况下,该类型为int。此外,对于byte,二进制-也会转换为int。 - Mike Zboray
@mikez - 我的意思是VB.NET也不能做到这一点,因为CLR没有16位算术。 - Jirka Hanika
@JirkaHanika,仅因clr不支持16位算术运算并不意味着VB不能使用一元负运算符将Byte转换为Short。事实上,这正是它所做的。然而,c#不会这样做,因为规范中有所说明。 - Mike Zboray
@mikez - 谢谢。我改正了。我现在理解这篇博客:http://blogs.msdn.com/b/ericgu/archive/2004/02/02/66345.aspx 意味着CIL子指令只能使用4字节或8字节的参数,但VB.NET使用4字节减法来模拟2字节算术,即使是二进制减(不像C#)。 - Jirka Hanika

3

这里有一个算法,它执行与转换为字节相同的逻辑,以帮助你理解它:

对于正数:

byte bNum = iNum % 256;

对于否定情况:

byte bNum = 256 + (iNum % 256);

这就像是在寻找任何一个导致 x + 255k 在范围 0 ... 255 内的 k。只有一个 k 会产生落在该范围内的结果,而结果将是强制转换为字节类型后的结果。

另一种看待它的方式是将其视为“循环遍历字节值范围”:

我们再次使用 iNum = -712,并定义一个 bNum = 0

我们将会执行 iNum++; bNum--; 直到 iNum == 0

iNum = -712;
bNum = 0;

iNum++; // -711
bNum--; // 255 (cycles to the maximum value)

iNum++; // -710
bNum--; // 254

... // And so on, as if the iNum value is being *consumed* within the byte value range cycle.

当然,这只是一个示例,以便更好地理解它的逻辑。

0

这就是在unchecked上下文中发生的情况。你可以说运行时(或编译器,如果你将Int32强制转换为Byte在编译时已知)添加或减去256次,直到找到一个可表示的值。

checked上下文中,会导致异常(或编译时错误)。请参见http://msdn.microsoft.com/en-us/library/khy08726.aspx


0

是的 - 除非你遇到异常。

.NET仅在4字节及更大的数据类型上定义所有算术运算。因此,唯一不明显的点是如何将int转换为byte

对于从整数类型到另一个整数类型的转换,转换结果取决于溢出检查上下文(ECMA 334标准第13.2.1节说)。

因此,在以下上下文中:

checked
{
    return (byte)(keyByte - codedByte);
}

你将会看到一个 System.OverflowException。然而在下面的上下文中:

unchecked
{
    return (byte)(keyByte - codedByte);
}

无论您是否将差值加上256的倍数,都保证始终看到您期望的结果;例如,2-255=3。

这是针对表示有符号值硬件适用。CLR标准(ECMA 335)在第12.1节中规定,Int32类型是“32位二进制补码有符号值”。(嗯,这也与当前.NET或Mono可用的所有平台匹配,因此几乎可以猜测它会起作用,但了解语言标准并且具有可移植性是很好的做法。)

某些团队不希望显式指定溢出检查上下文,因为他们在开发周期早期进行溢出检查,但不在发布代码中进行。在这些情况下,您可以安全地执行字节算术操作:

return (byte)((keyByte - codedByte) % 256);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接