Как преобразовать целое число без знака в целое число со знаком без исключения OverflowException
Я хотел бы иметь возможность конвертировать высокозначное беззнаковое целое число (значение, которое использует бит наивысшего порядка) в целое число со знаком. В этом случае мне все равно, что значение больше максимального значения знакового целочисленного типа. Я просто хочу, чтобы он конвертировался в то, что битовые значения представляют как знак-целое. Другими словами, я ожидаю, что это приведет к отрицательному числу.
Однако с VB.NET операция 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 есть более простой и понятный способ сделать это, я хотел бы знать, что это такое.
Ответы
Ответ 1
Постоянное использование BitConverter
будет немного неудобным, если вы используете это много - особенно для производительности. Если бы это был я, мне было бы очень сложно добавить библиотеку утилиты на С#, которая может делать прямые преобразования (через unchecked
, хотя 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
Ответ 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 вы можете создать метод расширения из этого, чтобы сделать его более приятным через
Ответ 3
Очень просто:
Для 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
Ответ 4
Я нашел это: ?? проблемы с typecasting в VB.NET?
Примерно на полпути вниз страница:
Старый, VB "Правильный" трюк "бокового шага" из шестнадцатеричного и назад снова работает!
Dim unsigned as UInt16 = 40000
Dim signed as Int16 = CShort(Val("&H" & Hex(unsigned)))
Кажется, что он работает довольно аккуратно!
Ответ 5
Я думаю, что самый простой способ:
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
Ответ 6
Я просто столкнулся с этой проблемой, и мне не понравился подход BitConverter, поскольку он кажется, что он не очень оптимизирован.
Итак, я считал, что хранение данных в памяти на самом деле всего 4 байта для int и uint.
Ниже представлен наиболее эффективный способ справиться с этим и работает на всех языках .NET, которые могут использовать класс маршала...
Dim x as UInteger = &H87654321
Dim gch as GCHandle = GCHandle.Alloc(x, Pinned)
Dim y as Integer = Marshal.ReadInt32(gch.AddrOfPinnedObject)
gch.Free
Надеюсь, это поможет кому-то.
Ответ 7
Обычно это делается с потоками на языках более высокого уровня, но .Net Framework предлагает способ сделать это без промежуточных потоковых объектов, использующих 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
Еще более странный подход Маршала кажется столь же эффективным, как и в стиле С#. При первом запуске маршал был медленнее, а во втором - маршал быстрее. Это результат второго запуска
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
Ответ 8
Некромантия.
В дополнение к ответу Марка Гравелла, если вам интересно, как это сделать в голове:
Обычно вы можете записать его как:
<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
Ответ 9
Если вам нужно делать это часто, вы можете создать такие методы расширения:
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)
Ответ 10
В этом примере ниже ответ Марка Гравелла расширен, чтобы продемонстрировать полезность в 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
Концептуально это отличается от "преобразования" типов переменных. Скорее, метод продемонстрировал сохранение сущности. В то же время доступны различные способы доступа к различным частям в нем.
Поскольку операция "осуществляет доступ", а не "конвертирует", она очень быстрая, экономная и эффективная (см. Комментарии участников к сообщению Марка).
Endianess обрабатывается компилятором.
Ответ 11
Не знаю VB, но я ожидаю, что он похож на С#, как на .NET-код.
В С# вы можете просто использовать тип cast:
UInt16 ui = 65000;
Int16 i = (Int16)ui;
Готово.