简体   繁体   English

如何在没有OverflowException的情况下将无符号整数转换为有符号整数

[英]How to convert unsigned integer to signed integer without OverflowException

I would like to be able to convert a high-valued unsigned-integer (a value that uses the highest-order bit) to a signed-integer. 我希望能够将高值无符号整数(使用最高位的值)转换为有符号整数。 In this case, I don't care that the value is higher than the maximum value of the signed integer type. 在这种情况下,我不关心该值是否高于有符号整数类型的最大值。 I just want it to convert to whatever the bit-values represent as a signed-integer. 我只是希望它转换为位值表示的有符号整数。 In other words, I would expect it to result in a negative number. 换句话说,我希望它会导致负数。

However, with VB.NET, the CType operation doesn't work that way (or any of the other conversion functions like CShort and CInteger ). 但是,使用VB.NET时, CType操作不会以这种方式工作(或任何其他转换函数,如CShortCInteger )。 When you try to convert an unsigned value that is higher than the desired signed-type's maximum value, it throws an OverflowException rather than returning a negative number. 当您尝试转换高于所需signed-type的最大值的无符号值时,它会抛出OverflowException而不是返回负数。 For instance: 例如:

Dim x As UShort = UShort.MaxValue
Dim y As Short = CShort(x)  ' Throws OverflowException

It's worth mentioning, too, that the DirectCast operation cannot be used to cast the value between the signed and unsigned types, since neither type inherits or implements the other. 值得一提的是, DirectCast操作不能用于在有符号和无符号类型之间转换值,因为这两种类型都不继承或实现另一种类型。 For instance: 例如:

Dim x As UShort = UShort.MaxValue
Dim y As Short = DirectCast(x, Short)  ' Won't compile: "Value of type 'UShort' cannot be converted to 'Short'

I have figured out one way to do what I want, but it seems unnecessarily ugly. 我已经想出了一种方法来做我想做的事,但它似乎不必要地丑陋。 Here's how I got it to work: 以下是我如何使用它:

Dim x As UShort = UShort.MaxValue
Dim y As Short = BitConverter.ToInt16(BitConverter.GetBytes(x), 0)  ' y gets set to -1

Like I said, that works, but if there's an easier, cleaner way of doing it in VB.NET, I'd love to know what it is. 就像我说的那样,但是如果在VB.NET中有一种更简单,更清晰的方式,我很想知道它是什么。

Constant use of BitConverter is going to be a bit inconvenient if you are using that a lot - in particular for performance. 如果你经常使用BitConverter会有点不方便 - 特别是在性能方面。 If that was me, I would be sorely tempted to add a utilities library in C# that can do direct conversions (via unchecked , although unchecked is normally the default in C# anyway ), and reference that library for this. 如果是我,我会非常想添加一个实用程序在C#中,可以做直接转换库(通过unchecked ,虽然unchecked通常是在C#中默认反正 ),并参考该库这一点。 Another option might be to abuse a "union" struct; 另一种选择可能是滥用“联盟”结构; the following should translate to VB fairly easily: 以下内容应该很容易转换为VB:

[StructLayout(LayoutKind.Explicit)]
struct EvilUnion
{
    [FieldOffset(0)] public int Int32;
    [FieldOffset(0)] public uint UInt32;
}
...
var evil = new EvilUnion();
evil.Int32 = -123;
var converted = evil.UInt32;

ie

<System.Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Explicit)>
Structure EvilUnion
    <System.Runtime.InteropServices.FieldOffset(0)>
    Public Int32 As Integer
    <System.Runtime.InteropServices.FieldOffset(0)>
    Public UInt32 As UInteger
End Structure
...
Dim evil As New EvilUnion
evil.Int32 = -123
Dim converted = evil.UInt32

Back in the VB6 days we had to write routines like this all the time: 回到VB6时代,我们不得不一直编写这样的例程:

Private Function ToShort(ByVal us As UShort) As Short
   If (us And &H8000) = 0 Then
      Return CType(us, Short)
   Else
      Return CType(CType(us, Integer) - UShort.MaxValue - 1, Short)
   End If
End Function

At least in .NET you can create an extension method out of this to make it nicer through 至少在.NET中,您可以创建一个扩展方法,使其更好

Very simple: 很简单:

For 32 bit 对于32位

    Dim uVal32 As UInt32 = 3000000000
    Dim Val32 As Int32 = Convert.ToInt32(uVal32.ToString("X8"), 16)

val32 ends up = -1294967296 val32结束= -1294967296

For 16 bit 对于16位

    Dim uVal16 As UInt16 = 60000
    Dim Val16 As Int16 = Convert.ToInt16(uVal16.ToString("X4"), 16)

val16 ends up = -5536 val16最终= -5536

I found this: ??problems typecasting in VB.NET?? 我发现这个: 在VB.NET中进行类型转换的问题?

About halfway down the page is this: 大约一半的页面是这样的:

The old, VB "Proper" trick of "side-stepping" out to Hexadecimal and back again still works! 旧的VB“正确”伎俩“踩到”十六进制并再次返回仍然有效!

Dim unsigned as UInt16 = 40000
Dim signed as Int16 = CShort(Val("&H" & Hex(unsigned)))

It seems to work pretty slick! 它看起来很漂亮!

I think the easiest way is as follows: 我认为最简单的方法如下:

Public Function PutSign(ByVal number As UShort) As Short
    If number > 32768 Then 'negative number
        Return (65536 - number) * -1
    Else
        Return number
    End If
End Function

I was just faced with this issue as well and didn't like the BitConverter approach as it seems like it's not very optimized. 我刚刚面对这个问题,并且不喜欢BitConverter方法,因为它似乎没有得到很好的优化。 So, I considered that the storage of the data in memory is really just 4 bytes for both an int and uint. 所以,我认为内存中的数据存储对于int和uint来说实际上只有4个字节。

The following seems to be the most efficient way to handle this and works in all .NET languages that can use the Marshal class... 以下似乎是处理此问题的最有效方法,适用于可以使用Marshal类的所有.NET语言...

Dim x as UInteger = &H87654321
Dim gch as GCHandle = GCHandle.Alloc(x, Pinned)
Dim y as Integer = Marshal.ReadInt32(gch.AddrOfPinnedObject)
gch.Free

Hope this helps someone. 希望这有助于某人。

Normally, this would be done with streams in higher level languages, but .Net framework exposes a way to do so without intermediate stream objects using Marshal. 通常情况下,这将使用更高级语言的流完成,但.Net框架使用Marshal公开了一种没有中间流对象的方法。

Imports System.Runtime.InteropServices
Module Module1
    Sub Main()
        Dim given As Int16 = -20
        Dim buffer As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(given))
        Marshal.StructureToPtr(given, buffer, False)
        Dim result As UInt16 = Marshal.PtrToStructure(buffer, GetType(UInt16))
        MsgBox(result)
    End Sub
End Module

To my surprise, Using Marshal seems to be more efficient than using Math, based on the stats I got 令我惊讶的是,根据我得到的统计数据,使用Marshal似乎比使用Math更有效

4 seconds of v1 yielded: 2358173 conversions
4 seconds of v2 yielded: 4069878 conversions

from test: 来自测试:

Imports System.Runtime.InteropServices

Module Module1
    Function v1(given As Int16) As UInt16
        Dim buffer As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(given))
        Marshal.StructureToPtr(given, buffer, False)
        Dim result As UInt16 = Marshal.PtrToStructure(buffer, GetType(UInt16))
        v1 = result
    End Function

    Function v2(given As Int16) As UInt16
        If given < 0 Then
            given = (Not given) + 1
        End If
        v2 = given
    End Function


    Sub Main()
        Dim total0 As Integer
        Dim total1 As Integer
        Dim t0 As DateTime = DateTime.Now()
        While ((DateTime.Now() - t0).TotalSeconds() < 4)
            v1(-Rnd() * Int16.MaxValue)
            total0 = total0 + 1
        End While

        Console.WriteLine("4 seconds of v1 yielded: " & total0 & " conversions")
        t0 = DateTime.Now()
        While ((DateTime.Now() - t0).TotalSeconds() < 4)
            v2(-Rnd() * Int16.MaxValue)
            total1 = total1 + 1
        End While
        Console.WriteLine("4 seconds of v2 yielded: " & total1 & " conversions")

        Console.ReadKey()
    End Sub

End Module

Stranger still, Marshal approach seems negligibly as effective as C# style cast. 更奇怪的是,Marshal方法似乎与C#style cast一样有效。 On my first run, marshal approach was slower, but on second run, marshal approach was faster. 在我的第一次运行中,编组方法较慢,但在第二次运行时,编组方法更快。 This is the result of the second run 这是第二次运行的结果

4 seconds of v1 yielded: 1503403 conversions
4 seconds of v2 yielded: 1240585 conversions
4 seconds of v3 yielded: 1592731 conversions

using this code 使用此代码

using System;
using System.Runtime.InteropServices;

class Program
{
    static DateTime startTime = DateTime.Now;        

    static double time {
        get {
            return (DateTime.Now - startTime).TotalMilliseconds;
        }
    }
    static ushort v1(short given) {
        if (given > 0) {
            return (ushort)given;
        }
        return (ushort)(~given + 1);
    }    

    static ushort v2(short given) {
        var buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(given));
        Marshal.StructureToPtr(given, buffer, false);
        ushort result = (ushort)Marshal.PtrToStructure(buffer, typeof(ushort));
        return result;
    }

    static ushort v3(short given)
    {
        return (ushort)given;
    }

    static void Main(string[] args)
    {
        int total0 = 0;
        int total1 = 0;
        int total2 = 0;
        double t0;

        t0 = time;
        while (time - t0 < 4000) {
            v1((short)(-new Random().NextDouble() * Int16.MaxValue));
            ++total0;
        }

        Console.WriteLine("4 seconds of v1 yielded: " + total0 + " conversions");

        t0 = time;
        while (time - t0 < 4000) {
            v2((short)(-new Random().NextDouble() * Int16.MaxValue));
            ++total1;
        }
        Console.WriteLine("4 seconds of v2 yielded: " + total1 + " conversions");


        t0 = time;
        while (time - t0 < 4000) {
            v3((short)(-new Random().NextDouble() * Int16.MaxValue));
            ++total2;
        }
        Console.WriteLine("4 seconds of v3 yielded: " + total2 + " conversions");


        Console.ReadKey();
    }
}

Now to bring in the king; 现在引进国王;

// ConsoleApplication3.cpp : main project file.

#include "stdafx.h"

using namespace System;
using namespace System::Runtime::InteropServices;

unsigned __int16 v4(__int16 given) {
    return (unsigned __int16)given;
}

public ref class Program
{
public:
    static DateTime startTime = DateTime::Now;

    static property double time {
        double get() {
            return (DateTime::Now - startTime).TotalMilliseconds;
        }
    }

    static UInt16 v1(Int16 given) {
        if (given > 0) {
            return given;
        }
        return (UInt16)(~given + 1);
    }    

    static UInt16 v2(Int16 given) {
        IntPtr buffer = Marshal::AllocCoTaskMem(Marshal::SizeOf(given));
        Marshal::StructureToPtr(given, buffer, false);
        Type ^t = UInt16::typeid;
        UInt16 result = (UInt16)Marshal::PtrToStructure(buffer, t);
        return result;
    }

    static UInt16 v3(Int16 given)
    {
        return (UInt16)given;
    }

    typedef String ^string;
    static void _Main(array<string> ^args)
    {
        int total0 = 0;
        int total1 = 0;
        int total2 = 0;
        int total3 = 0;
        double t0;

        t0 = time;
        while (time - t0 < 4000) {
            Double d = (gcnew Random())->NextDouble();
            v1((short)(-d * Int16::MaxValue));
            ++total0;
        }

        Console::WriteLine("4 seconds of v1 yielded: " + total0 + " conversions");

        t0 = time;
        while (time - t0 < 4000) {
            v2((short)(-((gcnew Random())->NextDouble()) * Int16::MaxValue));
            ++total1;
        }
        Console::WriteLine("4 seconds of v2 yielded: " + total1 + " conversions");


        t0 = time;
        while (time - t0 < 4000) {
            v3((short)(-((gcnew Random())->NextDouble()) * Int16::MaxValue));
            ++total2;
        }
        Console::WriteLine("4 seconds of v3 yielded: " + total2 + " conversions");

        t0 = time;
        while (time - t0 < 4000) {
            v4((short)(-((gcnew Random())->NextDouble()) * Int16::MaxValue));
            ++total3;
        }
        Console::WriteLine("4 seconds of v4 yielded: " + total3 + " conversions");


        Console::ReadKey();
    }
};


int main(array<System::String ^> ^args)
{
    Program::_Main(args);
    return 0;
}

well, the results are pretty interesting 好吧,结果非常有趣

4 seconds of v1 yielded: 1417901 conversions
4 seconds of v2 yielded: 967417 conversions
4 seconds of v3 yielded: 1624141 conversions
4 seconds of v4 yielded: 1627827 conversions

Necromancing. Necromancing。
As a complement to Marc Gravell's answer, if you wonder how to do it in the head: 作为Marc Gravell答案的补充,如果你想知道如何做到这一点:

You can generally write it as: 您通常可以将其写为:

<unsigned_type> value = unchecked(<unsigned_type>.MaxValue + your_minus_value + 1);

Because of type-checking, code goes like this: 由于类型检查,代码如下:

public uint int2uint(int a)
{
    int sign = Math.Sign(a);
    uint val = (uint) Math.Abs(a);

    uint unsignedValue;
    if(sign > 0) // +a
        unsignedValue = unchecked(UInt32.MaxValue + val + 1);
    else // -a, a=0
        unsignedValue = unchecked(UInt32.MaxValue - val + 1);

    return unsignedValue;
}

And then, if you want to do it in the head, you can do it like this: 然后,如果你想在头脑中做到这一点,你可以这样做:

BigInt mentalResult= <unsigned_type>.MaxValue + your_value;
mentalResult = mentalResult % <unsigned_type>.MaxValue;
if (your_value < 0) // your_value is a minus value
    mentalResult++;

// mentalResult is now the value you search

If you need to do this often, you can create high-performance extension methods like these: 如果您需要经常这样做,您可以创建如下的高性能扩展方法:

Imports System.Runtime.CompilerServices

Module SignConversionExtensions

    <StructLayout(LayoutKind.Explicit)> _
    Private Structure Union
        <FieldOffset(0)> Public Int16 As Int16
        <FieldOffset(0)> Public UInt16 As UInt16
    End Structure

    <Extension()> Public Function ToSigned(ByVal n As UInt16) As Int16
        Return New Union() With {.UInt16 = n}.Int16
    End Function

    <Extension()> Public Function ToUnsigned(ByVal n As Int16) As UInt16
        Return New Union() With {.Int16 = n}.UInt16
    End Function

End Module

This makes signed-unsigned conversions very simple: 这使得有符号无符号转换变得非常简单:

Dim x As UShort = UShort.MaxValue  ' unsigned x = 0xFFFF (65535)
Dim y As Short = x.ToSigned        ' signed y = 0xFFFF (-1)

In this example below, the answer by Marc Gravell is extended to demonstrate usefulness in VB: 在下面的这个例子中,Marc Gravell的答案被扩展为在VB中证明有用:

<System.Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Explicit)>
Structure vbUnion16
    <System.Runtime.InteropServices.FieldOffset(0)>
    Public UnSigned16 As UInt16
    <System.Runtime.InteropServices.FieldOffset(0)>
    Public Signed16 As Int16
    <System.Runtime.InteropServices.FieldOffset(0)>
    Public High8 As Byte
    <System.Runtime.InteropServices.FieldOffset(1)>
    Public Low8 As Byte
End Structure

Conceptually, it is different from "converting" types of variable. 从概念上讲,它与“转换”变量类型不同。 Rather, the method demonstrated storing an entity. 相反,该方法展示了存储实体。 At the same time different ways of access to various parts in it are made available. 同时,可以使用不同的方式访问其中的各个部分。

Since the operation is "accessing" not "converting", it is very fast, lean and efficient (see member's comments on Marc's post). 由于操作是“访问”而不是“转换”,因此它非常快速,精简和高效(参见成员对Marc帖子的评论)。

Endianess is handled by the compiler. Endianess由编译器处理。

Don't know VB, but I expect it is similar to C# as it is .NET code. 不知道VB,但我希望它与C#类似,因为它是.NET代码。 In C# you can simply use type cast: 在C#中你可以简单地使用类型转换:

UInt16 ui = 65000;
Int16   i = (Int16)ui;

Done. 完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM