Seite 1 von 1

IsNumeric Function

Verfasst: 12.11.2022 14:30
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

Re: IsNumeric Function

Verfasst: 12.11.2022 16:09
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

Re: IsNumeric Function

Verfasst: 12.11.2022 16:15
von jacdelad

Code: Alles auswählen

If Str(Val(String$))=String$

Re: IsNumeric Function

Verfasst: 12.11.2022 16:32
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

Re: IsNumeric Function

Verfasst: 12.11.2022 22:03
von jacdelad
Bei vielen einfachen Verwendungen sollte es reichen.

Wie wäre es ansonsten mit RegEx:

Code: Alles auswählen

^-?[0-9]\d*(\.\d+)?$

Re: IsNumeric Function

Verfasst: 13.11.2022 17:28
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!

Re: IsNumeric Function

Verfasst: 13.11.2022 18:36
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.

Re: IsNumeric Function

Verfasst: 14.11.2022 12:29
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

Re: IsNumeric Function

Verfasst: 14.11.2022 12:33
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


Re: IsNumeric Function

Verfasst: 15.11.2022 09:57
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)