【问题标题】:Is there a nice way to split an int into two shorts (.NET)?有没有一种很好的方法可以将一个 int 分成两个短裤(.NET)?
【发布时间】:2009-12-09 11:49:55
【问题描述】:

我认为这是不可能的,因为Int32 有 1 位符号和 31 位数字信息,而 Int16 有 1 位符号和 15 位数字信息,这导致有 2 位符号和 30 位信息.

如果这是真的,那么我不能将一个Int32 分成两个Int16。这是真的吗?

提前致谢。

额外信息:使用 Vb.Net,但我认为我可以毫无问题地翻译 C# 答案。

最初我想做的是将一个UInt32 转换为两个UInt16,因为这是用于与基于WORD 的机器交互的库。然后我意识到 Uint 不符合 CLS 并尝试对 Int32Int16 做同样的事情。

更糟糕的是:执行a = CType(c And &HFFFF, Int16); 会引发OverflowException。我希望该语句与a = (Int16)(c & 0xffff); 相同(不会引发异常)。

【问题讨论】:

  • “将一个 int32 转换为两个 int16”。您的具体要求是什么?
  • “转换”在这里是绝对不正确的术语。 “分裂”可能吗?描述你想做什么。 (可以将 UInt32 拆分为两个 UInt16。)
  • 谢谢,按要求添加了更多信息。
  • @SoMoS:我现在已经编辑了我的解决方案,它可以工作。哎呀:)

标签: c# .net vb.net math


【解决方案1】:

这当然可以在不丢失信息的情况下完成。在这两种情况下,您最终都会得到 32 位信息。它们是否用于符号位无关紧要:

int original = ...;

short firstHalf = (short) (original >> 16);
short secondHalf = (short) (original & 0xffff);

int reconstituted = (firstHalf << 16) | (secondHalf & 0xffff);

这里,reconstituted 将始终等于 original,因此不会丢失任何信息。

现在两条短裤符号的含义是另一回事 - 如果original 为负,firstHalf 将为负,但如果第 15 位(计数),secondHalf 将为负original 的 0-31) 被设置,这在原始形式中并没有特别的意义。

【讨论】:

  • 如果在第一种情况下不使用掩码,为什么要在第二种情况下使用掩码? c# 中的强制转换是否实际测试第一部分中是否有信息?还是它会像和会一样把它砍掉?
  • 正如我在 reinier 的回答中所解释的那样,您不能假设 .NET 中的小端序。
  • @DrJokepu:你可以假设移位和屏蔽会做什么,但是它存储在内存中。右移将被符号扩展,但转换为 short 将有效地掩盖它。
  • @Jon Skeet:对于简单的拆分和重构,这不是问题,但是如果它们具有不同的含义(例如,位 0..15 是 X 坐标,位 16..31 是 Y 坐标) 你最终会把两者混为一谈。我没有 XBOX 360 也没有模拟器,所以我无法测试它,但在我看来这就是会发生的事情。
  • 它在转换之前隐式地将其转换为 int。
【解决方案2】:

这应该可行:

int original = ...;
byte[] bytes = BitConverter.GetBytes(original);
short firstHalf = BitConverter.ToInt16(bytes, 0);
short secondHalf = BitConverter.ToInt16(bytes, 2);

编辑:

用 0x7FFFFFFF 测试,它可以工作

byte[] recbytes = new byte[4];
recbytes[0] = BitConverter.GetBytes(firstHalf)[0];
recbytes[1] = BitConverter.GetBytes(firstHalf)[1];
recbytes[2] = BitConverter.GetBytes(secondHalf)[0];
recbytes[3] = BitConverter.GetBytes(secondHalf)[1];
int reconstituted = BitConverter.ToInt32(recbytes, 0);

【讨论】:

  • 我没有阅读您的解决方案,因为我已经选择了一个。但我会用你的。谢谢。
  • 使用BitConverter 时,请始终将您的代码调整为BitConverter.IsLittleEndian,否则当您将其移植到其他平台时它可能会中断。
【解决方案3】:

Jon's answer,翻译成Visual Basic,没有溢出:

Module Module1
    Function MakeSigned(ByVal x As UInt16) As Int16
        Dim juniorBits As Int16 = CType(x And &H7FFF, Int16)
        If x > Int16.MaxValue Then
            Return juniorBits + Int16.MinValue
        End If
        Return juniorBits
    End Function

    Sub Main()
        Dim original As Int32 = &H7FFFFFFF    
        Dim firstHalfUnsigned As UInt16 = CType(original >> 16, UInt16)
        Dim secondHalfUnsigned As UInt16 = CType(original And &HFFFF, UInt16)
        Dim firstHalfSigned As Int16 = MakeSigned(firstHalfUnsigned)
        Dim secondHalfSigned As Int16 = MakeSigned(secondHalfUnsigned)

        Console.WriteLine(firstHalfUnsigned)
        Console.WriteLine(secondHalfUnsigned)
        Console.WriteLine(firstHalfSigned)
        Console.WriteLine(secondHalfSigned)
    End Sub
End Module

结果:

32767
65535
32767
-1

在 .NET 中,CType(&amp;Hffff, Int16) 导致溢出,(short)0xffff 给出 -1(没有溢出)。这是因为默认情况下 C# 编译器使用未经检查的操作和 VB.NET 检查。

我个人喜欢Agg's answer,因为我的代码比较复杂,而Jon的在checked环境下会导致溢出异常。

我也是created another answer,基于BitConverter 类的代码,针对这个特定任务进行了优化。但是,它使用了不安全的代码。

【讨论】:

  • 这是我真正想要的。非常感谢。无论如何,在 Vb.Net 中使用 unchecked 关键字会更好
  • 如果你想在一般检查的环境中使用我的,你总是可以将它包裹在“未检查”中:)
【解决方案4】:

是的,它可以使用掩码和位移来完成

 Int16 a,b;
 Int32 c;

 a = (Int16) (c&0xffff);
 b = (Int16) ((c>>16)&0xffff);

编辑

回答评论。重建工作正常:

 Int16 a, b;
 Int32 c = -1;

 a = (Int16)(c & 0xffff);
 b = (Int16)((c >> 16) & 0xffff);

 Int32 reconst = (((Int32)a)&0xffff) | ((Int32)b << 16);

 Console.WriteLine("reconst = " + reconst);

测试它并按预期打印-1。

EDIT2:改变了重建。 Int16 到 Int32 的提升导致所有符号位扩展。忘记了,它必须是 AND'ed。

【讨论】:

  • .NET VM 不一定是小端。 XBOX 360 在 PowerPC 架构上运行,并配置为使用大端。 XNA 框架是 Microsoft 的 .NET Framework 版本,用于为 XBOX 360 开发游戏和应用程序。所以你不能假设小端。
  • 我没有假设任何事情。我只是像要求的那样把它切成两块
  • reinier:很公平。我之所以这么说,是因为如果前半部分和后半部分的含义不同,您可能需要确保获得正确的一半。
  • @DrJokepu:我不相信字节序会影响这一点。移位的结果根据整数的 逻辑 位进行了很好的定义,而与内存中的表示无关。如果您认为存在一些明显的差异,请提供示例。
  • @somos:好电话。我更改了重建代码。问题是 Negative Int16 将其符号位扩展到 full32 位。我们不想要,所以我不得不 AND 它。,
【解决方案5】:

为什么不呢?为简单起见,让我们减少位数:假设我们有 8 位,其中左位是负位。

[1001 0110] // representing -22

你可以将它存储在 2 次 4 位中

[1001] [0110] // representing   -1 and 6

我不明白为什么不可能,你两次有 8 位信息

编辑:为了简单起见,我不仅减少了位数,而且也不使用 2-complement 方法。在我的例子中,左边的位表示减号,其余的将被解释为正常的正二进制数

【讨论】:

  • 这似乎更接近问题的初衷。
  • @Peter:一个小小的挑剔...在 8 位二进制补码中,10010110 代表 -106-22 实际上将由 11101010 代表。类似地,在 4 位二进制补码中,1001 表示 -15,而-1 实际上将由 1111 表示。
  • @Luke : 这显然不是两个补码 :-) ,不,它只是用于说明的伪代码
  • @Peter:如果他们无法弄清楚为什么 10010110 在他们的计算等中不能像 -22 那样工作,那么可能会让 OP 和/或未来的读者感到困惑。
  • (糟糕,我的第一条评论有误:1001 代表 -7不是 -15。)
【解决方案6】:

C#中的不安全代码,不会发生溢出,自动检测字节序:

using System;
class Program
{
    static void Main(String[] args)
    {
        checked // Yes, it works without overflow!
        {
            Int32 original = Int32.MaxValue;
            Int16[] result = GetShorts(original);
            Console.WriteLine("Original int: {0:x}", original);
            Console.WriteLine("Senior Int16: {0:x}", result[1]);
            Console.WriteLine("Junior Int16: {0:x}", result[0]);
            Console.ReadKey();
        }
    }
    static unsafe Int16[] GetShorts(Int32 value)
    {
        byte[] buffer = new byte[4];
        fixed (byte* numRef = buffer)
        {
            *((Int32*)numRef) = value;
            if (BitConverter.IsLittleEndian)
                return new Int16[] { *((Int16*)numRef), *((Int16*)numRef + 1) };
            return new Int16[] { 
                (Int16)((numRef[0] << 8) | numRef[1]),  
                (Int16)((numRef[2] << 8) | numRef[3])
            };
        }
    }
}

【讨论】:

  • 添加 /unsafe 选项来构建它。
  • 来自反射器:public static readonly bool IsLittleEndian;。因此我们在这里调用属性。
【解决方案7】:

您可以在 VB.NET 中使用 StructLayout:

更正:word 是 16bit,dword 是 32bit

<StructLayout(LayoutKind.Explicit, Size:=4)> _
   Public Structure UDWord
      <FieldOffset(0)> Public Value As UInt32
      <FieldOffset(0)> Public High As UInt16
      <FieldOffset(2)> Public Low As UInt16

      Public Sub New(ByVal value As UInt32)
         Me.Value = value
      End Sub

      Public Sub New(ByVal high as UInt16, ByVal low as UInt16)
         Me.High = high
         Me.Low = low
      End Sub
   End Structure

仅使用这些类型,签名将是相同的

<StructLayout(LayoutKind.Explicit, Size:=4)> _
   Public Structure DWord
      <FieldOffset(0)> Public Value As Int32
      <FieldOffset(0)> Public High As Int16
      <FieldOffset(2)> Public Low As Int16

      Public Sub New(ByVal value As Int32)
         Me.Value = value
      End Sub

      Public Sub New(ByVal high as Int16, ByVal low as Int16)
         Me.High = high
         Me.Low = low
      End Sub
   End Structure

编辑:

我已经发布/编辑了我的 anwser 几次,但还没有解释这个解决方案,所以我觉得我还没有完成我的答案。所以我现在就这样做:

在结构上显式使用 StructLayout 需要您通过 FieldOffset 属性 [FieldOffsetAttribute] 提供每个字段的定位(按字节偏移量)[StructLayoutAttribute]

使用这两个属性,您可以创建重叠字段,即unions

第一个字段 (DWord.Value) 将是 32 位整数,偏移量为 0(零)。要拆分这个 32 位整数,您将有两个额外的字段再次从偏移量 0(零)开始,然后第二个字段再多 2 个字节,因为 16 位(短)整数是 2 个字节。

据我回忆,通常当你拆分一个整数时,他们通常称前半部分为“高”,后半部分为“低”;从而命名我的另外两个字段。

通过使用这样的结构,您可以为运算符和类型加宽/收窄创建重载,以便轻松地从 Int32 类型交换到此 DWord 结构,以及比较 Operator Overloading in VB.NET

【讨论】:

  • 这适用于签名类型吗?我需要创建一个符合 CLS 的程序集。
  • 是的,您的问题中必须跳过该部分。我已经更新了我的答案以包含签名的整数。
  • 这不会针对字节序进行调整。
【解决方案8】:

您可以使用 StructLayout 来执行此操作:

[StructLayout(LayoutKind.Explicit)]
        struct Helper
        {
            [FieldOffset(0)]
            public int Value;
            [FieldOffset(0)]
            public short Low;
            [FieldOffset(2)]
            public short High;
        }

使用它,您可以将完整的 Value 设为 int ,将低部分、高部分设为 short 。

类似:

var helper = new Helper {value = 12345};

【讨论】:

    【解决方案9】:

    由于存储宽度(32 位和 16 位),如果您的 Int32 大于 32767,将 Int32 转换为 Int16 可能意味着信息丢失。

    【讨论】:

    • 他正在将其转换为 两个 Int16,而不是一个。
    【解决方案10】:

    如果你看一下位表示,那么你是对的。

    您可以使用无符号整数来执行此操作,因为它们没有符号位。

    【讨论】:

      【解决方案11】:

      Int32 数字 = 70000;

              string str = Convert.ToString(num, 2);
          //convert INT32 to Binary string   
              Int32 strl = str.Length;
          //detect string length
      
      
              string strhi, strlo;
          //ifvalue is greater than 16 bit 
              if (strl > 16)
              {
                 int lg = strl - 16;
                //dtect bits in higher word 
                 strlo = str.Substring(lg, 16);
           ///move lower word string to strlo 
      
                  strhi = str.Substring(0, lg);
         //mov higher word string to strhi
      
              }
              else
      //if value is less than 16 bit
              {
                 strhi = "0";
      //set higher word zero
                 strlo = str;
      ///move lower word string to strlo 
      
              }
      
              Int16 lowword, hiword;
              lowword = Convert.ToInt16(strlo, 2);
              hiword = Convert.ToInt16(strhi, 2);
              ////convert binary string to int16
              }
      

      【讨论】:

        【解决方案12】:

        我没有使用位运算符,但对于无符号值,这可能有效:

        public (ushort, ushort) SplitToUnsignedShorts(uint value)
        {
            ushort v1 = (ushort) (value / 0x10000);
            ushort v2 = (ushort) (value % 0x10000);
        
            return (v1, v2);
        }
        

        或者它的表达式主体版本:

        public (ushort, ushort) SplitToUShorts(uint value)
            => ((ushort)(value / 0x10000), (ushort)(value % 0x10000));
        

        至于标志,您必须决定如何拆分数据。两个中只能有一个负输出。请记住,有符号值总是牺牲一位来存储数字的负状态。这基本上将您在该变量中可以拥有的最大值“减半”。这也是 uint 可以存储两倍于有符号 int 的原因。

        至于将其编码为您的目标格式,您可以选择将第二个数字设为无符号短整数,以保留数值,或者您可以手动对其进行编码,这样一位现在代表该值的符号。这样,虽然您会丢失最初预期的符号位数值,但您不会丢失原始二进制数据,并且始终可以将其重建为原始值。

        最终归结为您希望如何存储和处理这些数据。只要您知道如何从您的编码值中提取(或合并到)数据,就不会丢失位,进而不会丢失数据。

        【讨论】:

          【解决方案13】:

          你可以使用这个 Nuget 包 LSharpCode.XExtensions 安装好之后就可以这样使用了:

          using LSharpCode.XExtensions.MathExtensions;
          Int32 varInt32name;
          Int16 varint16nameLow;
          Int16 varint16nameHigh;
          varInt32name.ToTwoInt16(out varint16nameLow,out varint16nameHigh);
          

          【讨论】:

            猜你喜欢
            • 2016-02-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-11-06
            • 1970-01-01
            • 2013-12-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多