Is there a way to force circular integer overflow in Excel VBA? - vba

I'm trying to convert some Java code to Excel and the required hashcode function generates an overflow error, instead of wrapping to the negative
Function FnGetStringHashCode(ByVal str As String) As Integer
Dim result, i
FnGetStringHashCode = 17
For i = 1 To Len(str)
Dim c, a
c = Mid(str, i, 1)
a = AscW(c)
FnGetStringHashCode = 31 * FnGetStringHashCode + a
Next i
End Function
Is there a way of doing this in Excel VBA?

Although there is no built-in way to do this, the computation is simple:
Public Function coerceLongToInt(toCoerce As Long) As Integer
Const MIN_INT As Long = -32768
Const MAX_INT As Long = 32767
Const NUM_INTS As Long = MAX_INT - MIN_INT + 1
Dim remainder As Long
remainder = toCoerce Mod NUM_INTS
If remainder > MAX_INT Then
coerceLongToInt = remainder - NUM_INTS
ElseIf remainder < MIN_INT Then
coerceLongToInt = remainder + NUM_INTS
Else
coerceLongToInt = remainder
End If
End Function
This is the behavior you want, right?
?coerceLongToInt(-32769)
32767
?coerceLongToInt(-32768)
-32768
?coerceLongToInt(-1)
-1
?coerceLongToInt(0)
0
?coerceLongToInt(1)
1
?coerceLongToInt(32767)
32767
?coerceLongToInt(32768)
-32768
You would use it like this:
Function FnGetStringHashCode(ByVal str As String) As Integer
Dim result, i
FnGetStringHashCode = 17
For i = 1 To Len(str)
Dim c, a
c = Mid(str, i, 1)
a = AscW(c)
FnGetStringHashCode = coerceLongToInt(31 * CLng(FnGetStringHashCode) + a)
Next i
End Function
You need the 'CLng' call in there to prevent VBA from raising an overflow error when it computes the intermediate value (31 * [some integer >= 1058]).

I have modified a little the script of ours. The main difference is returning type of your function. Now it returns Variant. As decimal is a subset of Variant, and it can store bigger numbers than long I think it is a good solution (see VBA data types) - I do not know is it possible to explicitly return Decimal. Here is the script
Function FnGetStringHashCode(ByVal str As String) As Variant
Dim tmp As Variant, c As String, a As Integer, i As Integer
tmp = 17
For i = 1 To Len(str)
c = Mid$(str, i, 1)
a = AscW(c)
tmp = 31 * tmp + a
Next i
FnGetStringHashCode = tmp
End Function
And a little test routine
Sub test()
Debug.Print CStr(FnGetStringHashCode("dawdaedae"))
End Sub

Related

Reversing Digits

I'm trying to make a function that takes a three digit number and reverses it (543 into 345)
I can't take that value from a TextBox because I need it to use the three numbers trick to find a value.
RVal = ReverseDigits(Val)
Diff = Val - RVal
RDiff = ReverseDigits(Diff)
OVal = Diff + RDiff
543-345=198
198+891=1089
Then it puts 1089 in a TextBox
Function ReverseDigits(ByVal Value As Integer) As Integer
' Take input as abc
' Output is (c * 100 + b * 10 + a) = cba
Dim ReturnValue As Boolean = True
Dim Val As String = CStr(InputTextBox.Text)
Dim a As Char = Val(0)
Dim b As Char = Val(1)
Dim c As Char = Val(2)
Value = (c * 100) + (b * 10) + (a)
Return ReturnValue
End Function
I've tried this but can't figure out why it won't work.
You can convert the integer to a string, reverse the string, then convert back to an integer. You may want to enforce the three digit requirement. You can validate the argument before attempting conversion
Public Function ReverseDigits(value As Integer) As Integer
If Not (value > 99 AndAlso value < 1000) Then Throw New ArgumentException("value")
Return Integer.Parse(New String(value.ToString().Reverse().ToArray()))
End Function
My code is pretty simple and will also work for numbers that don't have three digits assuming you remove that validation. To see what's wrong with your code, there are a couple of things. See the commented lines which I changed. The main issue is using Val as a variable name, then trying to index the string like Val(0). Val is a built in function to vb.net and the compiler may interpret Val(0) as a function instead of indexing a string.
Function ReverseDigits(ByVal Value As Integer) As Integer
' Dim ReturnValue As Boolean = True
' Dim Val As String = CStr(InputTextBox.Text)
Dim s As String = CStr(Value)
Dim a As Char = s(0)
Dim b As Char = s(1)
Dim c As Char = s(2)
Value = Val(c) * 100 + Val(b) * 10 + Val(a)
'Return ReturnValue
Return Value
End Function
(Or the reduced version of your function, but I would still not hard-code the indices because it's limiting your function from expanding to more or less than 3 digits)
Public Function ReverseDigits(Value As Integer) As Integer
Dim s = CStr(Value)
Return 100 * Val(s(2)) + 10 * Val(s(1)) + Val(s(0))
End Function
And you could call the function like this
Dim inputString = InputTextBox.Text
Dim inputNumber = Integer.Parse(inputString)
Dim reversedNumber = ReverseDigits(inputNumber)
Bonus: If you really want to use use math to find the reversed number, here is a version which works for any number of digits
Public Function ReverseDigits(value As Integer) As Integer
Dim s = CStr(value)
Dim result As Integer
For i = 0 To Len(s) - 1
result += CInt(Val(s(i)) * (10 ^ i))
Next
Return result
End Function
Here's a method I wrote recently when someone else posted basically the same question elsewhere, probably doing the same homework:
Private Function ReverseNumber(input As Integer) As Integer
Dim output = 0
Do Until input = 0
output = output * 10 + input Mod 10
input = input \ 10
Loop
Return output
End Function
That will work on a number of any length.

How to represent a number in the form of A*10-^n?

I recently wrote this program in Visul Basic 13.
it searchs for the nth catalan number but after 48 even Decimal type is too short.
Is there any other way to represent them? like in the form of A*10^n?
Public Class Try_Catalan_Number
'Catalan numbers form a sequence of natural numbers that occur in various counting problems,
'often involving recursively defined objects.
Inherits Base_Number
Public Overrides Sub Test()
Dim Return_Catalan_Value As Decimal
If Function_Catalan(Return_Catalan_Value) = False Then
Return_To_Form_Boolean = False
Else
Return_To_Form_Boolean = True
End If
Return_To_Form_Value = Function_Catalan(Return_Catalan_Value)
End Sub
Private Function Function_Catalan(Return_Catalan_Value As Decimal) As Decimal
'We return a Decimal function because catalan numbers can be very big and decimal is the biggest type.
Dim Binomial_Cofficients As Decimal
Dim Result As Decimal
Dim Number_Of_Loops As Integer
Dim tmpNumber As Object
Dim K As Decimal
Dim N As Decimal
If (Number > 48) Then
Return False
Exit Function
End If
'48 is the largest catalan number position which can be displayed...any position above 48 is too big.
tmpNumber = Number - 1
N = 2 * tmpNumber
K = tmpNumber
Result = 1
For Number_Of_Loops = 1 To K
Result = Result * (N - (K - Number_Of_Loops))
Result = Result / Number_Of_Loops
Next Number_Of_Loops
Binomial_Cofficients = Result
tmpNumber = Number - 1
tmpNumber = ((1 / (1 + tmpNumber)) * Binomial_Cofficients)
Return_Catalan_Value = tmpNumber
Return Return_Catalan_Value
End Function
End Class
[I assume by "Visul Basic 13" you mean the VB which is associated with Visual Studio 2013, i.e. VB version 12.0.]
You can use System.Numerics.BigInteger (you'll have to add a reference to System.Numerics):
Imports System.Numerics
Module Module1
Friend Function Factorial(n As Integer) As BigInteger
If n < 2 Then Return 1
If n = 2 Then Return 2
Dim f As BigInteger = BigInteger.Parse("2")
For i = 3 To n
f *= i
Next
Return f
End Function
Friend Function CatalanNumber(n As Integer) As BigInteger
Return Factorial(2 * n) / (Factorial(n + 1) * Factorial(n))
End Function
Sub Main()
For i = 0 To 550
Console.WriteLine(CatalanNumber(i).ToString())
Next
Console.ReadLine()
End Sub
End Module
I did not test to see the maximum Catalan number it can calculate, and I have no inclination to verify the results beyond those shown on the Wikipedia page.
Optimisations are left as an exercise for the reader ;)
Edit: FWIW, I can get it to run a bit faster by using
Function CatalanNumber(n As Integer) As BigInteger
Dim nFactorial = Factorial(n)
Dim twonFactorial = nFactorial
For i = (n + 1) To 2 * n
twonFactorial = BigInteger.Multiply(twonFactorial, i)
Next
Return twonFactorial / (BigInteger.Pow(nFactorial, 2) * (n + 1))
End Function
The speed increase varies from roughly 50% (n=50) to 20% (n=5000). If you're only using the function a few times for fairly small n, there may be little point worrying about it.
Edit2 Re-writing your function a bit to make it easier to read and removing the off-by-one error, we get:
Private Function Function_Catalan(a As Integer) As BigInteger
If a = 0 Then Return 1
Dim binomialCofficient As BigInteger = BigInteger.One
Dim n As Integer = 2 * a
Dim k As Integer = a - 1
For i As Integer = 1 To k
binomialCofficient = binomialCofficient * (n - (k - i)) / i
Next i
Return binomialCofficient / a
End Function
to get this format you could use:
String.Format("{0:E4}", InputNumber)

Decimal to binary for large number (>2253483438943167)

I have a VB.Net application that goes through a series of processes to decode a string, one problem with this is that I have found a function that converts from binary to decimal for any number, but I cannot find a function that will convert a supplied number (in string format) into a binary string. For reference, the binary to decimal conversion function is below:
Public Function baseconv(d As String)
Dim N As Long
Dim Res As Long
For N = Len(d) To 1 Step -1
Res = Res + ((2 ^ (Len(d) - N)) * CLng(Mid(d, N, 1)))
Next N
Return Str(Res)
End Function
What i would do if the number is less than 16*1e18 <-> can hold in a Uint64 :
1) store the number inside an unsigned 64 bits integer : num.
2) then just loop on each bit to test it :
mask = 1<<63
do
if ( num AND mask ) then ->> push("1")
else ->> push("0")
mask = mask >> 1
until mask = 0
(where push builds the output with a string concatenation, or, if performance matters, a StringBuilder, or it can be a stream,... )
what about this?
Module Module1
Sub Main()
Console.WriteLine(Convert.ToString(2253483438943167 * 5, 2))
Console.ReadKey()
End Sub
End Module
Try using the System.Numerics.BigInteger class like this:
Dim d As String
d = "2253483438943167"
Dim bi As BigInteger
bi = BigInteger.Parse(d)
Dim ba() As Byte
ba = bi.ToByteArray()
Dim s As String
Dim N As Long
Dim pad as Char
pad = "0"c
For N = Len(ba) To 1 Step -1
s = s + Convert.ToString(ba(N)).PadLeft(8, pad)
Next N
How about
Dim foo As BigInteger = Long.MaxValue
foo += Long.MaxValue
foo += 2
Dim s As New System.Text.StringBuilder
For Each b As Byte In foo.ToByteArray.Reverse
s.Append(Convert.ToString(b, 2).PadLeft(8, "0"c))
Next
Debug.WriteLine(s.ToString.TrimStart("0"c))
'10000000000000000000000000000000000000000000000000000000000000000

How can I list all the combinations that meet certain criteria using Excel VBA?

Which are the combinations that the sum of each digit is equal to 8 or less, from 1 to 88,888,888?
For example,
70000001 = 7+0+0+0+0+0+0+1 = 8 Should be on the list
00000021 = 0+0+0+0+0+0+2+1 = 3 Should be on the list.
20005002 = 2+0+0+0+5+0+0+2 = 9 Should not be on the list.
Sub Comb()
Dim r As Integer 'Row (to store the number)
Dim i As Integer 'Range
r = 1
For i = 0 To 88888888
If i = 8
'How can I get the sum of the digits on vba?
ActiveSheet.Cells(r, 1) = i
r = r + 1
End If
Else
End Sub
... Is this what you're looking for?
Function AddDigits(sNum As String) As Integer
Dim i As Integer
AddDigits = 0
For i = 1 To Len(sNum)
AddDigits = AddDigits + CInt(Mid(sNum, i, 1))
Next i
End Function
(Just remember to use CStr() on the number you pass into the function.
If not, can you explain what it is you want in a bit more detail.
Hope this helps
The method you suggest is pretty much brute force. On my machine, it ran 6.5min to calculate all numbers. so far a challenge I tried to find a more efficient algorithm.
This one takes about 0.5s:
Private Const cIntNumberOfDigits As Integer = 9
Private mStrNum As String
Private mRng As Range
Private Sub GetNumbers()
Dim dblStart As Double
Set mRng = Range("a1")
dblStart = Timer
mStrNum = Replace(Space(cIntNumberOfDigits), " ", "0")
subGetNumbers 8
Debug.Print (Timer - dblStart) / 10000000, (Timer - dblStart)
End Sub
Private Sub subGetNumbers(intMaxSum As Integer, Optional intStartPos As Integer = 1)
Dim i As Integer
If intStartPos = cIntNumberOfDigits Then
Mid(mStrNum, intStartPos, 1) = intMaxSum
mRng.Value = Val(mStrNum)
Set mRng = mRng.Offset(1)
Mid(mStrNum, intStartPos, 1) = 0
Exit Sub
End If
For i = 0 To intMaxSum
Mid(mStrNum, intStartPos, 1) = CStr(i)
subGetNumbers intMaxSum - i, intStartPos + 1
Next i
Mid(mStrNum, intStartPos, 1) = 0
End Sub
It can be sped up further by about factor 10 by using arrays instead of writing directly to the range and offsetting it, but that should suffice for now! :-)
As an alternative, You can use a function like this:
Function isInnerLowr8(x As Long) As Boolean
Dim strX As String, inSum As Long
isInnerLowr8 = False
strX = Replace(CStr(x), "0", "")
For i = 1 To Len(strX)
Sum = Sum + Val(Mid(strX, i, 1))
If Sum > 8 Then Exit Function
Next i
isInnerLowr8 = True
End Function
Now change If i = 8 to If isInnerLowr8(i) Then.

VB .net - shortest and fastest way to find Nth occurrence of char in string?

What is the professional way to achieve this?
Thanks.
I've shamelessly ripped off the example from this question and converted it from C# to VB.net.
Public Function GetNthIndex(s As String, t As Char, n As Integer) As Integer
Dim count As Integer = 0
For i As Integer = 0 To s.Length - 1
If s(i) = t Then
count += 1
If count = n Then
Return i
End If
End If
Next
Return -1
End Function
Here's a way to do it with Linq.
Public Function GetNthIndex(searchString As String, charToFind As Char, n As Integer) As Integer
Dim charIndexPair = searchString.Select(Function(c,i) new with {.Character = c, .Index = i}) _
.Where(Function(x) x.Character = charToFind) _
.ElementAtOrDefault(n-1)
Return If(charIndexPair IsNot Nothing, charIndexPair.Index, -1)
End Function
Usage:
Dim searchString As String = "Assessment"
Dim index As Integer = GetNthIndex(searchString, "s", 4) 'Returns 5
My Version of Andew's but I believe this takes into account if the first character is the character you are looking for
Public Function GetNthIndexStringFunc(s As String, t As String, n As Integer) As Integer
Dim newFound As Integer = -1
For i As Integer = 1 To n
newFound = s.IndexOf(t, newFound + 1)
If newFound = -1 Then
Return newFound
End If
Next
Return newFound
End Function
If you're going for faster:
Public Function NthIndexOf(s As String, c As Char, n As Integer) As Integer
Dim i As Integer = -1
Dim count As Integer = 0
While count < n AndAlso i >= 0
i = s.IndexOf(c, i + 1)
count += 1
End While
Return i
End Function
Although it is slightly slower than Mike C's answer if you're looking for the nth "a" in a long string of "a"s (for example).
Edit: adjusted following spacemonkeys' comment.