Page 4 of 4
Re: IsNumber
Posted: Mon Mar 22, 2010 9:01 pm
by Trond
I tried to mimic PB and Delphi. I think it's cleaner design to trim the spaces before checking if a string is numeric.
Here's with negative numbers (and negative exponents):
Code: Select all
Structure SParseNumber
Input.s
Look.c
EndStructure
Procedure GetChar(*S.SParseNumber)
*S\Look = Asc(*S\Input)
*S\Input = Mid(*S\Input, 2)
EndProcedure
Procedure GetInteger(*S.SParseNumber)
Protected Result = 0
While *S\Look >= '0' And *S\Look <= '9'
Result = 1
GetChar(*S)
Wend
ProcedureReturn Result
EndProcedure
Procedure GetSign(*S.SParseNumber)
If *S\Look = '-'
GetChar(*S)
EndIf
EndProcedure
Procedure GetFloat(*S.SParseNumber)
; Returns:
; 0 = syntax error
; 1 = valid integer
; 2 = valid floating point
GetSign(*S)
IsInt = GetInteger(*S)
If IsInt And *S\Look = '.' ; decimal point
GetChar(*S)
IsInt = GetInteger(*S)
If IsInt = 0
ProcedureReturn 0 ; syntax error
EndIf
ProcedureReturn 2
EndIf
ProcedureReturn IsInt
EndProcedure
Procedure GetNumberType(S.s)
; PureBasic Forum: Trond
; Returns:
; 0 = syntax error
; 1 = valid integer
; 2 = valid floating point, but not with exponent
; 3 = valid number in exponent format
N.SParseNumber
N\Input = S
GetChar(@N)
IsFloat = GetFloat(@N)
If IsFloat And N\Look = 'e'
GetChar(@N)
GetSign(@N)
IsInt = GetInteger(@N)
If IsInt And N\Look = 0
ProcedureReturn 3
Else
ProcedureReturn 0
EndIf
EndIf
If N\Look = 0
ProcedureReturn IsFloat
Else
ProcedureReturn 0
EndIf
EndProcedure
Re: IsNumber
Posted: Tue Mar 23, 2010 6:59 am
by idle
skywalk wrote:Hi,
@Trond and Idle...
Not sure if your versions are complete?
They reject several examples of exponential numbers...
EDIT: By the way, Set your Debug window font to a fixed pitch font(courier new

for easy viewing.
I just found out this is possible!
Thanks,
Steve
yes it was probably because I had only allocated the memory for 16 bytes and using 18 bytes
Re: IsNumber
Posted: Tue Mar 23, 2010 7:48 am
by Demivec
Here's a rewrite of Xombie's version, same functionality and results but with some simplifications to make it easier to follow (for me mostly). I thought I would share it in case anyone else might find it useful.
Code: Select all
;Original version: Xombie (11/29/2006), rewrite by Demivec
Procedure IsNumber(InString.s, DecimalCharacter.c = '.', ThousandsSeparator.c = ',')
;Returns 0 for non-numeric values, 1 for positive integers, -1 for negative integers,
;2 for positive floats and -2 for negative floats.
#NotNumber = 0
#IsInteger = 1
#IsFloat = 2
InString = Trim(InString)
Protected IsDecimal, CaughtThousand, CaughtDecimal, CaughtE
Protected Sign = 1, IsSignPresent, IsSignAllowed = #True, CountNumeric
Protected *CurrentChar.Character = @InString
While *CurrentChar\c
Select *CurrentChar\c
Case '0' To '9'
CountNumeric + 1
If CaughtThousand And CountNumeric > 3: ProcedureReturn #NotNumber: EndIf
IsSignAllowed = #False
Case ThousandsSeparator
If CaughtDecimal Or CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
ProcedureReturn #NotNumber
EndIf
CaughtThousand = #True
CountNumeric = 0
Case DecimalCharacter
If CaughtDecimal Or CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
ProcedureReturn #NotNumber
EndIf
CountNumeric = 0
CaughtDecimal = #True
IsDecimal = #True
CaughtThousand = #False
Case '-'
If IsSignPresent Or Not IsSignAllowed: ProcedureReturn #NotNumber: EndIf
If Not CaughtE: Sign = -1: EndIf
IsSignPresent = #True
Case '+'
If IsSignPresent Or Not IsSignAllowed: ProcedureReturn #NotNumber: EndIf
IsSignPresent = #True
Case 'E', 'e'
If CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
ProcedureReturn #NotNumber
EndIf
CaughtE = #True
CountNumeric = 0
CaughtDecimal = #False
CaughtThousand = #False
IsSignPresent = #False
IsSignAllowed = #True
Default
ProcedureReturn #NotNumber
EndSelect
*CurrentChar + SizeOf(Character)
Wend
If CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3): ProcedureReturn #NotNumber: EndIf
If IsDecimal
ProcedureReturn #IsFloat * Sign
EndIf
ProcedureReturn #IsInteger * Sign
EndProcedure
Re: IsNumber
Posted: Tue Apr 06, 2010 8:56 pm
by skywalk
It didn't dawn on me that ValD("100eeee") = 100 instead of 0. until I tried to implement a masked edit string gadget.
ValD() actually truncates the text after it finds a numeric value?? Doh!
So, your code fixes my MaskedEdit gadget entry.
Thanks very much demivec and xombie!
Code: Select all
Procedure.i SF_IsNumeric(StrIn$, DecimalCharacter.c='.', ThousandsSeparator.c=',')
; REV: 100405, Skywalk
; Modified PureBasic Forum: Xombie(061129) + Demivec(100323)
; Removed 3 constants, add Lcase, add 'd' exponent support
; REV: 110311, Skywalk
; Enabled leading '.' to define a numeric: -.1 = -0.1
; Return: 0 = non-numeric value = 0
; 1 = positive integer
; -1 = negative integer
; 2 = positive float
; -2 = negative float
StrIn$ = LCase(Trim(StrIn$)) ; eliminate comparisons for E & D
Protected.i IsDecimal, CaughtThousand, CaughtDecimal, CaughtE
Protected.i Sgn = 1, IsSignPresent, IsSignAllowed = 1, CountNumeric
Protected.i *CurrentChar.Character = @StrIn$
While *CurrentChar\c
Select *CurrentChar\c
Case '0' To '9'
CountNumeric + 1
If CaughtThousand And CountNumeric > 3
ProcedureReturn 0
EndIf
IsSignAllowed = 0
Case ThousandsSeparator
If CaughtDecimal Or CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
ProcedureReturn 0
EndIf
CaughtThousand = 1
CountNumeric = 0
Case DecimalCharacter
;If CaughtDecimal Or CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
If CaughtDecimal Or CaughtE Or (CaughtThousand And CountNumeric <> 3) ; Allow leading decimal to signify a numeric
ProcedureReturn 0
EndIf
CountNumeric = 0
CaughtDecimal = 1
IsDecimal = 1
CaughtThousand = 0
Case '-'
If IsSignPresent Or Not IsSignAllowed
ProcedureReturn 0
EndIf
If Not CaughtE: Sgn = -1: EndIf
IsSignPresent = 1
Case '+'
If IsSignPresent Or Not IsSignAllowed
ProcedureReturn 0
EndIf
IsSignPresent = 1
Case 'e', 'd' ; Lcase = Don't Care -> 'E', 'D'
If CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
ProcedureReturn 0
EndIf
CaughtE = 1
CountNumeric = 0
CaughtDecimal = 0
CaughtThousand = 0
IsSignPresent = 0
IsSignAllowed = 1
Default
ProcedureReturn 0
EndSelect
*CurrentChar + SizeOf(Character)
Wend
If CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
ProcedureReturn 0
EndIf
If IsDecimal
ProcedureReturn 2 * Sgn ; -> Float
EndIf
ProcedureReturn Sgn ; -> Integer
EndProcedure
Enumeration
#Window_0 = 0
#String_0 = 0
#Text_0
EndEnumeration
Procedure.i ForceNumeric(gID.i)
; Verify numeric entries...
If SF_IsNumeric(GetGadgetText(gID))
SetGadgetColor(gID, #PB_Gadget_BackColor, #Green)
ProcedureReturn #True
Else
SetGadgetColor(gID, #PB_Gadget_BackColor, #Red)
ProcedureReturn #False
EndIf
EndProcedure
If OpenWindow(#Window_0, 434, 225, 193, 174, "Verify Numeric Entry", #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_TitleBar )
Attributes.i = #ES_CENTER
TextGadget(#Text_0, 10, 10, 150, 20, "Enter Numeric Text Below", #PB_Text_Center)
hStringGadget = StringGadget(#String_0, 10, 45, 150, 20, "-1.234e-12", Attributes)
SendMessage_(hStringGadget, #EM_SETLIMITTEXT, 16, 0) ; Force character limit = 16
EndIf
quit = #False
Repeat
event = WaitWindowEvent()
Select event
Case #PB_Event_Gadget
Select EventGadget()
Case #String_0
If EventType() = #PB_EventType_Change
ForceNumeric(#String_0)
EndIf
EndSelect
Case #PB_Event_CloseWindow
quit = #True
EndSelect
Until quit
Re: IsNumber
Posted: Sat Apr 10, 2010 9:35 am
by Frarth
I always prefer to keep things simple. If a user enters '23rubbish' the Val function would return 23, so it is numeric. Each language has its specific VAL functions and to me, it would be logical to write an IsNumeric function which honours the VAL function's behaviour.
Having said this, I believe this would be a good start (with support for Hex numbers):
Code: Select all
; IsNumeric by Frarth (P) April 2010
Procedure.b IsNumeric(Text.s)
; something to test?
If Text = ""
ProcedureReturn #False
EndIf
; non zero integer?
If Val(Text) <> 0
ProcedureReturn #True
EndIf
; non zero float?
If ValD(Text) <> 0
ProcedureReturn #True
EndIf
; starts with zero?
If Left(Text, 1) = "0"
ProcedureReturn #True
EndIf
; starts with signed or float zero or hex zero?
If Len(Text) > 1
Select Left(Text, 2)
Case "-0", "+0", ".0", "$0"
ProcedureReturn #True
EndSelect
; starts with signed float or hex?
If Len(Text) > 2
Select Left(Text, 3)
Case "-.0", "+.0", "-$0", "+$0"
ProcedureReturn #True
EndSelect
EndIf
EndIf
ProcedureReturn #False
EndProcedure
; Examples
Text.s = "1"
Debug IsNumeric(Text)
Text.s = "1rubbish"
Debug IsNumeric(Text)
Text.s = ".0"
Debug IsNumeric(Text)
Text.s = "-.0"
Debug IsNumeric(Text)
Text.s = "-1.5E-7"
Debug IsNumeric(Text)
Text.s = "$beef"
Debug IsNumeric(Text)
Text.s = "-$beef"
Debug IsNumeric(Text)
Re: IsNumber
Posted: Sat Apr 10, 2010 3:27 pm
by skywalk
Frarth wrote:I always prefer to keep things simple. If a user enters '23rubbish' the Val function would return 23, so it is numeric. Each language has its specific VAL functions and to me, it would be logical to write an IsNumeric function which honours the VAL function's behaviour.
Having said this, I believe this would be a good start (with support for Hex numbers)
Hi Frarth,
What a crazy wacky world where a user is entering numbers in Hex?
Do you have Vulcans for customers?
Seriously though, I have had applications where users required a bit stream(variable lengths) to be defined for a serial interface to control custom integrated circuits.
Entering in HEX would complicate the subtle state changes.
ie: 10101010 = AA, The '1' state locations are way easier to follow in Binary than the HEX 'A' defines.
And restricting user inputs on Binary StringGadgets is limited to '1's and '0's and any don't care letter you choose, like 'X'.
As for my current application, if a user enters a '23rubbish', I want to notify them via a Red background, and not accept an erroneous entry.
Like you, I thought following the Val("stringnumeric") logic would have met my needs, but it fails in this case.
Re: IsNumber
Posted: Sat Apr 10, 2010 4:38 pm
by Frarth
Hi Steve,
Nope, I do not have Vulcans on board here; instead there are Arcturians

. Just thought it to be nice to support hex numbers.
Allowing customers to only follow very strict rules often requires a lot more coding, as we have seen in this topic. IMO there is more flexibility in allowing rubbish (erronous entries or formatted code) behind a number. If the outcome is 23, which it should be, who cares about any dismissed part? But that's just MHO.
Handling numeric data can be complicated anyway. For instance, handling geographic coordinates, dates or times. Most applications do not allow other separators than the standard (I hate those irritating error messages). But if the three parts can be retrieved because the separators are any other than numeric, the outcome will be correct. That's why I seldomly use a numeric filter with a string gadget.
Frank