How to convert unsigned integer to signed integer without OverflowException
Asked Answered
P

11

11

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.

Phallus answered 5/2, 2013 at 13:34 Comment(1)
It never would have occurred to me that this was difficult in VB; in C# this is simply a checked / unchecked keyword away...Steadfast
S
14

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
Steadfast answered 5/2, 2013 at 13:50 Comment(4)
Oh man, that is evil! Very interesting, though. I didn't know it was possible to have overlapping fields like that. Most of the time, I don't mind being forced to use VB.NET, but it's times like this that I really hate it. Of course this is nothing in comparison to the shockingly-bad disaster-of-an-idea which is the use of () for both method parameters AND arrays!!Phallus
@StevenDoggart the above isn't ideal, but it is much better than allocating an array every time (which is what GetBytes does). For () - yeah, that's a mess.Steadfast
I think a better way of converting from UInt32 to Int32 is to xor with 0x80000000UI, add the signed (negative) value 0x80000000, and cast the result to Int32.Epanaphora
This is by far the fastest way to convert. The bitconverter is 10x slower than the evil, and the marshal is 10x slower than the bitconverter.Angle
C
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

Centromere answered 5/2, 2013 at 14:16 Comment(2)
Thanks. Probably not as efficient as Marc's EvilUnion solution, but possibly more self-documenting...Phallus
Yeah, the other way to do this in VB6 was to use a Type (aka structure) like his approach and use LSet to hammer one field over the other. Or of course, the ubiquitous CopyMemory...Centromere
A
2

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
Alumina answered 23/3, 2015 at 14:8 Comment(1)
may be, u mean number >= 32768Dermatosis
M
2

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

Mckeehan answered 19/8, 2015 at 19:54 Comment(3)
Yes, that would work, but it seems highly unlikely that formatting the value as a string, and then parsing that string to get back to an integer would be as efficient as my original BitConverter solution.Phallus
True. It is slow. I ran a test some 100,000,000 conversion calls. EvilUnion was 1.21 secs, bitconverter was 2.67 secs and the hex string technique was 13.53 secs..... the winner is "Evil".Mckeehan
Nice. Thanks for taking the time to test that. Good to have a definitive confirmation of what I suspected. I figured evil would win, as much as I don't like it :)Phallus
V
2

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!

Vinitavinn answered 23/9, 2015 at 16:2 Comment(1)
results in overflow for negative numbersCoworker
B
1

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.

Burmese answered 23/8, 2016 at 17:57 Comment(1)
The BitConverter approach is about 10x faster :(Angle
C
1

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
Coworker answered 29/5, 2018 at 6:8 Comment(0)
R
0

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
Rexanne answered 12/11, 2015 at 9:55 Comment(0)
I
0

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)
Idol answered 2/7, 2018 at 15:56 Comment(0)
P
0

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.

Popham answered 20/2, 2019 at 10:50 Comment(0)
G
-2

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.

Gilolo answered 25/1, 2017 at 15:28 Comment(1)
Nope. That's precisely what I was asking. in VB, CType and DirectCast are the type-casting operators, and neither work in this case.Phallus

© 2022 - 2024 — McMap. All rights reserved.