问题
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
andCInteger
). 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. 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. 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.
回答1:
Constant use of BitConverter
is going to be a bit inconvenient if you are using that a lot - in particular for performance. 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. Another option might be to abuse a "union" struct; the following should translate to VB fairly easily:
[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;
i.e.
<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:
Back in the VB6 days we had to write routines like this all the time:
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
回答3:
Very simple:
For 32 bit
Dim uVal32 As UInt32 = 3000000000
Dim Val32 As Int32 = Convert.ToInt32(uVal32.ToString("X8"), 16)
val32 ends up = -1294967296
For 16 bit
Dim uVal16 As UInt16 = 60000
Dim Val16 As Int16 = Convert.ToInt16(uVal16.ToString("X4"), 16)
val16 ends up = -5536
回答4:
I found this: ??problems typecasting in 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!
Dim unsigned as UInt16 = 40000
Dim signed as Int16 = CShort(Val("&H" & Hex(unsigned)))
It seems to work pretty slick!
回答5:
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
回答6:
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. So, I considered that the storage of the data in memory is really just 4 bytes for both an int and uint.
The following seems to be the most efficient way to handle this and works in all .NET languages that can use the Marshal class...
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.
回答7:
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.
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
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. 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
回答8:
Necromancing.
As a complement to Marc Gravell's answer, if you wonder how to do it in the head:
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
回答9:
In this example below, the answer by Marc Gravell is extended to demonstrate usefulness in 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).
Endianess is handled by the compiler.
回答10:
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)
回答11:
Don't know VB, but I expect it is similar to C# as it is .NET code. In C# you can simply use type cast:
UInt16 ui = 65000;
Int16 i = (Int16)ui;
Done.
来源:https://stackoverflow.com/questions/14708778/how-to-convert-unsigned-integer-to-signed-integer-without-overflowexception