IsNumeric Function

Für allgemeine Fragen zur Programmierung mit PureBasic.
SMaag
Beiträge: 152
Registriert: 08.05.2022 12:58

IsNumeric Function

Beitrag von SMaag »

Gibt es eine Funktion, welche testet ob ein String ein numerischer Wert ist auch welchen Typs
wie z.B. Integer, Float, Hex oder binär?

etwa so
sTxt.s = "1.23"

If IsNumeric(sTxt) = #IsFloat
MyValue.d = ValD(sTxt)

ElseIf IsNumeric(sTxt) = #IsINT
MyValue.i = Val(sTxt)

Endif
Benutzeravatar
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: IsNumeric Function

Beitrag von mk-soft »

Gibt es leider nicht

Auch wenn es einige Beispiel gibt, habe ich diese mal neu geschrieben

Update v1.02.2
- Erlaubt Zeichen nach Zahl (wie PB)
- Erlaubt Leerzeichen vor Zahl (wie PB)
- Regeln verschärft (Strenger wie PB)

Code: Alles auswählen

;-TOP by mk-soft, v1.02.2, 12.11.2022

Enumeration 1
  #IsInteger
  #IsFloat
EndEnumeration

Procedure IsNumeric(Value.s)
  Protected r1, *value.character, signed, num, float, exponent, exponent2
  
  *value = @Value
  If *value
    Repeat
      If *value\c = '+' Or *value\c = '-'
        If signed
          num = #False
          Break
        EndIf
        signed = #True
      ElseIf *value\c >= '0' And *value\c <= '9'
        num = #True
        Break
      ElseIf *value\c <> ' '
        Break
      EndIf
      *value + SizeOf(character)
    ForEver
    If num
      Repeat
        *value + SizeOf(character)
        If *value\c = 0
          Break
        EndIf
        If *value\c = '.'
          float = 1
          Break
        ElseIf *value\c = 'e' Or *value\c = 'E'
          exponent = 1
          Break
        ElseIf *value\c < '0' Or *value\c > '9'
          Break
        EndIf
      ForEver
      
      If float
        Repeat
          *value + SizeOf(character)
          If *value\c >= '0' And *value\c <= '9'
            float + 1
          ElseIf *value\c = 'e' Or *value\c = 'E'
            exponent = 1
            Break
          Else
            Break
          EndIf
        ForEver
      EndIf
      If exponent
        Repeat
          *value + SizeOf(character)
          If *value\c >= '0' And *value\c <= '9'
            exponent + 1
          ElseIf *value\c = '+' Or *value\c = '-'
            If exponent >= 2
              Break
            ElseIf exponent2
              num = #False
              Break
            EndIf
            exponent2 = #True
          Else
            Break
          EndIf
        ForEver
      EndIf
      If num
        If float And float < 2
          r1 = 0
        ElseIf exponent And exponent < 2
          r1 = 0
        Else
          If float
            r1 = #IsFloat
          Else 
            r1 = #IsInteger
          EndIf
        EndIf  
      EndIf
    EndIf
  EndIf
  ProcedureReturn r1
EndProcedure

; ****

CompilerIf #PB_Compiler_IsMainFile
  
  Define t1.s
  
  Debug "Numbers"
  
  t1 = " 50"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "1000"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + Val(t1)
  
  t1 = "+1000 Meters"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + Val(t1)
  
  t1 = "-1000"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + Val(t1)
  
  t1 = "1.1"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "+100.0%"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "-100.0%"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "1E3"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "1.0e3+2"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "-0.1E+5+"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "-1.0e-3"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  
  Debug "Invalid Numbers"
  t1 = ".100"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  t1 = "100."
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "++100"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "--100"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "100..0"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "s100.0"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "2E"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = "1.0ee-3"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  t1 = "1.0e+-3"
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
  t1 = ""
  Debug t1 + " = Type " + IsNumeric(t1) + " Value = " + ValF(t1)
  
CompilerEndIf
Zuletzt geändert von mk-soft am 12.11.2022 21:05, insgesamt 7-mal geändert.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
jacdelad
Beiträge: 348
Registriert: 03.02.2021 13:39
Computerausstattung: Ryzen 5800X, 108TB Festplatte, 32GB RAM, Radeon 7770OC
Wohnort: Riesa
Kontaktdaten:

Re: IsNumeric Function

Beitrag von jacdelad »

Code: Alles auswählen

If Str(Val(String$))=String$
PureBasic 6.11/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/150TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
Benutzeravatar
mk-soft
Beiträge: 3701
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: IsNumeric Function

Beitrag von mk-soft »

jacdelad hat geschrieben: 12.11.2022 16:15

Code: Alles auswählen

If Str(Val(String$))=String$
Not work with Zero and Floats
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
jacdelad
Beiträge: 348
Registriert: 03.02.2021 13:39
Computerausstattung: Ryzen 5800X, 108TB Festplatte, 32GB RAM, Radeon 7770OC
Wohnort: Riesa
Kontaktdaten:

Re: IsNumeric Function

Beitrag von jacdelad »

Bei vielen einfachen Verwendungen sollte es reichen.

Wie wäre es ansonsten mit RegEx:

Code: Alles auswählen

^-?[0-9]\d*(\.\d+)?$
PureBasic 6.11/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/150TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
SMaag
Beiträge: 152
Registriert: 08.05.2022 12:58

Re: IsNumeric Function

Beitrag von SMaag »

Das ist leider nicht so ganz trivial wie das zuerst aussieht, hab auch schon eine Weile rumprobiert.
Alle Beispiele die man so findet haben leider ihre Schwächen. Ich hab mittlerweile auch ne Version
programmiert. Hat aber auch diverse Schwächen und ist zu kompliziert geworden. D.h. man versteht
das nicht mehr, wenn man das nach Wochen erweitern möchte.
Der Absatz von mk.soft hat mich aber auf eine bessere Idee gebracht. Nämlich die Zeichen nicht
allgemein zu parsen, und dann mit einer Menge IF und Case das für die verschiedenen Möglichkeiten
zu zerlegen, sondern jedes Zeichen , mehrmals immer für den gewünschten Typ zu parsen.
Das gibt weit mehr als eine Funktion - das wird ein komplettes Modull

Reglar Expressions, darüber hab ich auch schon nachgedacht! Hat aber einen Hacken. Das bringt
nur #True/#False für die Übereinstimmung mit einem Schema. Ich hätte das aber gerne erweitert,
so dass man die Fehlerstelle ausweisen kann. Z.B. zweites Komma oder falsches Zeichen.

ich hab den Ansatz von mk-Soft mal einem Testlauf durchgeführt, das erkennt noch alles mögliche als
korrekt: (gleiches Probelm hatte ich bei meinem Code auch)


======================================
'-' : Not Numeric :
'+' : Not Numeric :
'' : Not Numeric :

'-12.003e-3' : IsFloat :
'12e3' : IsINT :
'12e+3' : IsINT :
'12E+3' : IsINT :
'+12.003e-3' : IsFloat :
'+123e-3+2' : IsINT :
'12.003e-3' : IsFloat :
'-12.003E+03' : IsFloat :
'-12.003E+' : Not Numeric :
'-12.003E' : Not Numeric :

'0123456789' : IsINT :
'0001234567' : IsINT :
' 12345' : IsINT :
'12345 ' : IsINT :
'12 345' : IsINT :
'12-345' : IsINT :
'12+34-5' : IsINT :
'12+345e+34-5' : IsINT :
=========================================

Mein Problem geht aber noch etwa weiter.
Ich muss HEX und Binärwerte mit und ohne PreFix erkennen können,
evtl 1000er Punkte bei Währungswerten.

Die momentane Idee ist, am Anfang alle Möglichkeiten auf TRUE zu setzen
#IsInt, IsFloat, IsHex, IsBin, IsCurrency ...
dann das Zeichen für Zeichen zu parsen und die FLAGs zurücksetzen,
die es nicht mehr sein können. Sobald ich einen vernüftigen CodeAnsatz habe,
werde ich den hier mal Posten, dann hat bestimmt jemand noch gute Ideen

Danke erst mal!
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8679
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: IsNumeric Function

Beitrag von NicTheQuick »

Richtig "witzig" wird das erst, wenn du das jetzt noch für verschiedene Länder haben willst. Weil überall andere Zeichen benutzt werden, andere Trennzeichen, usw.

Beispiel: "123,456"

Nach en_US wäre das: 123456
Nach de_DE wäre das: 123.456
Nach de_CH wäre das: NaN/Error

Hab das mal mit Python verifiziert:

Code: Alles auswählen

>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'en_US.utf8')
'en_US.utf8'
>>> locale.atof("123,456")
123456.0
>>> locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
'de_DE.utf8'
>>> locale.atof("123,456")
123.456
>>> locale.setlocale(locale.LC_ALL, 'de_CH.utf8')
'de_CH.utf8'
>>> locale.atof("123,456")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.10/locale.py", line 338, in atof
    return func(delocalize(string))
ValueError: could not convert string to float: '123,456'
Wenn du unterscheiden willst, ob etwas binär, hexadezimal oder dezimal ist, dann kann man das natürlich nie sicher machen, da zum Beispiel die Zahl 101 alles sein kann.
Bild
SMaag
Beiträge: 152
Registriert: 08.05.2022 12:58

Re: IsNumeric Function

Beitrag von SMaag »

Ja, das ist echt Sch....!

da es ja nicht nur ein Typ sein kann z.B. 1001 (Binär, Hex, Integer), ist das in einer Funktion
nicht umzusetzen. Auch mein Ansatz mit den Bools je Typ auf TRUE und dann StepByStep
rücksetzen was es nicht ist, führt in das gleiche If-Else Caos. Man kann das einfach später
nicht mehr nachvollziehen und somit auch nicht einfach anpassen.
Der im Moment für mich einzig vernünftige Ansatz ist, für jeden Typ einen eigene Parser-Funktion
IsInt(), IsFloat(), IsBin(), IsHex() und das dann eben nacheinander aufgerufen.

Die IsInteger() Funktion hab ich bereits , so nach dem Schema werd ich das auch für die anderen
Typen machen. Damit bleibt das nachvollziehbar.

Achtung noch nicht 100% getestet! Kann noch Fehler haben!

Code: Alles auswählen

Procedure IsInteger(sTxt.s)
  ; ============================================================================
  ; NAME: IsInteger
  ; DESC:
  ; VAR(sTxt.s): Text to test
  ; RET.i: If it is an Integer: Number of Digits, otherwise 0
  ; ============================================================================
    
    Protected I, RET, Digits, c.c
    Protected xSign, xNegativ, xSpaceAtEnd
    Protected IsInt = #True
    Protected *char.pChar   ; Pointer to virtual CHAR-ARRAY
    
    *char = @sTxt           ; overlay the String with a virtual Char-Array
        
    If Not *char            ; If *Pointer to String is #Null
      ProcedureReturn 0     ; ******** NotNumeric => EXIT PROCEDURE *********     
    EndIf
    
    I = 0
    
    ; ----------------------------------------------------------------------
    ;  Check for '+', '-' and skip Spaces
    ; ---------------------------------------------------------------------- 
    While *char\c[I]
      
      Select *char\c[I]
        Case #IsNum_SPACE
          
        Case '+'
          xSign = #True
          
        Case '-'
          xSign = #True
          xNegativ = #True          
          Break
          
        Default
         Break
         
     EndSelect      
      I + 1     ; if we leave with Break, I is not inkremented
    Wend
    
    ; ----------------------------------------------------------------------
    ;  Parse Number - Check for Digits '0' to '9'
    ; ---------------------------------------------------------------------- 
    While *char\c[I]
      
      Select *char\c[I]
          
        Case #IsNum_SPACE
          xSpaceAtEnd=#True   ; Space dedected : if it is not in the middle it is ok!
          Break
          
        Case '0' To '9'
          Digits + 1          ; count Digits
                    
        Default               ; any other Char is not allowed in an Integer
          IsINT = #False      ; Is not an Integer
          Break
         
     EndSelect      
        I + 1                 ; Attention if we leave with break, I is not inkremented
    Wend
    
    ; ----------------------------------------------------------------------
    ;  Skip possible SPACEs at the end
    ; ---------------------------------------------------------------------- 
    If xSpaceAtEnd
      I + 1           
      While *char\c[I]       ; if all Chars at end are Spaces it is ok 
        If *char\c[I] <> #IsNum_SPACE ; if any other Char follows after the Space, it is not an INT
          IsINT = #False
        EndIf
        I + 1
      Wend      
    EndIf
    
    If IsInt 
      ProcedureReturn Digits      ; if it is an Integer, return the number of Digits
    Else
      ProcedureReturn 0           ; otherwise 0
    EndIf
    
  EndProcedure
SMaag
Beiträge: 152
Registriert: 08.05.2022 12:58

Re: IsNumeric Function

Beitrag von SMaag »

ohne den pCahr Type geht's nicht.
Das ist ein Pointer auf ein virtuelles Char-Array. Das gehört zu den undokumentierten Features von PB. Den Trick hab ich mir
im SourceCode der PB-IDE abgekuckt!

Code: Alles auswählen

  Structure pChar   ; virtual CHAR-ARRAY, used as Pointer to overlay on strings 
    c.c[0]          ; fixed ARRAY Of CHAR Length 0
  EndStructure

Benutzeravatar
juergenkulow
Beiträge: 188
Registriert: 22.12.2016 12:49
Wohnort: :D_üsseldorf-Wersten

Re: IsNumeric Function

Beitrag von juergenkulow »

@SMaag Danke.

Code: Alles auswählen

; Feldzugriff auf Speicher 
Structure pChar   ; virtual CHAR-ARRAY, used as Pointer to overlay on strings 
  c.c[0]          ; fixed ARRAY Of CHAR Length 0
EndStructure

Define *c.pChar=AllocateMemory(16)
*c\c[5]=33
;   MOV    rbp,qword [p_c]
;   MOV    word [rbp+10],33
;   C Backend: p_c->f_c[5LL]=33;
;   000000014000110C | 48:8B05 05330000         | mov rax,qword ptr ds:[140004418]                              |
;   0000000140001113 | 66:C740 0A 2100          | mov word ptr ds:[rax+A],21                                    | 21:'!'
For i=0 To 7
  ch.c=*c\c[i]
  ;     MOV    rbp,qword [p_c]
  ;     PUSH   rbp
  ;     MOV    rax,qword [v_i]
  ;     ADD    rax,rax
  ;     POP    rbp
  ;     ADD    rbp,rax
  ;     MOVZX  rax,word [rbp]
  ;     MOV    word [v_ch],ax
  ;     C Backend: v_ch=p_c->f_c[v_i];
  ;     0000000140001135 | 48:8B05 DC320000         | mov rax,qword ptr ds:[140004418]                              |
  ;     000000014000113C | 48:8B15 DD320000         | mov rdx,qword ptr ds:[140004420]                              |
  ;     0000000140001143 | 0FB70450                 | movzx eax,word ptr ds:[rax+rdx*2]                             |
  ;     0000000140001147 | 66:8905 E2320000         | mov word ptr ds:[140004430],ax                                |
  s.s+RSet(Hex(ch),4,"0000")+" "
Next  
Debug s
Debug Hex(*c\c[5])
ShowMemoryViewer(*c,16)
Antworten