Как преобразовать целое число без знака в целое число со знаком без исключения OverflowException

Я хотел бы иметь возможность преобразовать многозначное целое число без знака (значение, которое использует бит высшего порядка) в целое число со знаком. В этом случае мне все равно, что значение выше максимального значения целочисленного типа со знаком. Я просто хочу, чтобы он преобразовывался в то, что битовые значения представляют собой целое число со знаком. Другими словами, я ожидал бы, что это приведет к отрицательному числу.

однако, с VB.NET, the CType операция не работает таким образом (или любой из другие функции преобразования, такие как CShort иCInteger). При попытке преобразовать значение без знака, которое выше максимального значения желаемого типа со знаком, он выдает OverflowException вместо того, чтобы возвращать отрицательное число. Например:

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

стоит также упомянуть, что DirectCast операция не может использоваться для приведения значения между типами signed и unsigned, так как ни один тип не наследует или не реализует другой. Например:

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 я хотел бы знать, что это.

10 ответов


постоянное использование BitConverter будет немного неудобно, если вы используете это много - в частности, для производительности. Если бы это был я, у меня было бы сильное искушение добавить библиотеку утилит в C# , которая может выполнять прямые преобразования (через unchecked, хотя unchecked обычно используется по умолчанию в C# в любом случае), и ссылка на эту библиотеку для этого. Другим вариантом может быть злоупотребление структурой "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

назад в 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 вы можете создать метод расширения из этого, чтобы сделать его приятнее через


очень просто:

для 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


Я нашел вот это: ??проблемы typecasting в VB.NET??

примерно на полпути вниз по странице это:

старый, VB" правильный "трюк" бокового шага " до шестнадцатеричного и назад снова все еще работает!

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

Кажется, это работает довольно гладко!


Я думаю, что самый простой способ заключается в следующем:

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

Я просто столкнулся с этой проблемой, а также и не нравится подход BitConverter, как кажется, что это не очень оптимизирован. Итак, я считал, что хранение данных в памяти действительно составляет всего 4 байта как для int, так и для uint.

Кажется, что это наиболее эффективный способ справиться с этим и работает на всех языках .NET, которые могут использовать класс Marshal...

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

надеюсь, это кому-то поможет.


обычно это делается с потоками на языках более высокого уровня, но .Net framework предоставляет способ сделать это без промежуточных объектов потока с помощью Маршала.

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

к моему удивлению, использование Маршала кажется более эффективным, чем использование математики, основываясь на статистике, которую я получил

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#. На моем первом запуске маршал подходил медленнее, но на второй заход, маршал подходил быстрее. Это результат второго прогона

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

некромантии.
В дополнение к ответу Марка Гравелла, если вам интересно, как это сделать в голове:

можно вообще написать так:

<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

Если вам нужно делать это часто, вы можете создать такие методы расширения:

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
Dim y As Short = x.ToSigned

Не знаю VB, но я ожидаю, что он похож на C#, поскольку это .NET-код. В C# вы можете просто использовать тип cast:

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

сделано.