Как преобразовать целое число без знака в целое число со знаком без исключения 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;
сделано.