Using a VB6 app on Windows 7 both lines return TRUE, because somehow the decimal separator is not considered:
IsNumeric("123.45")
IsNumeric("123,45")
On Windows 8 or Windows 2012, the same code returns TRUE or FALSE depending on the regional settings. Considering the comma as decimal separator defined in regional settings, then:
IsNumeric("123.45") returns FALSE
IsNumeric("123,45") returns TRUE
Is there any way to restore the "old" behaviour without recompiling the app?
This is not a new issue with which version of Windows you are using. It's always been based on the locale settings of the machine.
What I did for my application is I made my own functions:
Public Function IsNumber(ByRef Expression As Variant) As Boolean
Select Case VarType(Expression)
Case vbInteger, vbLong, vbSingle, vbDouble, vbCurrency, vbDate, vbBoolean, vbDecimal, vbByte
IsNumber = True
Case vbString
Dim Negative As Boolean
Dim Number As Boolean
Dim Period As Boolean
Dim Positive As Boolean
Dim X As Long
For X = 1& To Len(Expression)
Select Case Mid$(Expression, X, 1&)
Case "0" To "9"
Number = True
Case "-"
If Period Or Number Or Negative Or Positive Then Exit Function
Negative = True
Case "."
If Period Or Exponent Then Exit Function
Period = True
Case "E", "e"
If Not Number Then Exit Function
If Exponent Then Exit Function
Exponent = True
Number = False
Negative = False
Period = False
Case "+"
If Not Exponent Then Exit Function
If Number Or Negative Or Positive Then Exit Function
Positive = True
Case vbSpace, vbTab, vbVerticalTab, vbCr, vbLf, vbFormFeed
If Period Or Number Or Exponent Or Negative Then Exit Function
Case Else
Exit Function
End Select
Next X
IsNumber = Number
End Select
End Function
If you're dealing with strings explicitly, then you could simplify that function. And also you likely don't want to deal with exponents, so this might be more suited:
Public Function IsNumber(ByRef Expression As String) As Boolean
Dim Negative As Boolean
Dim Number As Boolean
Dim Period As Boolean
Dim X As Long
For X = 1& To Len(Expression)
Select Case Mid$(Expression, X, 1&)
Case "0" To "9"
Number = True
Case "-"
If Period Or Number Or Negative Then Exit Function
Negative = True
Case "."
If Period Then Exit Function
Period = True
Case vbSpace, vbTab, vbVerticalTab, vbCr, vbLf, vbFormFeed
If Period Or Number Or Negative Then Exit Function
Case Else
Exit Function
End Select
Next X
IsNumber = Number
End Function
When you are in need of converting the number, use Str() instead of CLng()/CInt()/CDbl()/CSng()/Val(). Str() treats periods as decimals regardless of locale, just like my IsNumber() function above.
Related
I am trying to grab a cell check if it has decimal places and remove them then place a specific format in a cell depending on how many characters there are in the number, the len function returns null, and the instr function works but when passed to a variable returns null. Thank you to anyone who can help. At the end of the first if function I print the results of the 3 variables not working to the immediate window to verify, with the Debug.Print command please go to view menu and activate immediate window to watch.
Function cnvtDta()
ActiveSheet.Select
Data1 = Range("data").Value
Dim rslt As String
rslt = Data1
Set myrng = Range("data")
Dim wot, sowot
'Find decimal place in cell
dot = myrng.Find(".", myrng)
If dot = True Then
'if decimal place strip remainders and decimal point
Dim pos, res
pos = InStr(1, rslt, ".")
res = Left(rslt, pos)
sowot = Len(res)
End If
Debug.Print res
Debug.Print sowot
Debug.Print pos
'Return specific formats to cell
'thank you kindly to anyone who can spare the time to genuinely help
End Function
So basically there's a couple of parts to your question.
Check if value has decimals. Here's one way to do it (based on values, not on strings)
Function DoesCellContainDecimals(inputRange As Range) As Boolean
Dim tolerance As Double
tolerance = 0.0001
If Not IsNumeric(inputRange.Value2) Then
'invalid argument
DoesCellContainDecimals = False
Exit Function
End If
If (Abs(Fix(inputRange.Value2) - inputRange.Value2) < tolerance) Then
'value does not have meaningful decimals
DoesCellContainDecimals = False
Else
'value has meaningful decimals
DoesCellContainDecimals = True
End If
End Function
Get the integer part of a number. There are two functions. Similar but different behavior with negative numbers (make sure if the value is a number first):
Int(6.5) '6
Fix(6.5) '6
Int(-6.5) '-7
Fix(-6.5) '-6
Format a number. Either turn it to string or set Range.NumberFormat property:
Format(6500000,"# ### ###") '6 500 000
Range("A1").NumberFormat = "# ### ##0" 'same effect as above but only when displaying in that cell.
I have this formula (below) where I am trying to find a space in C1. Instead of this, I would like to update this formula to look for anything except for "C" or any number and not only find a space.
LEFT(C1, find("" "", C1, 1)-1)
For e.g.
if C1 has - "C1234 - XXX" or "C1234-XXX" or "C1234:XXX", I always want the above function to find anything except for "C" and "1234" (i.e. numbers).
P.S.: I would want to use the find function only with improvements to meet the above conditions.
Please suggest.
Perhaps this:
'To create a new string from a source string which will or will not contain the characters present within the source string
'Examples of string of characters: 0123456789 -OR- {}[]<>\/|+*=-_(),.:;?!##$%^&™®©~'" OR - combination of various characters
Public Function getNewStringFromString(ByVal strSource As Variant, ByVal strChars As Variant, Optional isInString As Boolean = True) As String
Dim strArr As Variant, iChar As Variant
getNewStringFromString = ""
If VarType(strSource) = vbString And VarType(strChars) = vbString Then
strSource = Trim(strSource): strChars = Trim(strChars)
If Len(strSource) > 0 And Len(strChars) > 0 Then
strArr = Split(StrConv(strSource, vbUnicode), vbNullChar)
For Each iChar In strArr
If (isInString Xor isInArray(iChar, strChars)) = False Then getNewStringFromString = getNewStringFromString + iChar
Next iChar
Erase strArr
End If
End If
End Function
Use as the following:
MsgBox getNewStringFromString(CStr(Range("C1")), "C0123456789")
Forgot to give you the code for the isInArray function. Here it is:
'To check if an element is within a specific Array, Object, Range, String, etc.
Public Function isInArray(ByVal itemSearched As Variant, ByVal aArray As Variant) As Boolean
Dim item As Variant
If VarType(aArray) >= vbArray Or VarType(aArray) = vbObject Or VarType(aArray) = vbDataObject Or TypeName(aArray) = "Range" Then
For Each item In aArray
If itemSearched = item Then
isInArray = True
Exit Function
End If
Next item
isInArray = False
ElseIf VarType(aArray) = vbString Then
isInArray = InStr(1, aArray, itemSearched, vbBinaryCompare) > 0 'Comparing character by character
Else
On Error Resume Next
isInArray = Not IsError(Application.Match(itemSearched, aArray, False))
Err.Clear: On Error GoTo 0
End If
End Function
Given your data format, where
C is always the first character
subsequent values are all digits
You want to return the C followed by the digits
Try:
="C" & LOOKUP(9E+307,VALUE(MID(A1,2,{1,2,3,4,5,6,7})))
If there might be more than 7 digits, you can either extend the array constant, or use a formula to create a larger array.
The formula looks for the largest integer in the string, starting with position 2. So it will stop at the last non-digit, since anything including a non-digit will return an error.
If the "non-digit" might be your decimal or thousands separator, you will need to replace it with something else, with a nested SUBSTITUTE
Replace . , and space with -
=LOOKUP(1E+307,--SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(MID(A1,2,{1,2,3,4,5,6,7}),",","-"),".","-"),".","-"))
For a VBA solution, I would use regular expressions.
Option Explicit
Function getCnum(str As String)
Dim RE As Object
Const sPat As String = "(^C\d+).*"
Set RE = CreateObject("vbscript.regexp")
With RE
.Global = False
.MultiLine = True
.ignorecase = True
.Pattern = sPat
getCnum = .Replace(str, "$1")
End With
End Function
Note that this also validates the string by checking that the first letter is, in fact, a C (or c). If you want it to be case-sensitive, make the obvious change.
how can i check if a string only contains 4 digit numbers ( or a year )
i tried this
Dim rgx As New Regex("^/d{4}")
Dim number As String = "0000"
Console.WriteLine(rgx.IsMatch(number)) // true
number = "000a"
Console.WriteLine(rgx.IsMatch(number)) // false
number = "000"
Console.WriteLine(rgx.IsMatch(number)) //false
number = "00000"
Console.WriteLine(rgx.IsMatch(number)) // true <<< :(
this returns false when its less than 4 or at characters but not at more than 4
thanks!
I actually wouldn't use a regex for this. The expression is deceptively simple (^\d{4}$), until you realize that you also need to evaluate that numeric value to determine a valid year range... unless you want years like 0013 or 9015. You're most likely going to want the value as an integer in the end, anyway. Given that, the best validation is probably just to actually try to convert it to an integer right off the bat:
Dim numbers() As String = {"0000", "000a", "000", "00000"}
For Each number As String In numbers
Dim n As Integer
If Integer.TryParse(number, n) AndAlso number.Length = 4 Then
'It's a number. Now look at other criteria
End If
Next
Use LINQ to check if All characters IsDigit:
Dim result As Boolean = ((Not number Is Nothing) AndAlso ((number.Length = 4) AndAlso number.All(Function(c) Char.IsDigit(c))))
You should use the .NET string manipulation functions.
Firstly the requirements, the string must:
Contain exactly four characters, no more, no less;
Must consist of a numeric value
However your aim is to validate a Date:
Function isKnownGoodDate(ByVal input As String) As Boolean 'Define the function and its return value.
Try 'Try..Catch statement (error handling). Means an input with a space (for example ` ` won't cause a crash)
If (IsNumeric(input)) Then 'Checks if the input is a number
If (input.Length = 4) Then
Dim MyDate As String = "#01/01/" + input + "#"
If (IsDate(MyDate)) Then
Return True
End If
End If
End If
Catch
Return False
End Try
End Function
You may experience a warning:
Function isKnownGoodDate does not return a value on all code
paths. Are you missing a Return statement?
this can be safely ignored.
I am taking a VBA class and am completely stuck on this problem. We CANNOT use the masked text box, which would solve this problem. Instead the professor actually wants me to learn the code, can you imagine?
All kidding aside, the user needs to enter a gas price into a text box, then hit calculate to receive the total cost of the trip. There is much more to the interface but will spare you the details. If a user enters anything else number than a positive number with one decimal place, it should return an error. I have figured out 0 or 0000 as well as a negative number such as -3.45. Now I have to get any text or special characters to give me an error as well as something like 34.56.12.45. You never know, a user may feel the need to type in their IP address. The key to the assignment is that I catch all probable user errors.
Here is what I've written for the calculation as well as catch the errors. I have tried the Try/Catch statements as well. Nothing worked but I got the first two parts of the IF statement to work yet always failing on the last IF part until it gets to the calculation.
Private Sub btnCalc_Click(sender As Object, e As EventArgs) Handles btnCalc.Click
Dim Mileage As Decimal
Dim Miles As Decimal
Dim GasPrice As Decimal
Dim Cost As Decimal
If CDec(txtbxGasPrice.Text) = 0 Then
MessageBox.Show("Please enter a positive dollar amount")
txtbxGasPrice.Text = String.Empty
End If
If CDec(txtbxGasPrice.Text) < 0 Then
MessageBox.Show("Please enter a positive dollar amount")
txtbxGasPrice.Text = String.Empty
End If
If Cost = CDec((Miles / Mileage) * GasPrice) Then
Miles = CDec(lblTMiles.Text)
Mileage = CDec(lblMileage.Text)
GasPrice = CDec(txtbxGasPrice.Text)
lblTotalCost.Text = Cost.ToString("C2")
End If
If CBool(txtbxGasPrice.Text = "") Then
MsgBox("You must enter a dollar amount")
End If
*If Not IsNumeric(txtbxGasPrice.Text) Then
MessageBox.Show("Please enter a positive dollar amount")
txtbxGasPrice.Text = String.Empty*
End If
End Sub
'I have placed this at the top, in the middle, at the bottom but no luck. What am I missing?
Appreciate your thoughts - Lauren
This one seems to meet your criteria and pass David's tests:
Function IsValid(txt As String) As Boolean
If Not IsNumeric(txt) Then
Exit Function
End If
If Len(txt) < 2 Then
Exit Function
End If
If Not Mid(txt, Len(txt) - 1, 1) = "." Then
Exit Function
End If
If Not txt > 0 Then
Exit Function
End If
IsValid = True
End Function
This seems like a perfect application of regular expressions, but could be out of scope for this problem, maybe even better though vb has a Decimal.TryParse(or parse) that will take a string and try to parse it to a decimal.
http://msdn.microsoft.com/en-us/library/system.decimal.tryparse.aspx
on a side not I'm not 100% sure how it acts with xx.xx.xx but I'm betting it will fail and help your problem
I'm going to expand on CSgoose's idea to use RegEx, it seems a lot more reliable. I am very, very green when it comes to using RegEx but I try to work on Q's here at SO, so this may not be the optimal pattern to match, but a function like this seems to do the trick when I test a few value.
0/0.0/0.00 = False
-1.5 = False
1.5 = True
5.45 = False
Steve = False
Steve.6 = False
Steve6.58 = False
6.574.2 = False
A value that evaluates to 0 will return false. Negative values return false. Value must have a decimal component, be comprised of any # of digits (this can be tweaked if you want to limit it, eg., to ##.# format, etc.). Matches full text only, so things like IP addresses won't return true, etc.
NOTE This is VBA, but should be easily adaptable for your purposes)
Sub YourSub()
If Not IsMatch(CStr(txtbxGasPrice.Text)) Then
MsgBox "Please ensure that the value you enter is a positive dollar amount, to 1 decimal place!", vbCritical, "Invalid Gas Price Value!"
End IF
End Sub
This function requires enabling reference to Microsoft VBScript Regular Expressions 5.5, or you could use late-binding.
Function IsMatch(str As String) As Boolean
'Tests for a positive numeric value, formatted 0.0 with a mandatory decimal component
' exact match only
Dim re As RegExp
Dim allMatches As MatchCollection
Dim retVal As Boolean
retVal = False 'by default
If Not IsNumeric(str) Then GoTo EarlyExit 'ignore any non-numeric value
Set re = New RegExp
re.Pattern = "\d*\.[0-9]"
Set allMatches = re.Execute(str)
If allMatches.Count = 1 Then
'If there are multiple matches, then I think safe to say it's not a match,
' make sure it's a full string match
If str > 0 Then
retVal = (allMatches(0) = str)
End If
End If
EarlyExit:
Set re = Nothing
IsMatch = retVal
End Function
Update to force, for example, ##.# format, you could do
re.Pattern = "[1-9]?\d\.\d"
I have the following code to check values entered into two input boxes, if both values are zero then the MsgBox should display "Stop!" (I will change this later to exiting the sub but I am using a MsgBox for testing)
From testing I've seen these results:
A zero in both strings produces the expected message box.
A non zero in the first string followed by any non zero value in the second string does nothing (as expected).
A zero in the first string followed by a second string value equal to or greater than 10 produces the message box (unexpected).
I've also noticed that if the second string is 6-9 it is displayed as x.00000000000001%. I think this is a floating point issue and could be related? This behaviour occurs without the IF... InStr function too.
Option Explicit
Sub Models()
Dim MinPer As String, MaxPer As String, Frmula As String
Dim Data As Worksheet, Results As Worksheet
Set Data = Sheets("Data")
Set Results = Sheets("Results")
Application.ScreenUpdating = False
MinPer = 1 - InputBox("Enter Minimum Threshold Percentage, do not include the % symbol", _
"Minimum?") / 100
MaxPer = 1 + InputBox("Enter Maximum Threshold Percentage, do not include the % symbol", _
"Maximum?") / 100
If (InStr(MinPer, "0") = 0) And (InStr(MaxPer, "0") = 0) Then
MsgBox "STOP!"
End If
' Remainder of code...
This is the most interesting problem I've come across so far in VBA and welcome any discussion about it.
Edit: I use this code to display on screen the paramaters for the end-user to see. Hence how I noticed the .00000000001% issue:
.Range("D2").Value = "Min is " & 100 - MinPer * 100 & "%"
.Range("D3").Value = "Max is " & MaxPer * 100 - 100 & "%"
Two things
1) Declare MinPer, MaxPer as Long or a Double and not a String as you are storing outputs from a calculation
2) Don't directly use the InputBox in the calculations. Store them in a variable and then if the input is valid then use them in the calculation
Dim MinPer As Double, MaxPer As Double, Frmula As String
Dim Data As Worksheet, Results As Worksheet
Dim n1 As Long, n2 As Long
Set Data = Sheets("Data")
Set Results = Sheets("Results")
Application.ScreenUpdating = False
On Error Resume Next
n1 = Application.InputBox(Prompt:="Enter Minimum Threshold Percentage, do not include the % symbol", _
Title:="Minimum?", Type:=1)
On Error GoTo 0
If n1 = False Then
MsgBox "User cancelled"
Exit Sub
End If
On Error Resume Next
n2 = Application.InputBox(Prompt:="Enter Maximum Threshold Percentage, do not include the % symbol", _
Title:="Maximum?", Type:=1)
On Error GoTo 0
If n2 = False Then
MsgBox "User cancelled"
Exit Sub
End If
If n1 = 0 And n2 = 0 Then
MsgBox "STOP!"
End If
MinPer = 1 - (Val(n1) / 100)
MaxPer = 1 + (Val(n2) / 100)
This is because the number "10" has a "0" in the string (second character) so both evaluate to true.
Try this instead:
If (MinPer = "0") And (MaxPer = "0") Then
MsgBox "STOP!"
End If
For additional control save the user input (MinPer , MaxPer) and THEN text them for validity before performing nay mathematical operations on them.
InStr(MinPer, "0") is just checking to see whether the string contains a zero
character.
You need to convert the string value to an integer. Use the IsNumeric and CInt functions
to do that. See this URL:
vba convert string to int if string is a number
Dim minPerINT as Integer
Dim maxPerINT as Integer
If IsNumeric(minPer) Then
minPerINT = CInt(minPer)
Else
minPerINT = 0
End If
If IsNumeric(maxPer) Then
maxPerINT = CInt(maxPer)
Else
maxPerINT = 0
End If
If minPerINT = 0 and maxPerINT=0 Then
MsgBox "STOP!"
End If
Depending on what data can be entered It may also be a good idea to check if the length
of the data is zero using the len() function.