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

11
我希望能够将一个高位无符号整数(即使用最高位的值)转换为有符号整数。在这种情况下,我并不在意该值是否高于有符号整数类型的最大值。我只想将其转换为位值表示的有符号整数。换句话说,我希望它能得到一个负数。
然而,在VB.NET中,CType操作不是这样工作的(也包括其他转换函数,如CShort和CInteger)。当您尝试转换一个超过所需有符号类型的最大值的无符号值时,它会抛出OverflowException异常,而不是返回一个负数。例如:
Dim x As UShort = UShort.MaxValue
Dim y As Short = CShort(x)  ' Throws OverflowException

值得一提的是,DirectCast 操作不能用于在有符号类型和无符号类型之间转换数值,因为这两种类型都不继承或实现另一种类型。例如:
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'

我已经找到了一种实现自己想要的功能的方法,但它看起来有点冗长。这是我的解决方案:
Dim x As UShort = UShort.MaxValue
Dim y As Short = BitConverter.ToInt16(BitConverter.GetBytes(x), 0)  ' y gets set to -1

就像我所说的那样,这是可行的。但如果有更简单、更清晰的VB.NET实现方式,我很想知道。


3
在 VB 中,我从未想过这很困难;而在 C# 中,这只需要一个 checked/unchecked 关键字即可解决。 - Marc Gravell
11个回答

14

如果你频繁使用BitConverter,那么会有些不方便 - 特别是对于性能。如果我是你,我可能会极力想要在C#中添加一个实用程序库来进行直接转换(通过unchecked,尽管在C#中unchecked通常是默认设置)。并且为此引用该库。另一个选项可能是滥用"union"结构体; 以下内容应该相对容易地转换为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;
即。
<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

1
哦,天啊,那太邪恶了!虽然非常有趣。我不知道可以像那样有重叠的字段。大多数时候,我不介意被迫使用VB.NET,但像这样的时候,我真的很讨厌它。当然,与将()用于方法参数和数组的想法相比,这算不了什么,那个想法简直是一个灾难! - Steven Doggart
1
@StevenDoggart 上面的方法并不是理想的,但比每次分配一个数组要好得多(这就是GetBytes所做的)。对于() - 是的,那很混乱。 - Marc Gravell
1
我认为将UInt32转换为Int32的更好方法是使用0x80000000UI异或,加上有符号(负)值0x80000000,并将结果强制转换为Int32 - supercat
这绝对是最快的转换方式。BitConverter 比 Evil 慢 10 倍,而 Marshal 比 BitConverter 还慢 10 倍。 - Brain2000

2
我找到了这个:??在VB.NET中进行类型转换时的问题??

页面的中间部分有以下内容:

旧版VB中“Proper”技巧的“绕路”十六进制再转回来仍然有效!

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

看起来它的工作相当顺畅!


负数导致结果溢出 - Dmytro

2
非常简单:

32位

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

val32最终结果为-1294967296。

对于16位

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

val16最终的值为-5536。


1
是的,那样做可以实现,但是将值格式化为字符串,然后解析该字符串以返回整数的效率似乎不如我的原始“BitConverter”解决方案高效。 - Steven Doggart
1
真的。速度较慢。我运行了一项测试,大约有 1 亿次转换调用。EvilUnion 时间为 1.21 秒, BitConverter 时间为 2.67 秒,而十六进制字符串技术时间则长达 13.53 秒...... 获胜者是 "Evil"。 - JerryCic
不错。感谢您抽出时间进行测试。很高兴得到了我所怀疑的明确确认。我想邪恶会获胜,尽管我不喜欢它 :) - Steven Doggart

2
我认为最简单的方法如下:

我认为最简单的方法如下:

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

也许你的意思是 number >= 32768 - FLCL

2
在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

至少在.NET中,您可以创建一个扩展方法来使其更加美观


谢谢。可能不如Marc的EvilUnion解决方案高效,但可能更具自我记录性... - Steven Doggart
2
是的,在VB6中另一种方法是使用Type(又名结构体),像他的方法一样使用LSet来覆盖另一个字段。或者当然,还有无处不在的CopyMemory... - tcarvin

1
通常情况下,这在高级语言中可以通过流来完成,但是 .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

令我惊讶的是,根据我获得的统计数据,使用Marshal似乎比使用Math更有效。
4 seconds of v1 yielded: 2358173 conversions
4 seconds of v2 yielded: 4069878 conversions

来自测试:

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

更让人惊奇的是,与 C# 样式转换相比,Marshal 方法似乎没有多大效果。在第一次运行时,Marshal 方法较慢,但在第二次运行时,Marshal 方法较快。这是第二次运行的结果。

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

使用这段代码。
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();
    }
}

现在引入国王;
// 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;
}

嗯,结果相当有趣

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

1
我也遇到了这个问题,不太喜欢使用BitConverter方法,因为它似乎不是很优化。所以,我考虑将数据存储在内存中,一个int和uint只需要4个字节。
以下似乎是处理这个问题最有效的方法,并且适用于可以使用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

希望这能帮助到某些人。

1
BitConverter 方法的速度大约快了10倍 :( - Brain2000

0

死灵法师。

作为Marc Gravell答案的补充,如果你想知道如何在头部执行它:

通常可以这样写:

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

由于类型检查,代码会像这样:
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;
}

然后,如果你想在头部执行它,可以这样做:

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

0
如果您需要经常这样做,可以创建高性能的扩展方法,例如:
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

这使得有符号和无符号的转换变得非常简单:

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

0
在下面的示例中,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

从概念上讲,它与变量“转换”的方式不同。 相反,所示范的方法是存储实体。 同时提供了访问其中各个部分的不同方法。

由于操作是“访问”而不是“转换”,因此非常快速,精简和有效(请参见Marc帖子中成员的评论)。

编译器处理字节序问题。


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