How can I safely downsize a long to an int?

1k Views Asked by At

According to the question, "Can I convert long to int?", the best way in C# to convert a long to an int is:

int myIntValue = unchecked((int)myLongValue);

How can I do this in VB.NET? VB.NET does not have the concept of unchecked to my understanding.

When I convert the code in a converter, it says to use CInt(). But CInt throws a system.overflowexception. Using unchecked in C# prevents a system.overflowexception.

I'd rather not catch the exception.

Dim x As Long = Int32.MaxValue
x = x + 100
Console.WriteLine(x)

Dim y As Integer = CInt(x)
Console.WriteLine(y)
6

There are 6 best solutions below

3
On BEST ANSWER

As you can see in the answers to my related, similar question, there are ways to do that sort of thing, but nothing really more convenient than a simple Try/Catch block. If you want to avoid the inefficiency of a Try/Catch block, I would recommend checking the value first, like this:

If (myLongValue >= Integer.MinValue) AndAlso (myLongValue <= Integer.MaxValue) Then
    myIntValue = CInt(myLongValue)
End If

Or, if you want to set the value to something specific when it is outside of the range:

If myLongValue < Integer.MinValue Then
    myIntValue = Integer.MinValue
ElseIf myLongValue > Integer.MaxValue Then
    myIntValue = Integer.MaxValue
Else
    myIntValue = CInt(myLongValue)
End If

If you wanted to make it more convenient, you could make it a function. If convenience is the ultimate goal, you could even make it an extension method:

<Extension>
Public Function AsInt32(value As Long) As Integer
    If value < Integer.MinValue Then
        Return Integer.MinValue
    ElseIf value > Integer.MaxValue Then
        Return Integer.MaxValue
    Else
        Return CInt(value)
    End If
End Function
1
On

Is there a conversion function already written that will check if its greater than that, and if not, set it to Int32.MaxValue?

No but that would be straightforward:

Dim l as Long = Int32.MaxValue + 12433243243

Dim i as Int32
if l > int32.MaxValue Then
   i = int32.MaxValue
elseIf l < int32.MinValue
   i = int32.MinValue
Else
   i = CInt(l)
End If

Or in a single line:

i = if(l > int32.MaxValue, int32.MaxValue, if(l < int32.MinValue, int32.MinValue, CInt(l)))

Or using Math.Min:

i = CInt(Math.Max(Math.Min(l, int32.MaxValue), int32.MinValue))
2
On

You can use the mod function with Convert.ToInt32:

i = Convert.ToInt32(x Mod (Int32.MaxValue))
0
On

A branchless way of performing this kind of operation would be:

Function UInt64ToInt32Wrap(v As UInt64) As Int32
    Return CInt((CUInt(v And &HFFFFFFFFUI) Xor &H80000000UI) + &H80000000)
End Function
Function Int64ToInt32Wrap(v As Int64) As Int32
    Return CInt(((v And &HFFFFFFFFL) Xor &H80000000L) + &H80000000)
End Function
Function UInt32ToInt32Wrap(v As UInt32) As Int32
    Return CInt((v Xor &H80000000UI) + &H80000000)
End Function

Note that in each case, the last hex value is an Int32 with value -2147483648, so adding it to an unsigned type will cause a promotion to Int64; adding that to the result of the previous XOR will always yield a value that is in range for an Int32.

I wish something like one of these had been provided as a compiler intrinsic, since even though the operations are branchless and the JIT can probably reduce everything to a 32-bit "xor" and a 32-bit "add" instruction, it shouldn't be necessary to use ANY code to accomplish that.

Incidentally, I think another approach would be to define a structure with a "LayoutKind.Explicit" attribute that overlaid an Int32 and a UInt32; that would probably eliminate the unnecessary mutually-cancelling arithmetic operations, but it might force the jitter to store a value to memory and reload it when it could otherwise keep things in registers.

3
On

Here's a fairly painless way to down cast a long to an int, just extracting the low-order 32 bits without throwing an exception. No 64-bit division, no try/catch blocks, just bit twiddling and a single comparison.

Private Function CastLongToInt(x As Long) As Integer
    Dim value As Integer = CType(x And &H7FFFFFFFL, Integer) ' get the low order 31 bits

    ' If bit 32 (the sign bit for a 32-bit signed integer is set, we OR it in
    If (0 <> (x And &H80000000L)) Then
        value = value Or &H80000000I
    End If

    Return value
End Function

Here's an ever simpler rendition — just bit-twiddling. No no comparisions at all. We just grab 2 16-bit nibbles and assemble them into the final value:

Private Function CastLongToInt(x As Long) As Integer
    Dim hiNibble As Integer = &HFFFFL And (x >> 16) 
    Dim loNibble As Integer = &HFFFFL And x
    Dim value As Integer = (hiNibble << 16) Or loNibble
    Return value
End Function

Edited To Note:

Another option would be to use a nullable integer (System.Nullable<int>). In VB.Net, it would be something like this:

Private Function TryCastLongToInt(x As Long) As Integer?
    Dim value As Integer?
    Try
        value = CType(x, Integer)
    Catch
      ' This exception intentionall swallowed here
    End Try
    Return value
End Function

Or, if the notion of intentionally catching and swallowing exceptions bugs you:

Private Function TryCastLongToInt(x As Long) As Integer?
    If (x < Integer.MinValue) Then Return Nothing
    If (x > Integer.MaxValue) Then Return Nothing
    Return x
End Function

Either way, the return value will be either an integer value or Nothing if the 64-bit value was outside the domain of a 32- bit integer.

0
On

Great answers so far, here's another one similar to Steven Doggart's:

Dim x As Long = Int32.MaxValue
x = x + 100
Console.WriteLine(x)

Dim y As Integer
If x > Int32.MaxValue Then y = Int32.MaxValue Else y = x
Console.WriteLine(y)

Results:

2147483747
2147483647

Or:

Dim x As Long = Long.MaxValue
Console.WriteLine(x)

Dim y As Integer
If x > Int32.MaxValue Then y = Int32.MaxValue Else y = x
Console.WriteLine(y)

Results:

9223372036854775807
2147483647

Wrap it in a function:

Function DownsizeLongToInt(ByVal x As Long) As Integer
    Dim y As Integer
    If x > Int32.MaxValue Then y = Int32.MaxValue Else y = x
    Return y
End Function

Note the function name. I've named it that way to indicate that this is going to cause truncation/data loss and shouldn't be used for "converting" a Long to Int, which may be lossy, and which may also be the reason why the various convert functions do not attempt to do this. Just a guess.