Sehr große Zahlen addieren führt zum falschen Ergebnis
-
Tyzer -
3. November 2023 um 02:27 -
Erledigt
-
-
Tyzer
3. November 2023 um 02:30 Hat den Titel des Themas von „Sehr große Zahlen addieren führt zum falschen ergebnis“ zu „Sehr große Zahlen addieren führt zum falschen Ergebnis“ geändert. -
Code
Alles anzeigenGlobal $a = "0x7FFE6E84D380", $b = "0xFFFFFFF2" ConsoleWrite(Hex(_SumStrings(Hex2Dec($a), Hex2Dec($b))) & @CRLF) Func Hex2Dec($iN) Local $aN, $ihex = 0 $aN = StringSplit(StringTrimLeft($iN, 2), "", 1) For $x = 1 To UBound($aN) - 1 $ihex += Dec($aN[$x]) * (16 ^ (UBound($aN) - 1 - $x)) Next Return $ihex EndFunc ;==>Hex2Dec Func _SumStrings($addend1,$addend2) Local $Lenght, $tocarry = 0, $Sum = "", $c, $SubTotal If Not IsString($addend1) Then $addend1 = String(Int($Addend1)) If Not IsString($addend2) Then $addend2 = String(Int($Addend2)) If not StringIsInt($addend1) Or not StringIsInt($addend2) Then Exit EndIf If StringLen($addend1) > StringLen($addend2) Then $Lenght = StringLen($addend1) Else $Lenght = StringLen($addend2) EndIf If $Lenght <= 3 Then $Lenght = 3 Else $Lenght = $Lenght + Mod($Lenght,3) EndIf While StringLen($Addend1) <> $Lenght $Addend1 = "0" & $Addend1 Wend While StringLen($Addend2) <> $Lenght $Addend2 = "0" & $Addend2 Wend For $c = 1 to $Lenght/3 $SubTotal = Int(StringRight($addend1,3)) + Int(StringRight($addend2,3)) + $ToCarry If $SubTotal > 999 Then $ToCarry = Int($SubTotal/1000 ) Else $ToCarry = 0 EndIf String($SubTotal) While StringLen($SubTotal) < 3 $SubTotal = "0" & $SubTotal Wend $Sum = StringRight($Subtotal,3) & $Sum $Addend1 = StringTrimRight($Addend1,3) $Addend2 = StringTrimRight($Addend2,3) Next While StringInStr($sum,"0") = 1 $Sum = StringTrimLeft($sum,1) Wend Return $sum EndFunc
(Die Funktionen sind nicht von mir)
-
gibts hier irgendwelche Umwege?
Such mal im Forum nach der BigNum UDF.
-
Moin,
bei den von Dir verwendeten Werten würde ich noch nicht von 'sehr großen Zahlen' sprechen.
Meine Meinung zum Problem, das mir bis jetzt nicht bewusst war:
AutoIt unterscheidet (im Gegensatz zu AutoHotkey) zwischen 32-bittigen und 64-bittigen Integerwerten. Bei der Zuordnung der Hexwerte zu einem dieser Datentypen wird 0x7FFE6E84D380 (6 Bytes) offenbar als 64-bittiger Wert behandelt, während 0xFFFFFFF2 (4 Bytes) als 32-bittiger Wert behandelt wird.
Der 32-bittige Hexwert 0xFFFFFFF2 entspricht aber dem Dezimalwert -14. Als 64-bittiger Wert ergäbe sich dagegen 4294967282, und mit diesem Wert rechnet der Windows Rechner.
Eine 'Gleichbehandlung' der Variablen $a und $b kann man erzwingen, indem man den 32-bittigen Wert mit führenden Nullen auf mindestens 5 Bytes erweitert.
Eine andere Möglichkeit habe ich auf die Schnelle nicht gefunden.
Ob das ein Bug ist, müssen Andere entscheiden.
-
Liefer bei mir auch das falsche Ergebnis.
Aber teste mal folgendes (Den "String" erstmal in einen Integer verwandeln, dann rechnen und dann in Hex):
Ausgabe: 00007FFF6E84D372
Auf die Idee hat mich das hier gebracht: https://www.autoitscript.com/forum/topic/10…&comment=733853
Der Grund ist das ein String von Hex scheinbar anders behandelt wird als eine Zahl.
-
-
Ob das ein Bug ist, müssen Andere entscheiden.
Entscheiden kann ich nicht. Aber argumentieren.
Und ich halte dies für einen Bug denn:
Auf Speicherebene gepresst in eine 4-Byte-Stelle und interpretiert als 32-Bit-Wert, würde diese Bitfolge tatsächlich als -14 interpretiert.Die Aufgabe der Hex-Notation für Zahlen in AutoIt ist jedoch eine andere: Eingabe von Zahlenwerten im Hexadezimalsystem. Und an diesem Punkt ist der Datentyp, in welchem das mal kodiert werden noch völlig egal.
Daher ist es ein Abweichen vom zu erwartenden Verhalten und für mich daher klar ein Bug.
Ich bereite das mal als Ticket für den Issue-Tracker auf.
Eine andere Möglichkeit habe ich auf die Schnelle nicht gefunden.
Das ist erst einmal die schnellste und cleverste Variante. Ich bin nur auf die explizite Variante gekommen: $b = Dec("FFFFFFF2", 2)
Edit: >>Ticket<< erstellt.
-
Beitrag von Schnuffel (
3. November 2023 um 19:52 )Dieser Beitrag wurde vom Autor gelöscht (3. November 2023 um 19:59 ). -
Beitrag von Schnuffel (
3. November 2023 um 19:57 )Dieser Beitrag wurde vom Autor gelöscht (3. November 2023 um 19:59 ). -
Ticket wurde abgelehnt.
Grund: Es wird nicht der Unterschied zwischen der mathematischen Hexadezimaldarstellung von Zahlen und der konkreten Kodierung von Zahlen in bestimmten Datentypen verstanden.
-
Moin AspirinJunkie,
danke für den Versuch. Ich bin nach wie vor der Meinung, dass das Verhalten so kaum erwartet wird. Es scheint aber in AutoIt konsistent zu sein:
AutoIt; Der Hexwert 0XFFFFFFF2 entspricht als Int dem Dezimalwert -14 ; und als UInt dem Dezimalwert 4294967282 MsgBox(0, "Positiv", Hex(4294967282)) ; => 00000000FFFFFFF2 MsgBox(0, "Negativ", Hex(-14)) ; => FFFFFFF2
Einem positiven Wert im Bereich von 0x80000000 bis 0xFFFFFFFF muss man offensichtlich zumindest eine 0 voranstellen. Die Doku sollte das allerdings 'deutlicher' beschreiben.
-
Na eben das zeigt ja, dass das Verhalten eben genau nicht konsistent ist.
Man kann eine Variable mit einer Zahl vorbelegen.
Hierfür hat man ausdrücklich die Wahl (siehe Hilfe) zwischen der Dezimaldarstellung und der Hexadezimaldarstellung.
Wichtig: Es geht um die mathematische Darstellung einer Zahl nur mit jeweils unterschiedlichen Zahlensystembasen.
Diese Darstellung ist unabhängig davon wie diese Zahl mal binär kodiert wird - also ob als Int32, UInt32, Int64 etc.
Und jeder korrekte Zahlensystemkonverter der Welt macht aus 0x80000000 eben 2147483648. Einfach weil das Hexadezimalsystem nun mal so funktioniert.
Jetzt sagt aber AutoIt: Ne im Bereich 0x80000000 bis 0xFFFFFFFF interpretieren wir das was der User eingibt aber anders: Wir nehmen hier an, dass der User die binäre Kodierung einer Int32-Zahl meint anstatt dem Zahlenwert.
Das ist aber eben genau nicht konsistent, da sie dann konsequenterweise bei Eingabe von Zahlen im Dezimalsystem genauso verfahren müssten damit es konsistent ist. Sprich: gibt der User Zahlen im Bereich 2147483648 bis 4294967295 ein, gehen wir davon aus, dass der stattdessen die binäre Repräsentation in Int32 meint.
Denn: Die Zahlenwerte sind exakt die gleichen nur andere Basis ( (FFFFFFFF)16 = (4294967295)10 ).
Dennoch werden die Zahlen unterschiedlich interpretiert. Und davon steht nichts in der Doku.
Ich bleibe dabei dass es ein Bug ist.
Möchte man negative Zahlen, dann nimmt man den Vorzeichenoperator und fängt nicht an Nullen abzuzählen.
Andere dynamisch typisierende Sprachen wie z.b. Python, Javascript und Ruby machen das übrigens korrekt im Gegensatz zu AutoIt.
-
Nachdem ich die Doku noch einmal genauer erforscht habe, würde ich es nicht unbedingt als Bug bezeichnen, weil es schon halbwegs dokumentiert ist:
Numbers
Supported types are signed integers and floating-point: Int32, Int64 and Double. Bitsize of an integer depends on its magnitude and is automatically adjusted.
Es gibt also keine 'unsigned integers'.
Es gab mal Zeiten, in denen Arbeitsspeicher ein kostbares Gut war. Damals haben sich alle darum bemüht, die Daten möglichst platzsparend zu speichern. Diese Angewohnheit scheint AutoIt selbst in Zeiten der 64-Bit Systeme mit Gigabytes von RAM in Bezug auf die Zahlen beibehalten zu haben. 'Unter der Decke' war man ja schon immer recht änderungsscheu. Nach wie vor können die Funktionen zur Bitmanipulation Bit...() mit 64-Bit Werten nichts anfangen.
Es steht außer Zweifel, dass sich der Wert 0xFFFFFFF2 in 4 Bytes speichern lässt. Wenn diese 4 Bytes dann als 'signed integer' behandelt werden, ergibt das korrekterweise den Dezimalwert -14.
Das Absurde daran ist für mich, dass für die o.a. 'magnitude' nicht nur der Wert sondern auch die Länge der Hexbytefolge berücksichtigt wird, sodass vorangestellte Nullen den Wertetyp (Int32 / Int64) beeinflussen.
-
Es gibt also keine 'unsigned integers'.
Weswegen ein 32-Bit-Integer in AutoIt nicht das korrekte Format ist um Zahlen ab 2147483648 zu kodieren.
Ab diesem Zahlenwert muss ergo auf Int64 ausgewichen werden.
Das haben die Blitzmerker von AutoIt auch korrekt erkannt aber nur wenn man die Zahl dezimal notiert.
Nur wenn sie hexadezimal angegeben wird, wird dies unterlassen.
Es steht außer Zweifel, dass sich der Wert 0xFFFFFFF2 in 4 Bytes speichern lässt.
Nur dann wenn die Kodierung 8 binäre Zahlenwertstellen besitzt. Und das ist nur bei UInt32 der Fall. Die hexadezimale Darstellung der Zahl 4294967282 ist 0xFFFFFFF2. Die hexadezimale Darstellung der Zahl -14 ist hingegen -0xE.
Erst wenn es an die Kodierung dieser Zahl geht und man sich für Int32 entscheidet, erst dann ist die Binärfolge hierfür im Speicher 0xFFFFFFF2.
Die hexadezimale Zahl 0xFFFFFFF2 kann sich ergo nicht in 4 Byte speichern lassen, wenn man Int32 als Kodierung verwendet, da damit dieser Zahlenbereich nicht abgedeckt wird.
Das Absurde daran ist für mich, dass für die o.a. 'magnitude' nicht nur der Wert sondern auch die Länge der Hexbytefolge berücksichtigt wird, sodass vorangestellte Nullen den Wertetyp (Int32 / Int64) beeinflussen.
Das ist nicht wirklich absurd sondern das Kernproblem der Sache.
Wenn wir Code der Form $A = 123 schreiben dann geht der Parser/Interpreter hin und sieht: Aha - eine Variablendefinition. Der zugewiesene Wert entspricht dem Stringmuster eines Zahlenwertes. Nun muss er noch überlegen, welche interne Kodierung er hierfür nimmt.
Das macht er im Falle der Dezimaldarstellung ganz korrekt indem er schaut ob der Zahlenwert noch im Int32-Wertebereich liegt oder darüber:
AutoIt$dA = 2147483647 $dB = 2147483648 ConsoleWrite(StringFormat("% 12s = %-14d (%s)\n% 12s = %-14s (%s)\n", _ "2^31 - 1",$dA, VarGetType($dA), _ "2^31",$dB, VarGetType($dB) _ ))
Das ist sinnvoll und korrekt.
Wenn man aber stattdessen die ebenfalls zulässige hexadezimale Notation wählt, dann geht der Parser/Interpreter komplett anders vor.
Um zu prüfen, welche Kodierung er verwenden soll schaut er überhaupt nicht mehr auf den Zahlenwert sondern einfach auf die Stringlänge der Zahl im Quellcode.
Ist es größer 8 (bzw. 10) dann Int64, ist es kleiner 8 dann Int32.Der Zahlenwert wird hierbei komplett außer Acht gelassen und das führt zu dem hier beschriebenen Phänomen:
AutoIt$dA = 0x7FFFFFFF $dB = 0x80000000 $dC = 0x000000001 ConsoleWrite(StringFormat("% 12s = %-14s (%s)\n% 12s = %-14s (%s)\n% 12s = %-14s (%s)\n", _ "0x7FFFFFFF",$dA, VarGetType($dA), _ "0x80000000",$dB, VarGetType($dB), _ "0x000000001",$dC, VarGetType($dC) _ ))
Alles was die Devs ändern müssten, wäre auch bei Hexadezimalnotation auf den Zahlenwert selbst zu prüfen anstatt auf die unsinnige Länge.
Will den hier niemand den Unterschied zwischen Zahlenwert und Kodierung verstehen?
-
Will denn hier niemand den Unterschied zwischen Zahlenwert und Kodierung verstehen?
Doch, genau das ist ja das Problem.
Für eine Folge von 32 Bits (0 .. 31) im Speicher gibt es nicht nur einen möglichen Wert, wenn die Bitstelle 31 gesetzt ist. Abhängig davon, ob der Bereich im Programm als 'signed' oder 'unsigned' definiert ist, ergeben sich zwei unterschiedliche Werte.
Bei der Zuordnung von Werten zu den Datentypen Int32 und Int64 hat einer der AutoIt-Entwickler anscheinend die 'geniale' Idee gehabt, bei Hexwerten ohne aufwändige Konvertierungen und Wertebereichsprüfungen direkt auf die Länge der Hexzeichenfolge zurückzugreifen. Daraus folgt dann: Alles mit maximal 8 Stellen hinter dem 0x passt in 4 Bytes (Int32), alles andere wird in 8 Bytes (Int64) gespeichert. Das bringt auf modernen Rechnern bestimmt einige Nanosekunden Performanzgewinn.
Ich kann mir das Verhalten nicht anders erklären, aber vielleicht weiß ja einer von Euch mehr?
-
Doch, genau das ist ja das Problem.
Für eine Folge von 32 Bits (0 .. 31) im Speicher gibt es nicht nur einen möglichen Wert, wenn die Bitstelle 31 gesetzt ist.
Also immer noch nicht den Unterschied verinnerlicht.
Die Hexadezimaldarstellung hat noch nichts - überhaupt nichts - mit Bits zu tun.Es ist einfach nur eine Form den Wert einer Zahl mit Zeichen (in dem Fall die Zeichen 0-F) darzustellen.
Sie unterscheidet sich nicht von der Dezimaldarstellung oder den römischen Zahlen oder sonstwelchen Repräsentationen.
Und das wichtigste hierbei ist: Es ist definiert wie ein bestimmter Zahlenwert in diesem System abgebildet wird.
Eine Zahl im Hexadezimalsystem mit der Darstellung 0x8000000016 ist immer und ausschließlich der Zahlenwert 214748364810.Diesen Raum verlässt man in dem Moment wo man anfängt von Bits zu reden.
Denn dann befinden wir uns in einer ganz anderen Ebene: der der Zahlenkodierung im Speicher.Es gibt im Hexadezimalsystem keine Vorzeichenbits - die gibt es nur in der speziellen Art und Weise der Zahlenkodierung nach ISO/IEC 10967.
Der Umstand, dass hier alles durcheinander gewürfelt wird obwohl es klar abzugrenzende Gebiete sind, hängt primär damit zusammen, dass wir es auch in AutoIt gewohnt sind Bitfolgen hexadezimal zu notieren.
Bei der Variablendefinition wird aber eben keine Bitfolge zugewiesen (das würde man nämlich stattdessen per Binary("0x80000000") machen) sondern es werden Zahlenwerte übergeben.So steht es in der Hilfe und genauso handhabt es AutoIt auch wenn man das Dezimalsystem verwendet.
Nun sieht aber blöderweise die Binärfolge 0x80000000 genau gleich aus wie der Zahlenwert 0x8000000016.
Beide definieren aber etwas gänzlich anderes.Und das ist es was die Verwirrung auslöst.
Auch wenn beide Dinge gleich dargestellt werden - nämlich durch eine hexadezimale Entsprechung - gibt das eine eine Bitfolge im Speicher an, welche erst noch entsprechend interpretiert werden muss und das andere eine ganz normale Zahl wie man sie mit Zettel und Stift auch schreiben kann.Vielleicht wird dies etwas deutlicher wenn man es mal bildlich gegenüber stellt:
AsInt32(Binary(0xFFFFFFF2)) = -0xE16Diese doppelte Verwendung des Hexadezimalsystems - einmal als Zahlenwertdefinition und einmal als Abbildung einer Bitfolge - führt dazu, dass dieses fälschlicherweise anders behandelt wird als die dezimale Darstellung.
Machen wir es mal an einem Beispiel - folgende 3 Zahlenwerte sind alle gleich:
FFFFFFF216 = 214748364810 = 111111111111111111111111111100102
Wir sehen daran: Beide Zahlenwerte, sowohl mit der Basis 16 als auch der Basis 10 stellen die gleiche Binärzahl dar.
Im UInt-System kann man diese Binärstellen direkt in Bits übersetzen.Und beide passen aufgrund des Vorzeichenbits nicht 1 zu 1 in ein Int32.
Dennoch behandelt AutoIt die Dezimaldarstellung korrekt als Zahlenwert und verwendet ein Int64 für diese, während es die Hexadezimalzahl als Bitfolge interpretiert und versucht dessen Binärrepräsentation in Bits eines Int32 zu quetschen.
Dabei stellen beide ein und dieselbe Zahl dar und würden ergo auch gleich kodiert werden müssen.
Werden sie aber eben genau nicht. -
Nun sieht aber blöderweise die Binärfolge 0x80000000 genau gleich aus wie der Zahlenwert 0x8000000016.
Beide definieren aber etwas gänzlich anderes.Ich weiß nicht, wie Du darauf kommst.
In der Datenverarbeitung wird das Hexadezimalsystem sehr oft verwendet, da es sich hierbei letztlich um eine komfortablere Verwaltung des Binärsystems handelt. Die Datenwörter bestehen in der Informatik meist aus Oktetten, die statt als achtstellige Binärzahlen auch als nur zweistellige Hexadezimalzahlen dargestellt werden können. Im Gegensatz zum Dezimalsystem eignet sich das Hexadezimalsystem mit seiner Basis als vierte Zweierpotenz (16 = 24) zur einfacheren Notation der Binärzahlen, da stets eine feste Anzahl Zeichen zur Wiedergabe des Datenwortes benötigt wird. Ein Nibble kann exakt mit einer hexadezimalen Ziffer und ein Byte mit zwei hexadezimalen Ziffern dargestellt werden.
Die Hexwerte entsprechen den Bitfolgen im Speicher, nur die Reihenfolge der Bytes wird bei x86-Systemen intern gedreht.
Unsere normalen Computer kennen nicht anderes als Bitfolgen, die nach konkreten Anweisungen be- bzw. verarbeitet werden müssen. Es gab allerdings mal ein paar Großrechner, die Dezimalzahlen in Hardwre abbilden konnten..
-
Beitrag von Andy (
15. November 2023 um 18:55 )Dieser Beitrag wurde vom Autor gelöscht (15. November 2023 um 18:55 ). -
Ich weiß nicht, wie Du darauf kommst.
Du hast immer noch nicht die Trennung verinnerlicht. Daher zitierst du einen Textfetzen, der den Anwendungsfall abdeckt den ich oben als zweiten Anwendungsfall für das Hexadezimalsystem bereits beschrieben habe - die textliche Darstellung von Bitfolgen.
Es geht hier aber eben an der Stelle der Variablendefinition genau nicht um die Repräsentation von Bitfolgen sondern von Zahlenwerten.
Dass die binäre Entsprechung des Zahlenwertes so in den Speicher geklatscht wird, ist ja eben der Fehler um den es geht.Die Hexwerte entsprechen den Bitfolgen im Speicher, nur die Reihenfolge der Bytes wird bei x86-Systemen intern gedreht.
Das ist ja genau der Fehler. Es werden stur die Binärstellen der Zahl genommen und 1 zu 1 in Bits gepresst.
Dabei wird aber ignoriert, dass die Bits in einem Speicherbereich liegen wo die Zahlen anders kodiert werden müssen.
Bleiben wir doch bei Wikipedia und rechnen mal manuell: >>Umwandlung ins Dezimalsystem<<
Wir nehmen die Zahl FFFFFFF216 und konvertieren sie nach diesem Schema ins Dezimalsystem. Was erhalten wir mit der Rechnung? Genau: 214748364810
Komisch - nichts mit -14 oder sowas?
Nun ist es aber so, dass die Zahl FFFFFFF216 und die Zahl 214748364810 exakt die gleiche Binärfolge haben.
Wenn man dies also in Bits überführt, müsste bei einer Variablendefinition mit 2147483648 ergo auch -14 herauskommen, da wie gesagt exakt die gleiche Binärfolge da ist.
Tut es aber nicht - warum? Weil die Devs dort bisschen mehr Sorgfalt mit den Wertebereichsabfragen für die Zahlenkodierung haben walten lassen.
Und genau das ist das Versäumnis um das es hier geht.Es wird nicht der Zahlenwert beachtet sondern stur die Bitfolge.
Also genau die Trennung von der ich die ganze Zeit schon rede.Wir könnten gerne diskutieren wenn in der AutoIt Hilfe stehen würde: Zahlen werden im Dezimalsystem notiert und Bitfolgen in hexadezimaler Notation.
Stattdessen steht dort klipp und klar:ZitatIntegers (whole numbers) can also be represented in hexadecimal notation by preceding the integer with 0x: e.g. 0x409 or 0x4fff.
Ab dem Punkt verbietet es sich hexadezimale Notation und Dezimalnotation unterschiedlich zu behandeln.
Und wie gesagt: Alle anderen dynamisch typisierenden Sprachen verwenden eben den Zahlenwert und klar nicht die Bitfolgenrepräsentation.
Dort erhält man bei einer Variablendefinition mit 0xFFFFFFF2 als Wert für diese Zahl den korrekten Wert 2147483648 und eben nicht wie bei AutoIt -14.
Also was macht AutoIt hier richtig was alle anderen falsch machen?Müssen wir jetzt ein Bug-Ticket bei Python, Javascript Ruby & Co. aufmachen weil die dort alle zu dämlich sind?
-
Ich bin absolut bei AspirinJunkie .
Der 32-bittige Hexwert 0xFFFFFFF2 entspricht aber dem Dezimalwert -14.
DAS ist schon der Fehler.....eine Hexadezimalzahl entspricht einer Zahl zur Basis 16. Und da ist absolut kein Platz für "negative" Zahlen.
Dass ein INT in 32Bit die negativen Zahlen mit einschließt, hat mit der Hexadezimaldarstellung nichts zu tun! Diese Definition ist ausschließlich relevant für die Darstellung von Dezimalzahlen. Und nur dann erfolgt auch die "Anpassung" an ein Vorzeichen!
0xFFFFFFF2 ist also IMMER 4294967282.
Und da diese dezimale Zahl als (signed) INT nicht im Wertebereich von −2.147.483.648 bis 2.147.483.647 DARSTELLBAR ist, wird per Definition (für die dezimale Darstellung von INT) ein "negativer" Wert von -14 "festgelegt".
Die Entsprechung einer hexadezimalen Zahl ist (in AutoIt s. DllStructCreate) PTR, HWND oder HANDLE. Und da kann man machen was man will, es wird NIE "negative" PTR, HWND oder HANDLE geben.
//EDIT
Die Aufgabe der Hex-Notation für Zahlen in AutoIt ist jedoch eine andere: Eingabe von Zahlenwerten im Hexadezimalsystem. Und an diesem Punkt ist der Datentyp, in welchem das mal kodiert werden noch völlig egal.
Daher ist es ein Abweichen vom zu erwartenden Verhalten und für mich daher klar ein Bug.
THIS!
-