Ich habe heute eines meiner ersten Java-Programme "geschrieben" und
dabei drei Variablen als Byte definiert.
byte a=b=c=0;
Ein
c=a+b;
will Java (1.4.x) aber gar nicht rechnen und mängelt rum, dass es
Integer haben will. Kann mir jemand sagen warum? Auch mit 2Byte-Werten
(der Name fällt mir gerade nicht ein) will es nicht, erst mit 4Byte-
Werten (int) kommt kein Fehler mehr.
Gruß Torsten
hast du byte a=b=c=(byte) 0;
probiert?
Deine Lösung mag Eclipse auch nicht.
Wenn man das Original in Eclipse reinhaut und alle Lösungsvorschläge
macht, kommt man zu meiner Lösung.
Stefan Kuhne
On 15 Okt., 23:15, Michael Osipov <sg...@gmx.net> wrote:
>
> hast du byte a=b=c=(byte) 0;
>
> probiert?
Nein. Ich wüsste ja nicht mal das das geht (bin ja schließlich
blutiger Java-Anfänger). Andererseits frage ich mich aber auch was das
ändern sollte. Der Compiler erkennt (er teilt es beim auftretenden
Fehler mit) ja dass die Variable als byte definiert ist und er sagt
mir recht eindeutig dass er int haben will. Also scheint die
Definition/ Festlegung (heißt das so?) der Variable richtig zu sein,
nur rechnen will Java nicht damit.
Gruß Torsten
die bloße zahl "0" ist eine Integerzahl. Für long wäre das 0L. für float
0.0f sont wäre 0.0 ein double. Der rest muss gecastet werden.
Ich habe eben mal den SDK installiert um zu testen:
K:\Java>C:\j2sdk1.4.2_16\bin\javac test.java
test.java:5: cannot resolve symbol
symbol : variable b
location: class test
byte a=b=c=(byte) 0;
^
test.java:5: cannot resolve symbol
symbol : variable c
location: class test
byte a=b=c=(byte) 0;
^
Das war wohl nix!
Gruß Torsten
Das funktioniert tatsächlich!
byte a = 64, b = 64, c;
c=(byte) (a+b);
Ausgegeben wird c erwartungsgemäß mit -128.
Warum muss man c in der zweiten Zeile nochmals als byte definieren?
Ich bekomme einen Fehler wenn ich (byte) weglasse und auch wenn ich
nur die Klammern um a+b weglasse:
K:\Java>C:\j2sdk1.4.2_16\bin\javac test.java
test.java:6: possible loss of precision
found : int
required: byte
c= (a+b);
^
1 error
Die Verwendung der Klammern um a+b und auch (byte) in der zweiten
Zeile erscheint mir unlogisch. Warum der ganze Aufwand?
Gruß Torsten
Grüße
Bernd
Grüße
Bernd
On 16 Okt., 00:00, Bernd Post <spheredan...@gmx.de> wrote:
>
> Vorsicht, es gibt eine Java Wrapperklasse Byte.
Du möchtest mir damit sagen ich soll auf Groß-/Kleinschreibung achten?
> Zum deklarieren mehrerer Variablen und zuweisen eines Werts schreibt man
> byte a, b, c = 0;
In diesem Fall meckert der Compiler später im Programm dass a und b
nicht initialisiert wurden. Ich dachte bis eben noch dass bei der
Deklaration "byte a;" automatisch a mit dem Wert 0 belegt wird. Dem
scheint aber nicht so zu sein. Man muss jede Variable einzeln
initialisieren, denn ein "byte a=b=c=0;" funktioniert auch nicht, wie
ich soeben festgestellt habe.
> (in Java sind bytes übrigens
> signed).
Das heißt?
Gruß Torsten
Stefan Kuhne
> Die Vorbelegung kann, muss aber nicht passieren.
Geht's noch schwammiger? Was soll denn der Neuling denken? *g*
Attribute werden mit dem jeweiligen "Nullwert" des Typs vorbelegt,
blocklokale Variablen hingegen nicht.
Gruß,
Michael Paap
> Die Verwendung der Klammern um a+b und auch (byte) in der zweiten
> Zeile erscheint mir unlogisch. Warum der ganze Aufwand?
Java führt arithmetische Operationen, bei denen beide Operanden
Ganzzahlen im Wertebereich von int sind (char, byte, short, int) auch in
diesem Wertebereich aus, d.h. das Ergebnis ist ein int. Sind beide
Operanden Ganzzahlen und mindestens einer ist ein long, wird die
Operation im Wertebereich von long ausgeführt, das Ergebnis ist ein long.
Wenn du nun schreibst:
byte a = 1;
byte b = 1;
byte c = a + b;
dann geht das nicht, denn das Ergebnis von a + b ist nach ob.g. Regel
ein int. Ein int einer byte-Variablen zuzuweisen aber ist potentiell mit
Informationsverlust verbunden, daher musst du dem Compiler zusichern,
dass du weisst, was du tust, d.h. du musst eine explizite Typumwandlung
durchführen:
byte c = (byte) (a + b);
Gruß,
Michael
On 15 Okt., 23:19, Stefan Kuhne <SK-Pri...@gmx.net> wrote:
>
> es geht auch:
> byte a = 0,b = 0,c = 0;
> c=(byte) (a+b);
Das funktioniert!
Aber kann mir mal jemand sagen, warum ich in der zweiten Zeile
nochmals c als Byte-Wert deklarieren muss, obwohl ich das schon in der
ersten zeile gemacht habe?
Gruß Torsten
> Aber kann mir mal jemand sagen, warum ich in der zweiten Zeile
> nochmals c als Byte-Wert deklarieren muss,
Musst Du nicht und tust Du nicht. Das "(byte)" bezieht sich auf das
nachstehende "(a+b)". Das (a+b) wird dadurch nach byte gewandelt und
"passt" erst dann in das byte c "rein".
Bye
Achim
Das heisst dass ein Java byte Werte zwischen -127 und 127 haben kann,
nicht 0 bis 255 (wie gewohnt).
Deswegen auch die Warnung, weil ein Addieren von zwei bytes einen Wert
über 127 ergeben kann.
(englische Seiten zum Thema)
http://www.darksleep.com/player/JavaAndUnsignedTypes.html
http://www.rgagnon.com/javadetails/java-0026.html
--
Sabine Dinis Blochberger
Op3racional
www.op3racional.eu
JFTR: -128 bis 127
Bye
Achim
Du mußt lernen, Fehlernachrichten besser zu interpretieren.
In der Tat kann Java mit bytes nicht rechnen, es rechnet mit int oder
long.
Somit "mängelt es" nicht "rum, daß es Integer haben will", sondern
weist lediglich darauf hin, daß es int (nicht Integer!) hat und dies
einem byte nicht ohne Weiteres zuweisen mag.
Dein laxer Fehlerbericht ist natürlich cool, und das Problem ist zum
Glück so trivial, daß Dir trotzdem Hilfe zuteil wurde. Es wäre aber
gut, wenn Du bei anderen Anfragen hier die originale(n)
Fehlermeldung(en) postest, statt Deiner Interpretation davon.
> Dein laxer Fehlerbericht ist natürlich cool, und das Problem ist zum
> Glück so trivial, daß Dir trotzdem Hilfe zuteil wurde. Es wäre aber
> gut, wenn Du bei anderen Anfragen hier die originale(n)
> Fehlermeldung(en) postest, statt Deiner Interpretation davon.
http://laeuftnicht.mpaap.de *g*
Gruß,
Michael
Michael Paap wrote:
> http://laeuftnicht.mpaap.de *g*
Etwas kürzer und etwas weniger persönlich (Stichwort Loser), und ich
würde die Lektüre dieser Seite auch gelegentlich weiterempfehlen. Bei
dem Schreibstil fürchte ich allerdings, dass der Betroffene spätestens
am Anfang des zweiten Absatzes aussteigt (nicht zu Unrecht, IMHO).
Ciao,
Ingo
> Etwas kürzer und etwas weniger persönlich (Stichwort Loser), und ich
> würde die Lektüre dieser Seite auch gelegentlich weiterempfehlen.
Persönlich? Unpersönlicher als per Link auf eine Webseite kann man
jemandem kaum mitteilen, was er falschmacht, oder? *g*
> Bei
> dem Schreibstil fürchte ich allerdings, dass der Betroffene spätestens
> am Anfang des zweiten Absatzes aussteigt (nicht zu Unrecht, IMHO).
Och.
Gruß,
Michael
Das ist schon okay, finde ich.
Wobei unser OP dies nicht verdient hat, IMHO. Immerhin hat er sich
eine Theorie gebildet, die so falsch gar nicht ist: "Java kann nicht
mit byte-Zahlen rechnen". Nur die Interpretation der Fehlernachricht
ging wohl in die Hose, darauf wollte ich ihn halt aufmerksam machen.
> > byte a = 64, b = 64, c;
> > c=(byte) (a+b);
[..warum casten?..]
> Weil Java dich vor dem Fehler bewahren will in einem großen Zahlenraum
> zu rechnen und dann das Ergebniss in einen viel kleineren Zahlenraum zu
> kippen mit dem evtl. Informationsverlust. Du definierst nicht c nochmal
> als byte, sondern du versicherst Java, dass du wirklich mit einem
> Mapping eines Integerergebniss auf einen byte Wertebereich zufrieden
> bist. Siehe auch
> http://de.wikipedia.org/wiki/Typumwandlung
Interessant finde ich, dass der OP davon sprach, dass der Java-Compiler
anmeckerte, dass ein "int" nicht in die byte-Variable passt, wobei er
IMO eher anmeckern sollte, dass da kein short rein passt, schließlich
kann x+y maximal ein short ergeben, wenn x und y ein byte sind.
Überhaupt eine Sache, die ich ganz schön behämmert finde:
public class Spastomat{
private byte x;
public void setX(final byte x){
this.x=x;
}
public static void main(final String[] args){
new Spastomat().setX(3);
}
}
Kompiliert nicht ("metod not applicable for the argument setX(int)),
weil ich nicht .setX((byte)3) geschrieben habe. Dabei ist mal völlig
klar, dass die Konstante "3" in ein byte rein passt.
Gruß,
-Wanja-
--
"Gewisse Schriftsteller sagen von ihren Werken immer: 'Mein Buch, mein
Kommentar, meine Geschichte'. [..] Es wäre besser, wenn sie sagten:
'unser Buch, unser Kommentar, unsere Geschichte'; wenn man bedenkt, dass
das Gute darin mehr von anderen ist als von ihnen." [Blaise Pascal]
> Interessant finde ich, dass der OP davon sprach, dass der Java-Compiler
> anmeckerte, dass ein "int" nicht in die byte-Variable passt, wobei er
> IMO eher anmeckern sollte, dass da kein short rein passt, schließlich
> kann x+y maximal ein short ergeben, wenn x und y ein byte sind.
Das ist dem Compiler spezifikationsgemäß halt völlig egal.
http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.2.2
> Kompiliert nicht ("metod not applicable for the argument setX(int)),
> weil ich nicht .setX((byte)3) geschrieben habe. Dabei ist mal völlig
> klar, dass die Konstante "3" in ein byte rein passt.
Sei froh, dass du schlauer bist als der Compiler. So lang das ab und zu
noch so ist, gibts Jobs für dich. ;-)
Gruß,
Michael
>Überhaupt eine Sache, die ich ganz schön behämmert finde:
>
>
>public class Spastomat{
> private byte x;
> public void setX(final byte x){
> this.x=x;
> }
>
> public static void main(final String[] args){
> new Spastomat().setX(3);
> }
>}
>
>Kompiliert nicht ("metod not applicable for the argument setX(int)),
>weil ich nicht .setX((byte)3) geschrieben habe. Dabei ist mal völlig
>klar, dass die Konstante "3" in ein byte rein passt.
Das weiß der Compiler auch, deshalb braucht es in der folgenden Variante
auch keine (byte)-Casts:
public class Gaykomat {
private static final byte A = 1;
private static final byte B = 2;
private static final byte C = A + B;
private byte x;
public void setX(final byte x) {
this.x = x;
}
public static void main(final String[] args) {
new Spastomat().setX(C);
}
}
Man könnte also scherzhaft sagen, der Compiler will dich zu einem
Programmierstil ohne magic numbers anhalten.
Tatsächlich ist es aber wesentlich komplexer und das Verhalten, dass du
kritisierst ist eigentlich die Lösung für ein tieferliegenderes Problem.
Erst mal: Warum funktionierte mit »public static final byte A,B,C« der
Code »A=1;B=2;C=A+B;« ohne Casts, wo doch dieser ganze Thread darum geht,
dass man für »byte a=1, b=2, c=a+b« plötzlich einen expliziten Cast braucht?
Antwort: §5.2 Assignment Conversion (JLS3rd) kennt für constant
expressions (§15.28) Ausnahmeregeln, die einen narrowing cast erlauben,
sofern zur Compile-Zeit garantiert werden kann, dass der Wert im engeren
Typ darstellbar ist.
Warum kann man eine solche Ausnahmeregel nicht auch für Methoden-Parameter
definieren?
Nun zunächst mal muss man sich darüber klar werden, dass beim Assignment
der Typ der Variablen an die zugewiesen wird fest steht, und daher die
Ausnahmeregel überprüfen kann ob eine Konstante im zulässigen Wertebereich
ist, oder nicht. Beim Methodenaufruf hingegen ist es genau umgekehrt: der
Typ der tatsächlichen Parameter-Werte steht fest und es wird dann erst
geschaut, ob eine Methode existiert, die dazu kompatible Parameter-Typen
hat. Und kompatibel heißt eben nur widening casts, boxing, oder
zuweisungskompatible Oberklassen. Würde hier zusätzlich die Sonderregel
der Zuweisungen gelten, wäre folgende Zweideutigkeit nicht mehr auflösbar*):
static void foo(byte b, int i) {}
static void foo(int i, byte b) {}
foo(1,1); // <- error ambiguous
foo((byte)1,(int)1); // <- error ambiguous
foo((byte)1,(byte)1); // <- error ambiguous
foo((int)1,(byte)1); // <- error ambiguous
alles egal, foo könnte nicht mehr mit Konstanten die als Byte darstellbar
sind aufgerufen werden können, man müsste sich jedesmal dieses Workarounds
bedienen:
byte tmp1 = 1;
int tmp2 = 1;
foo(tmp1, tmp2);
Und das ist jetzt nur das deutlichste Kuriosum das mir einfiel, wenn die
Ausnahmeregel auch bei Methodenaufrufen gelten würde, es gibt viele
wesentlich subtilere Auswirkungen, die regelmäßig zum Aufruf der falschen
Methoden oder unnötigen Zweideutigkeiten führen würden. Du kannst
natürlich versuchen dies alles durch weitere Ausnahmeregeln bei der
Methodenauswahl wieder zu reparieren, aber das führt schnell vom
hundertsten zum tausendsten und würde die Spec eher verschlechtern als
verbessern.
Das alles wird vermieden indem man gleich auf die Ausnahmeregel bei
Methoden verzichtet und ab und zu mal gezwungenermaßen einen expliziten
narrowing cast verwendet, wie eben in
setX((byte)3);
Was du also behämmert findest, ist eigentlich der elegante Kompromiss um
einen ganzen Sack an Problemen zu vermeiden.
cu
*) Dass dies wirklich so wäre, kann man schon heute Dank Boxing erahnen:
static void bar(int a, Integer b) {}
static void bar(Integer a, int b) {}
bar(1,1); // <- error ambiguous
bar(Integer.valueOf(1), Integer.valueOf(1)); // <- error ambiguous
Aber
bar(1,Integer.valueOf(1));
wäre eindeutig, jedoch nur, weil mit Autoboxing entsprechende zusätzlich
Ausnahmeregeln in die Methodenauswahl (§15.12.2) eingebaut wurden, welche
durch die Drei-Phasen-Suche ausgedrückt werden und Boxing erst in Phase 2
erlaubt wird, was aber nur durchlaufen wird, wenn ohne Boxing keine Method
zu finden war.
**) Ich sehe gerade die Java-Designer haben die Essenz meines obigen
Essays sogar in der JLS hinterlassen, siehe §5.3 Method Invocation
Conversion:
»Method invocation conversions specifically do not include the implicit
narrowing of integer constants which is part of assignment conversion
(§5.2). The designers of the Java programming language felt that including
these implicit narrowing conversions would add additional complexity to
the overloaded method matching resolution process (§15.12.2).
Thus, the example:
class Test {
static int m(byte a, int b) { return a+b; }
static int m(short a, short b) { return a-b; }
public static void main(String[] args) {
System.out.println(m(12, 2)); // compile-time error
}
}
causes a compile-time error because the integer literals 12 and 2 have
type int, so neither method m matches under the rules of (§15.12.2). A
language that included implicit narrowing of integer constants would need
additional rules to resolve cases like this example.«
Ingo Menger wrote:
>>>Etwas kürzer und etwas weniger persönlich (Stichwort Loser), und ich
>>>würde die Lektüre dieser Seite auch gelegentlich weiterempfehlen.
>>
>>Persönlich? Unpersönlicher als per Link auf eine Webseite kann man
>>jemandem kaum mitteilen, was er falschmacht, oder? *g*
>
> Das ist schon okay, finde ich.
> Wobei unser OP dies nicht verdient hat, IMHO. Immerhin hat er sich
> eine Theorie gebildet, die so falsch gar nicht ist: "Java kann nicht
> mit byte-Zahlen rechnen". Nur die Interpretation der Fehlernachricht
> ging wohl in die Hose, darauf wollte ich ihn halt aufmerksam machen.
Genau. Und da macht es wenig Sinn, ihm einen Text zu empfehlen, in dem
lang und breit erklärt wird, dass und warum er ein Loser sei - einfach
weil das Quatsch ist!
(Sicher ist es richtig, dass auf einige Leute der Text - und die
Bezeichnun Loser - zutrifft, aber die sind dann wiederum so bescheurt,
dass sie gar nicht erst anfangen, so einen Text zu lesen, weil sie ja
sowieso nie etwas falsch machen, Java zu buggy ist und sie doch einfach
nur eine Lösung für ihr offensichtliches Problem haben möchten, ohne
dafür erst aufwändig Ihr Problem beschreiben zu wollen. Aber das ist
meiner Erfahrung nach nur ein Bruchteil der Leute. Ergo: Weder dem einen
noch dem anderen kann man den Text empfehlen. @mpaap: Oder um es mit
deinen Worten zu sagen: Dein Text ist scheisse und Du bist ein Loser. ;-)
Ciao,
Ingo
wieder mal ein super Posting!
Man denkt, "Es könnte so einfach sein, isses aber nicht". Dein Posting
hat sehr schön beschrieben, warum das eigentlich doch die
einfachste/beste Lösung ist!
Danke,
Ingo
> Kompiliert nicht ("metod not applicable for the argument setX(int)),
> weil ich nicht .setX((byte)3) geschrieben habe. Dabei ist mal vllig
> klar, dass die Konstante "3" in ein byte rein passt.
Solche "Runtime-Rechnungen schon zur Compile-Zeit machen"
sind in Java halt nicht vorgesehen,
sondern die Regel heisst,
dass alle ganzzahligen Werte und Ausdruecke
standardmaessig den Typ in haben
und man abweichende Typen durch Casten oder durch Postfix angeben muss.
Als extreme Folge davon liefert bekanntlich
long oneYear = 365 * 24 * 60 * 60 * 1000;
leider nicht denselben Wert wie
long oneYear = 365 * 24 * 60 * 60 * 1000L;
--
> (Sicher ist es richtig, dass auf einige Leute der Text - und die
> Bezeichnun Loser - zutrifft, aber die sind dann wiederum so bescheurt,
> dass sie gar nicht erst anfangen, so einen Text zu lesen, weil sie ja
> sowieso nie etwas falsch machen, Java zu buggy ist und sie doch einfach
> nur eine Lösung für ihr offensichtliches Problem haben möchten, ohne
> dafür erst aufwändig Ihr Problem beschreiben zu wollen. Aber das ist
> meiner Erfahrung nach nur ein Bruchteil der Leute. Ergo: Weder dem einen
> noch dem anderen kann man den Text empfehlen. @mpaap: Oder um es mit
> deinen Worten zu sagen: Dein Text ist scheisse und Du bist ein Loser. ;-)
Wie du sachst. ;-)
Allerdings habe ich auf die Seite schon genügend positive Rückmeldungen
bekommen. Ich gebe aber zu, dass sie zu einem nicht unerheblichen Teil
nicht von den Leuten stammten, die man dorthin geschickt hatte, sondern
von denen, die jemanden hingeschickt hatten. Aber *deren* Bedürfnisse,
auf effiziente garstig zu sein, erfüllt zu haben, ist ja auch was wert. :-)
Gruß,
Michael
Michael Paap wrote:
> Allerdings habe ich auf die Seite schon genügend positive Rückmeldungen
> bekommen. Ich gebe aber zu, dass sie zu einem nicht unerheblichen Teil
> nicht von den Leuten stammten, die man dorthin geschickt hatte, sondern
> von denen, die jemanden hingeschickt hatten. Aber *deren* Bedürfnisse,
> auf effiziente garstig zu sein, erfüllt zu haben, ist ja auch was wert. :-)
Nun gut. Und wenn ich mal Zeit habe (;-) kann ich ja noch eine
"entschärfte" Version der Seite verfassen.
Im Übrigen war mein "Angriff" natürlich nicht persönlich gemeint,
sondern sollte nur meine Aussage an einem praktischen Beispiel
demonstrieren, das gut gemeinte Kritik oft nicht ankommt, wenn sie
beleidigend daher kommt. Also nix für ungut.
Ciao,
Ingo
... den Typ int ...
> und man abweichende Typen durch Casten oder durch Postfix angeben muss.
--
Man könnte es ja dem Leser überlassen, 2 Abschnitte, oben 2 Links:
'Fies und ehrlich'
'Wattebauschwerfen'
oder
'Fies und garstig'
Peter
Peter Büttner wrote:
> Man könnte es ja dem Leser überlassen, 2 Abschnitte, oben 2 Links:
> 'Fies und ehrlich'
> 'Wattebauschwerfen'
Vernünftig mit jemandem zu reden, ohne ihn von oben herab zu beleidigen
hat IMHO nichts mit "Wattebauschwerfen" zu tun, aber ansonsten ACK.
Ich denke allerdings, beim Verfassen solcher Texte sollte man sich
überlegen, was man möchte: Will man dem Leser etwas mitteilen und
Hinweise geben, was er sinnvollerweise an seinen Postings verbessern
kann, und damit die Zahl schlechter Postings eindämmen, oder will man
sich einfach nur den Frust von der Seele schreiben und ist gar nicht
daran interessiert, dass irgendjemand das liest.
Ciao,
Ingo
[..]
> static void foo(byte b, int i) {}
> static void foo(int i, byte b) {}
>
> foo(1,1); // <- error ambiguous
> foo((byte)1,(int)1); // <- error ambiguous
> foo((byte)1,(byte)1); // <- error ambiguous
> foo((int)1,(byte)1); // <- error ambiguous
Yo, das könnte der Compiler dann auch anmeckern und einen so zum
narrowing cast zwingen, wie gehabt, oder übersehe ich was?
Ist ja nicht so, als ob man nicht schon ähnliche Probleme genau so lösen
würde:
int[] victims = ...
List<Integer> data= ...
for(final int victim : victims){
data.remove((Object)victim);
}
> Und das ist jetzt nur das deutlichste Kuriosum das mir einfiel, wenn die
> Ausnahmeregel auch bei Methodenaufrufen gelten würde, es gibt viele
> wesentlich subtilere Auswirkungen, die regelmäßig zum Aufruf der falschen
> Methoden oder unnötigen Zweideutigkeiten führen würden. Du kannst
> natürlich versuchen dies alles durch weitere Ausnahmeregeln bei der
> Methodenauswahl wieder zu reparieren, aber das führt schnell vom
> hundertsten zum tausendsten und würde die Spec eher verschlechtern als
> verbessern.
Also ich denke nicht, dass es da viele Ausnahmeregelungen braucht.
Wenn ein Aufruf "Ambigous" ist, muss man halt nen downcast machen. Ich
kritisiere ja nur, dass es in eindeutigen Fällen blöd ist.
Ein anderes Beispiel:
byte a = getRandomByte();
byte b = getRandomByte();
short c = a+b;
sollte doch ohne cast Problemlos sein. Ist es aber nicht und ich finde
das, Entschuldigung, unnötig unhandlich.
Wohingegen mich der Compiler nicht davor schützt:
int x = Integer.MAX_VALUE;
int y = Integer.MAX_VALUE;
int z = x+y; //vorsicht übertrag!
Hier wäre IMO ein cast eher angebracht:
int x = getRandomInteger();
int y = getRandomInteger();
int z = (int)(x+y);
Während ein Cast hier IMO unnötig wäre:
int x = getRandomInteger();
int y = getRandomInteger();
long z = x+y;
> **) Ich sehe gerade die Java-Designer haben die Essenz meines obigen
> Essays sogar in der JLS hinterlassen, siehe §5.3 Method Invocation
> Conversion:
>
> »Method invocation conversions specifically do not include the implicit
> narrowing of integer constants which is part of assignment conversion
> (§5.2). The designers of the Java programming language felt that including
> these implicit narrowing conversions would add additional complexity to
> the overloaded method matching resolution process (§15.12.2).
>
> Thus, the example:
>
> class Test {
> static int m(byte a, int b) { return a+b; }
> static int m(short a, short b) { return a-b; }
> public static void main(String[] args) {
> System.out.println(m(12, 2)); // compile-time error
> }
> }
>
> causes a compile-time error because the integer literals 12 and 2 have
> type int, so neither method m matches under the rules of (§15.12.2). A
> language that included implicit narrowing of integer constants would need
> additional rules to resolve cases like this example.«
Gegen den Compiletime-error habe ich ja nix, wenn es wirklich
zweideutig, aber mein Beispiel war aber gar nicht zweideutig, sondern
eindeutig.
> > IMO eher anmeckern sollte, dass da kein short rein passt, schlielich
> > kann x+y maximal ein short ergeben, wenn x und y ein byte sind.
>
> > Kompiliert nicht ("metod not applicable for the argument setX(int)),
> > weil ich nicht .setX((byte)3) geschrieben habe. Dabei ist mal vllig
> > klar, dass die Konstante "3" in ein byte rein passt.
>
> Solche "Runtime-Rechnungen schon zur Compile-Zeit machen"
> sind in Java halt nicht vorgesehen,
> sondern die Regel heisst,
> dass alle ganzzahligen Werte und Ausdruecke
> standardmaessig den Typ in haben
> und man abweichende Typen durch Casten oder durch Postfix angeben muss.
Dass die JLS das so fest gelegt hat ist klar, sonst müsste man das nicht
casten, aber genau diesen Punkt in der JLS erlaube ich mir ja zu
kritisieren. ;-)
Nehmen wir den Fall hier:
byte a = 10;
byte b = 20;
short c = a+b;
Der Compiler kann hier eigentlich problemlos erkennen, dass byte+byte in
short passt und das entsprechend compilieren, man wird ja wohl
kritisieren dürfen, dass die JLS in diesem Punkt einfach mal schwach
ist? Ich schätze dass die Typen byte und short in der JLS eh ein wenig
stiefmütterlich behandelt wurden.
> wieder mal ein super Posting!
Dem kann ich nur zustimmen, Ralfs Postings überzeugen ohnehin fast immer
durch Qualität.
>
> Wohingegen mich der Compiler nicht davor schützt:
>
> int x = Integer.MAX_VALUE;
> int y = Integer.MAX_VALUE;
> int z = x+y; //vorsicht übertrag!
>
> Hier wäre IMO ein cast eher angebracht:
>
> int x = getRandomInteger();
> int y = getRandomInteger();
> int z = (int)(x+y);
>
> Während ein Cast hier IMO unnötig wäre:
>
> int x = getRandomInteger();
> int y = getRandomInteger();
> long z = x+y;
>
Guter Punkt, aber was ist dann mit
long a = Long.MAX_VALUE;
long b = a;
long c = a+b;
Wohin castest Du das, bzw. was ist es, wenn Du nicht castest?
Ich fände es schon verstörend, wenn nicht alle Ganzzahltypen gleich
behandelt würden.
Ingo Menger wrote:
>>Hier wäre IMO ein cast eher angebracht:
>>
>>int x = getRandomInteger();
>>int y = getRandomInteger();
>>int z = (int)(x+y);
>>
>>Während ein Cast hier IMO unnötig wäre:
>>
>>int x = getRandomInteger();
>>int y = getRandomInteger();
>>long z = x+y;
>
> Guter Punkt, aber was ist dann mit
> long a = Long.MAX_VALUE;
> long b = a;
> long c = a+b;
>
> Wohin castest Du das, bzw. was ist es, wenn Du nicht castest?
> Ich fände es schon verstörend, wenn nicht alle Ganzzahltypen gleich
> behandelt würden.
Nun, was Wanja (IMHO zu Recht) vermisst, ist eine ArithemticException,
die geworfen wird, wenn es einen Überlauf gibt. Wenn man das Ganze zu
Ende denkt (Ralf, korrigiere mich bitte! ;-), kommt man aber IMHO zu
folgendem Ergebnis:
(A) Entweder Java müsste bei Rechenoperationen als Ergebnis
grundsätzlich den nächst größeren Typen (*) verwenden, also z.B.
int+int=long. Dann ist tatsächlich sehr häufig (IMHO zu häufig, als dass
der Code lesbar bleibt) ein cast nötig, wie Wanjas Beispiel zeigt:
int x = getRandomInteger();
int y = getRandomInteger();
int z = (int)(x+y);
(*) Beim größten Typen (long) gäbe es die (mehr oder weniger - eher
weniger als mehr - unschöne ;-) Ausnahme, dass long+long=long ergibt.
(B) Oder aber, Java müsste in dem selben Typ weiterrechen (int+int=int)
und ggf. Exceptions werfen. Man könnte also schön schreiben:
int b=...,c=...;
int a=b+c;
Das hätte allerdings den unschönen Effekt, dass Literale das System
durcheinander würfeln würden:
long l=2147483648+2147483648;
Dies würde eine Exception werfen, da die Summe der beiden (int-Wertigen)
Literale zunächst auch ein int wäre (bevor es in die long-wertige
Variable l geschrieben würde).
Damit sind wir aber wieder bei Ralfs Punkt, dass man solche magic
numbers ohnehin vermeiden sollte. Daher wäre das vielleicht verkraftbar.
Behelfen könnte man sich mit expliziten Angaben:
long l=2147483648L+2147483648L;
(C) Alternativ könnte man sich für Variante A entscheiden, aber
zusätzlich implizite casts in die Sprache einbauen. Das ist allerdings
IMHO eine schlechte Alternative, da solche impliziten casts für
erhebliche Verwirrung bzw. Fallstricke sorgen können (ich denke da an
C++). Insbesondere wage ich kaum, zu beurteilen, wie das mit
Auto(un)boxing zusammenarbeitet bzw. sich damit vertrüge.
Wenn ich recht überlege, erscheint mir (B) die sinnvollste Variante zu
sein - und zudem die, die am besten mit dem bisherigen Java kompatibel wäre.
Ciao,
Ingo
Da ist er ja nicht der einzige.
Allerdings bedeutete dies, daß jetzt zusätzlich jede Rechenoperation +
- * Exceptions werfen könnten.
Das kann man nicht rückwärtskompatibel machen. Denn es gibt bestimmt
haufenweise Programme, die in dem Wissen und der durch die JLS
gedeckten Sicherheit geschrieben sind, daß die Integer-Arithmetik
*stillschweigend* modulo 2^32 bzw. 2^64 etc. funktioniert.
(Int.MAX_VALUE + Int.MAX_VALUE ist ja eigentlich kein richtiger
Überlauf, 0-Int.MIN_VALUE ist einer).
Der Weg wäre m.E. nur gangbar, wenn die Exception nur dann geworfen
würde, wenn danach gefragt wird, d.h. wenn die Operation innerhalb
eines
try {
... hier!
}
catch (IntegralOverflow) {
....
}
abläuft.
> (A) Entweder Java müsste bei Rechenoperationen als Ergebnis
> grundsätzlich den nächst größeren Typen (*) verwenden,
> (*) Beim größten Typen (long) gäbe es die (mehr oder weniger - eher
> weniger als mehr - unschöne ;-) Ausnahme, dass long+long=long ergibt.
Das fände ich *äußerst* unschön. (A) ginge nur, wenn es nicht wirklich
einen größten Typ gäbe. Das nun wieder hieße, daß man sich von den
Silikon-Typen entfernt, und etwa eine offene Hierarchie von
Integraltypen anbietet: byte, short, int, long, long long, long long
long, ...
So etwa:
long long long drei = 1L + 1L + 1L;
Dann könnte man den expliziten narrowing casts Exceptions werfen
lassen, wenn das Ergebnis nicht im Zielwertebereich ist:
(long) (1L + 1L +1L) ok
(long) (Long.MAX_VALUE << 2) ---> Exception
Diese könnte man dann auf Wunsch vermeiden: (long) (Long.MAX_VALUE &
irgendwas) funktioniert beispielsweise immer.
> Wenn ich recht überlege, erscheint mir (B) die sinnvollste Variante zu
> sein - und zudem die, die am besten mit dem bisherigen Java kompatibel wäre.
Gerade mit der Rückwärtskompatibilität hapert es da, wie ich oben
dargestellt habe.
Ich wäre dann eher dafür, BigIntegers zum Normalfall zu machen. Dies
würde gleich das Problem der unliebsamen OverflowExceptions (die ja
nur auf Unzulänglichkeiten des Silikons beruhen und nicht etwa
mathematische Gründe haben wie bei der Division durch 0) mit
beseitigen. Dies setzt natürlich eine effiziente Implementierung in
der JVM voraus und entsprechende Hilfe vom Compiler, so daß z.B.
integer x = Long.MAX_VALUE;
integer y = (x + 1) * (x - 1) ;
geht. Ich möchte mir gar nicht ausmalen, was man für eine ellenlange
Zeile schreiben müßte, um y in BigInteger-Methodenschreibweise zu
berechnen.
Die JVM/Compiler Unterstützung müßte so sein, daß integer sich wie ein
primitiver Typ anfühlt, aber dennoch ein Referenztyp ist. Diverse
Optimierungen müßten dazu führen, daß die integer Performance nicht
schlechter ist als wenn man dasselbe Programmstück mit int geschrieben
hätte, vorausgesetzt der Wertebereich bliebe in den Grenzen von int.
(Oder long).
Ingo Menger wrote:
>>Nun, was Wanja (IMHO zu Recht) vermisst, ist eine ArithemticException,
>>die geworfen wird, wenn es einen Überlauf gibt.
>
> Da ist er ja nicht der einzige.
> Allerdings bedeutete dies, daß jetzt zusätzlich jede Rechenoperation +
> - * Exceptions werfen könnten.
>
> Das kann man nicht rückwärtskompatibel machen. Denn es gibt bestimmt
> haufenweise Programme, die in dem Wissen und der durch die JLS
> gedeckten Sicherheit geschrieben sind, daß die Integer-Arithmetik
> *stillschweigend* modulo 2^32 bzw. 2^64 etc. funktioniert.
> (Int.MAX_VALUE + Int.MAX_VALUE ist ja eigentlich kein richtiger
> Überlauf, ...).
Wieso soll das kein "richtiger Überlauf" sein?
Im Übrigen ist die Frage, was man als "kompatibel" bezeichnet. Wenn ein
Bug gefixt wird ist das ja nie rückwärtskompatibel, weil irgendjemand
sich darauf verlassen könnte, dass der Bug da ist, und plötzlich ist er
es nicht mehr. Das kann aber IMHO kein Argument sein.
> Der Weg wäre m.E. nur gangbar, wenn die Exception nur dann geworfen
> würde, wenn danach gefragt wird, d.h. wenn die Operation innerhalb
> eines
> try {
> ... hier!
> }
> catch (IntegralOverflow) {
> ....
> }
>
> abläuft.
Ne, das kann es auch nicht sein, weil die Frage, ob eine Exception
geworfen wird, nicht von dem Kontext abhängen darf. Es stellten sich
dann nämlich zwangsläufig weitere Fragen (die die Inkonsistenz deines
Vorschlages aufzeigen):
Was ist hiermit:
int add(int a, int b) throws IntegralOverlow {
return a+b;
}
Da die Addition laut deiner Forderung - wenn sie geinlined wird - eine
Exception wirft, muss sie das natürlich auch tun, wenn sie nicht
geinlined wird.
Anderereits...: IntegralOverflow sollte ja sicher eine
UncheckedException sein, oder? Dann muss sie ja gar nicht deklariert
werden! Und wenn man sie nicht deklariert, soll sie dann (nach deiner
Logik) trotzdem geworfen werden?
Das ist IMHO nicht zu Ende gedacht!
>>(A) Entweder Java müsste bei Rechenoperationen als Ergebnis
>>grundsätzlich den nächst größeren Typen (*) verwenden,
>>(*) Beim größten Typen (long) gäbe es die (mehr oder weniger - eher
>>weniger als mehr - unschöne ;-) Ausnahme, dass long+long=long ergibt.
>
> Das fände ich *äußerst* unschön.
Sag ich ja!
> (A) ginge nur, wenn es nicht wirklich
> einen größten Typ gäbe. Das nun wieder hieße, daß man sich von den
> Silikon-Typen
Was sind Silikon-Typen? Extra große? ;-)
> entfernt, und etwa eine offene Hierarchie von
> Integraltypen anbietet: byte, short, int, long, long long, long long
> long, ...
Neenee, wie ich schon schrieb: Bei long müsste es eine Ausnahme geben.
Aber wie gesagt: Wir sind uns einig, dass diese Variante nicht schön ist.
>>Wenn ich recht überlege, erscheint mir (B) die sinnvollste Variante zu
>>sein - und zudem die, die am besten mit dem bisherigen Java kompatibel wäre.
>
> Gerade mit der Rückwärtskompatibilität hapert es da, wie ich oben
> dargestellt habe.
IMHO unproblematisch, wie ich oben schrieb. Aber darüber lißere sich -
wie so oft - lange und ergebnislos streiten.
> Ich wäre dann eher dafür, BigIntegers zum Normalfall zu machen. Dies
> würde gleich das Problem der unliebsamen OverflowExceptions (die ja
> nur auf Unzulänglichkeiten des Silikons beruhen und nicht etwa
> mathematische Gründe haben wie bei der Division durch 0) mit
> beseitigen. Dies setzt natürlich eine effiziente Implementierung in
> der JVM voraus und entsprechende Hilfe vom Compiler, so daß z.B.
>
> integer x = Long.MAX_VALUE;
> integer y = (x + 1) * (x - 1) ;
>
> geht. Ich möchte mir gar nicht ausmalen, was man für eine ellenlange
> Zeile schreiben müßte, um y in BigInteger-Methodenschreibweise zu
> berechnen.
ACK! Ich bin mir aber nicht sicher, ob man BigIntegers genauso schnell
hinbekommt wie die nativen Typen. Daher möchte ich (bis zum Beweis des
Gegenteils) nicht auf diese verzichten.
Trotzdem spricht natürlich nichts gegen (sondern vieles für) eine
syntaktisch bessere Unterstützung für BigIntegers.
Ciao,
Ingo
Euch ist schon klar, dass ihr wenn ihr
byte op byte => short
short op short => int
etc.
fordert, von vornherein so Dinge wie
--a
i++
hc+=x;
f*=n;
etc.
ausschließt?
Für mich ist diese Wanjasche Idee, allein schon deshalb indiskutabel.
Und IntegerOverflows gibt es bei Java nunmal grundsätzlich nicht. (Und
wenn ich mich nicht irre war Aspach- äähh - Apple-Basic, die letzte
Programmiersprache, die ich in der Praxis eingesetzt habe, die sowas
hatte.) AFAIK verzichten alle aktuellen Sprachen aus Performancegründen
auf praktisch alle ArithmetikExceptions außer evtl. der
DivisionByZeroException.
BTW, die einzige wirkliche Alternative innerhalb eines statischen
Typsystems zu Javas Vorgehen wäre:
byte op byte => byte
short op short => short
etc.
Und das würde nun auf Java übertragen bedeuten, dass man knappe 50 weitere
ByteCode-Befehle bräuchte um die Lücken in Tabelle 3.2 (
http://java.sun.com/docs/books/jvms/second_edition/html/Overview.doc.html#7565
) zu schließen, die bei einer solchen Arithmetik nicht mehr automatisch
auf int-Arithmetik zurückfallen.
Was sich an der Java-Sprache also zeigt, ist in Wirklichkeit, das Bemühen
den Java-ByteCode einfacher zu halten, schließlich wollte man ja als
Designsziel von Java die Portabilität des ByteCode-Interpreters (und evtl.
-JIT-Compilers) hoch halten.
cu
Weil nichts überläuft. Mal auf Byte-Ebene betrachtet: 0x7f + 0x7f =
0xfe
Wo läuft da was über?
Da Du (bzw. die VM) Zahlen wo das vordere bit 1 ist als negative
*interpretierst* ist es natürlich logisch gesehen ein "Überlauf",
okay.
> Im Übrigen ist die Frage, was man als "kompatibel" bezeichnet. Wenn ein
> Bug gefixt wird ist das ja nie rückwärtskompatibel, weil irgendjemand
> sich darauf verlassen könnte, dass der Bug da ist, und plötzlich ist er
> es nicht mehr. Das kann aber IMHO kein Argument sein.
Sorry, Du argumentierst hier (unwissentlich?) ganz schlecht. Was
spezifiziert ist, kann kein Bug sein. Und wie Integer-Arithmetik
abläuft, ist spezifiziert. (Bitte zwing mich jetzt nicht, die Schrift
zu konsultieren und zu zitieren.)
Daher kann man Programmen, die gemäß JLS 3.0 korrekt rechnen und
laufen, nicht plötzlich eine ganz andere Semantik unterschieben. Dort
könnte z.B. stehen:
// make sure the JVM uses 2's complement integer arithmetic without
overflow as specified.
assert Int.MAX_VALUE+1 == Int.MIN_VALUE;
> Ne, das kann es auch nicht sein, weil die Frage, ob eine Exception
> geworfen wird, nicht von dem Kontext abhängen darf.
Eben. Daher kommen Exceptions für bisher exceptionlose Abläufe nicht
in Frage.
> Es stellten sich
> dann nämlich zwangsläufig weitere Fragen (die die Inkonsistenz deines
> Vorschlages aufzeigen):
Das ist mir schon klar, daß der inkonsistent ist.
> Das ist IMHO nicht zu Ende gedacht!
Richtig. Es soll nur aufzeigen, in welche Schwulitäten man käme, wenn
man integer overflow möglichst rückwärtskompatibel einführen wollte.
>
> >>(A) Entweder Java müsste bei Rechenoperationen als Ergebnis
> >>grundsätzlich den nächst größeren Typen (*) verwenden,
> >>(*) Beim größten Typen (long) gäbe es die (mehr oder weniger - eher
> >>weniger als mehr - unschöne ;-) Ausnahme, dass long+long=long ergibt.
>
> > Das fände ich *äußerst* unschön.
>
> Sag ich ja!
Sagtest Du nicht "eher weniger"
> > (A) ginge nur, wenn es nicht wirklich
> > einen größten Typ gäbe. Das nun wieder hieße, daß man sich von den
> > Silikon-Typen
>
> Was sind Silikon-Typen? Extra große? ;-)
Nein. Datentypen, die in der Hardware eingemeißelt sind.
Oder was glaubst Du, warum Int und Long so funktionieren, wie sie
funktionieren?
> > Gerade mit der Rückwärtskompatibilität hapert es da, wie ich oben
> > dargestellt habe.
>
> IMHO unproblematisch, wie ich oben schrieb. Aber darüber lißere sich -
> wie so oft - lange und ergebnislos streiten.
Ich verstehe nicht, wie ein so gewitzter Mensch wie Du die
fundamentalen Schwierigkeiten, die sich da ergeben, nicht sehen kann
oder will.
Siehe mein oben geschriebenes assert - sicher so wie es da steht an
den Haaren herbeigezogen - aber es gibt mit Sicherheit Unmengen an
Code, der mit 2-er-Complement-Bitfiddleien (und sei es auf der byte-
Ebene) gespickt ist.
> ACK! Ich bin mir aber nicht sicher, ob man BigIntegers genauso schnell
> hinbekommt wie die nativen Typen. Daher möchte ich (bis zum Beweis des
> Gegenteils) nicht auf diese verzichten.
Ich auch nicht. Und dafür bin ich aber bereit, auch deren
Unvollkommenheiten in Kauf zu nehmen.
> Und IntegerOverflows gibt es bei Java nunmal grundsätzlich nicht. (Und
> wenn ich mich nicht irre war Aspach- äähh - Apple-Basic, die letzte
> Programmiersprache, die ich in der Praxis eingesetzt habe, die sowas
> hatte.) AFAIK verzichten alle aktuellen Sprachen aus Performancegründen
> auf praktisch alle ArithmetikExceptions außer evtl. der
> DivisionByZeroException.
Und dies entspricht wohl auch dem Verhalten der meisten Hardware-CPUs.
Sprich, bei Division durch 0 gibts einen Trap, Signal, oder was auch
immer, bei Überlauf wird evtl. das carry-bit gesetzt. CPUs die
zwischen "logischen" und "arithmetischen" Operationen unterscheiden,
setzen u.U. noch bei letzteren das Sign-Flag.
Wollte man IntegerOverflow hieße das auf den meisten CPUs prüfen des
Carry- und des Sign-Flags nach *jeder* Operation, bei CPUs ohne
entsprechende Flags wird es noch schlimmer, es ist ja keineswegs ganz
kostenlos vorherzusagen, ob diese Flags gesetzt würden, wenn es sie
gäbe.
>Wollte man IntegerOverflow hieße das auf den meisten CPUs prüfen des
>Carry- und des Sign-Flags nach jeder Operation, bei CPUs ohne
>entsprechende Flags wird es noch schlimmer, es ist ja keineswegs ganz
>kostenlos vorherzusagen, ob diese Flags gesetzt würden, wenn es sie
>gäbe.
Genau, und dann möchte ich mal diejenigen hören, die sich jetzt schon über
Bounds Checks an Arrays aufregen, wenn die wissen, dass bei jeder
Rechenoperation auch nochmal so ein aufwändiger Check gemacht wird.
cu
Sehe ich ein.
War auch nur so ein eingeworfener Gedanke.
Ändert aber nichts an meiner grundsäzlichen Kritik, dass der Compiler
IMO auf einen Cast verzichten könnte, wenn zur Compile-Zeit klar ist,
dass das Ergebnis in das Ziel passt.
> BTW, die einzige wirkliche Alternative innerhalb eines statischen
> Typsystems zu Javas Vorgehen wäre:
>
> byte op byte => byte
> short op short => short
> etc.
>
> Und das würde nun auf Java übertragen bedeuten, dass man knappe 50 weitere
> ByteCode-Befehle bräuchte um die Lücken in Tabelle 3.2 (
> http://java.sun.com/docs/books/jvms/second_edition/html/Overview.doc.html#7565
> ) zu schließen, die bei einer solchen Arithmetik nicht mehr automatisch
> auf int-Arithmetik zurückfallen.
Sekunde, ich redete hier noch nicht von der Bytecode, sondern von der
Source-Ebene. Ich finde es, egal was im Bytecode draus wird, störend,
dass ich bei "byte op byte -> short" einen narrowing cast einsetzen
muss.
> Was sich an der Java-Sprache also zeigt, ist in Wirklichkeit, das Bemühen
> den Java-ByteCode einfacher zu halten, schließlich wollte man ja als
> Designsziel von Java die Portabilität des ByteCode-Interpreters (und evtl.
> -JIT-Compilers) hoch halten.
Ja, an sowas dachte ich auch. Schlägt sich IMO zu sehr im Source nieder.
> Wollte man IntegerOverflow hieße das auf den meisten CPUs prüfen des
> Carry- und des Sign-Flags nach *jeder* Operation, bei CPUs ohne
> entsprechende Flags wird es noch schlimmer, es ist ja keineswegs ganz
> kostenlos vorherzusagen, ob diese Flags gesetzt würden, wenn es sie
> gäbe.
Interessant wäre eine Möglichkeit das Flag aus Java raus zu testen.
Ich wüsste allerdings nicht, wie man das elegant in die Sprache rein
bekäme.
Ingo Menger wrote:
>>>(Int.MAX_VALUE + Int.MAX_VALUE ist ja eigentlich kein richtiger
>>>Überlauf, ...).
>>
>>Wieso soll das kein "richtiger Überlauf" sein?
>
> Weil nichts überläuft. Mal auf Byte-Ebene betrachtet: 0x7f + 0x7f =
> 0xfe
> Wo läuft da was über?
> Da Du (bzw. die VM) Zahlen wo das vordere bit 1 ist als negative
> *interpretierst* ist es natürlich logisch gesehen ein "Überlauf",
> okay.
Natürlich geht es mir um die *logische* Semantik. Mit Bits möchte ich ja
gerade nicht hantieren müssen, dafür ist ja die JVM da.
>>Im Übrigen ist die Frage, was man als "kompatibel" bezeichnet. Wenn ein
>>Bug gefixt wird ist das ja nie rückwärtskompatibel, weil irgendjemand
>>sich darauf verlassen könnte, dass der Bug da ist, und plötzlich ist er
>>es nicht mehr. Das kann aber IMHO kein Argument sein.
>
> Sorry, Du argumentierst hier (unwissentlich?) ganz schlecht. Was
> spezifiziert ist, kann kein Bug sein. Und wie Integer-Arithmetik
> abläuft, ist spezifiziert.
Das war natürlich nicht unwissentlich. Vielleicht hätte ich noch einen
Smiley setzen sollen. Ich finde einfach die Spec an der Stelle buggy.
>>Ne, das kann es auch nicht sein, weil die Frage, ob eine Exception
>>geworfen wird, nicht von dem Kontext abhängen darf.
>
> Eben. Daher kommen Exceptions für bisher exceptionlose Abläufe nicht
> in Frage.
Jetzt schmeisst Du aber zwei Dinge durcheinander! Das Argument trifft
natürlich nur auf einen der beiden Sachverhalte zu (was nichts daran
ändert, dass beide Sachverhalte nichtsdestotrotz richtig sind, insofern
sind wir uns einig).
>>Es stellten sich
>>dann nämlich zwangsläufig weitere Fragen (die die Inkonsistenz deines
>>Vorschlages aufzeigen):
>
> Das ist mir schon klar, daß der inkonsistent ist....
> Es soll nur aufzeigen, in welche Schwulitäten man käme, wenn
> man integer overflow möglichst rückwärtskompatibel einführen wollte.
Achso...
>>>>(A) Entweder Java müsste bei Rechenoperationen als Ergebnis
>>>>grundsätzlich den nächst größeren Typen (*) verwenden,
>>>>(*) Beim größten Typen (long) gäbe es die (mehr oder weniger - eher
>>>>weniger als mehr - unschöne ;-) Ausnahme, dass long+long=long ergibt.
>>
>>>Das fände ich *äußerst* unschön.
>>
>>Sag ich ja!
>
> Sagtest Du nicht "eher weniger"
Ja, weil ich meine Idee noch besser fände. Meine Aussage, dass ich die
Spec an der Stelle für buggy halte (wenngleich natürlich für "bewusst so
entworfen"), ist durchaus ernsthaft gemeint.
>> > (A) ginge nur, wenn es nicht wirklich
>>
>>>einen größten Typ gäbe. Das nun wieder hieße, daß man sich von den
>>>Silikon-Typen
>>
>>Was sind Silikon-Typen? Extra große? ;-)
>
> Nein. Datentypen, die in der Hardware eingemeißelt sind.
> Oder was glaubst Du, warum Int und Long so funktionieren, wie sie
> funktionieren?
Das ist mir schon klar. Ich kenne die halt nur unter der Bezeichnung
"native Typen", und die Bezeichnung "Silikon-Typen" war mir neu. War ein
offensichtlich misslungenes Wortspiel, welches ich jetzt nicht erläutern
möchte ;-)
> Ich verstehe nicht, wie ein so gewitzter Mensch wie Du die
> fundamentalen Schwierigkeiten, die sich da ergeben, nicht sehen kann
> oder will.
Ich kann mir eben nur schwer vorstellen, dass es wirklich so viele
Java-Programme gibt, die sich darauf *verlassen*, dass int+int ein
(mathematisch) falsches Ergebnis liefern kann:
> Siehe mein oben geschriebenes assert - sicher so wie es da steht an
> den Haaren herbeigezogen - aber es gibt mit Sicherheit Unmengen an
> Code, der mit 2-er-Complement-Bitfiddleien (und sei es auf der byte-
> Ebene) gespickt ist.
Nun, die Operatoen & und | sollen natürlich kein Overflow erzeugen
(können sie IMHO rein logisch ja auch gar nicht). Und die "Optimierung",
dass man zwei disjunkte Bitfelder anstelle mit | auch mit + verknüpfen
kann, halte ich nicht für ausschlaggebend.
Ciao,
Ingo
Wanja Gayk wrote:
> Ändert aber nichts an meiner grundsäzlichen Kritik, dass der Compiler
> IMO auf einen Cast verzichten könnte, wenn zur Compile-Zeit klar ist,
> dass das Ergebnis in das Ziel passt.
> ... Ich finde es, egal was im Bytecode draus wird, störend,
> dass ich bei "byte op byte -> short" einen narrowing cast einsetzen
> muss.
Dir ist aber auch klar (?), dass Du damit ganz neue Doppeldeutigkeiten
schaffst, weswegen deine *grundsätzliche* Idee IMHO indiskutabel ist:
byte b1=...
byte b2=...
foo(b1+b2);
void foo(byte b) {...}
void foo(short b) {...}
Der einzig saubere Ansatz sind IMHO ArithemthicExceptions (die aber an
deinem "Problem" nichts ändern).
Ciao,
Ingo
Klar - es ist eine Performance-Frage. Aber wenn ich bedenke, dass manche
Leute bereit sind, BigInteger als default int-Typ zu verwenden, sollte
der eine Bit-Test pro Rechenoperation da IMHO kein Problem sein. ;-)
Meinetwegen wäre auch Wanjas neue Idee (das Overflow-Bit abfragbar zu
machen) ein Kompromiss, aber ich bezweifele, dass das performanter geht:
(1) was ist mit zusammengesetzten Ausdrücken: a+b+c
(2) wg. Multithreading müsste das Bit ja Thread-weise gecached werden
Ciao,
Ingo
> Ich kann mir eben nur schwer vorstellen, dass es wirklich so viele
> Java-Programme gibt, die sich darauf *verlassen*, dass int+int ein
> (mathematisch) falsches Ergebnis liefern kann:
Noch nicht mal das stimmt so.
Modulo 2^32/2^64 rechnen die Ints/Longs ja durchaus korrekt.
Man kann streiten, ob es ein Fehler war, Java so zu spezifizieren, wie
es ist (nämlich, IMHO, mit dem offenkundigen Ziel, die meisten
Arithmetik-Bytecodes in der JVM einfach und vor allem schnell mit dem
entsprechenden Maschinenbefehl implementieren zu können).
Ich finde das allerdings müßig, da es nun mindestens 10 Jahre zu spät
ist, daran was zu ändern.
>Ich kann mir eben nur schwer vorstellen, dass es wirklich so viele
>Java-Programme gibt, die sich darauf verlassen, dass int+int ein
>(mathematisch) falsches Ergebnis liefern kann:
Ähem, wann hast du denn das letzte Mal hashCode implementiert ohne dich
genau darauf zu verlassen?
hashCode ist doch das Paradebeispiel für Berechnungen, die sich auf das
implizite Modulo 2^32 verlassen. Und hashCode wird in fast jedem Programm
gebraucht (und sei implizit in der API-Implementierung). So mit wird genau
umgekehrt ein Schuh daraus: Ich kann mir nur schwer vorstellen, dass Java
Programme gibt, die sich _nicht_ darauf verlassen, dass int+int ein
(mathematisch) falsches Ergebnis liefern kann.
cu
PS: Deine Flag-Speicherung zwischen Threads aus dem anderen Post ist auch
so ein Lapsus. Was bitte wird denn heute gemacht? Heute wird doch auch
beim Threadwechsel der gesamte Registersatz wiederhergestellt inkl.
Statusregister ergo Flags.
Ja. Das ist natürlich schwierig, da diese Bits ständig herumflippen.
Selbst wenn man sowas wie Integer.isCarrySet() hätte, wäre es wohl
nicht ganz einfach zu garantieren, daß dies das Carry-Bit der letzten
(in diesem Thread) in Java-Code ausgeführten Integer-Rechenoperation
ist. Ohne diese Garantie wäre es schlicht nutzlos.
Deshalb, meine Idee nochmal modifiziert:
try {
// code
} catch (CarryBitException cb) { ...
} catch (...BitException sb) { ...
}
Man könnte die Ausnahme spezifizieren, daß der arithmetische Code in
dem entsprechenden try-block mit mindestens einer der Exceptions (aber
keiner anderen Exception) vom Compiler so übersetzt wird, daß ein
Overflow eine Exception wirft. (Entsprechende JVM-Bytecodes wären
natürlich nötig.)
Irgendwie natürlich auch unschön, aber immerhin praktisch.
Existierender Code bliebe so, wie er ist. Neuer Code bliebe auch
"normal". Nur die Freaks, die Überläufe erkennen wollen, schreiben
ihren Code in so ein try-Statement.
>>Ich kann mir eben nur schwer vorstellen, dass es wirklich so viele
>>Java-Programme gibt, die sich darauf *verlassen*, dass int+int ein
>>(mathematisch) falsches Ergebnis liefern kann:
>
> Noch nicht mal das stimmt so.
> Modulo 2^32/2^64 rechnen die Ints/Longs ja durchaus korrekt.
Nun gut. Dann kann ich mir eben nur schwer vorstellen, dass es wirklich
so viele Java-Programme gibt, die sich darauf *verlassen*, dass int+int
nur bezüglich Modulo 2^32/2^64 ein richtiges Ergebnis liefert.
> Man kann streiten, ob es ein Fehler war, Java so zu spezifizieren, wie
> es ist (nämlich, IMHO, mit dem offenkundigen Ziel, die meisten
> Arithmetik-Bytecodes in der JVM einfach und vor allem schnell mit dem
> entsprechenden Maschinenbefehl implementieren zu können).
> Ich finde das allerdings müßig, da es nun mindestens 10 Jahre zu spät
> ist, daran was zu ändern.
Meine Antwort bezog sich auf deine Frage, "... wie ... Du die
fundamentalen Schwierigkeiten, die sich da ergeben, nicht sehen kann
oder will."
Soll heissen: Ich finde es keineswegs zu spät, die Spec (an dieser
Stelle) zu ändern, weil ich da in der Tat keine Schwierigkeit -
geschweige denn eine fundamentale - sehe.
Über das Performance-Argument kann man natürlich immernoch lange
streiten, aber das wäre in der Tat müßig. Zu diesem Argument würde ich
persönlich auch keine Position beziehen wollen, solange ich nicht
entsprechende Benchmarks kenne.
Ciao,
Ingo
Ralf Ullrich wrote:
>> Ich kann mir eben nur schwer vorstellen, dass es wirklich so viele
>> Java-Programme gibt, die sich darauf verlassen, dass int+int ein
>> (mathematisch) falsches Ergebnis liefern kann:
>
> Ähem, wann hast du denn das letzte Mal hashCode implementiert ohne dich
> genau darauf zu verlassen?
Sch...! HashCodes - gutes Argument, daran hatte ich in der Tat nicht
gedacht. OK, ich korrigiere mich hiermit und behaupte ab jetzt das
Gegenteil. Was schert mich mein Geschwätz von gestern - oder einer
Minute? ;-) Ernsthaft: Du hast natürlich völlig recht! Das war ein
ziemlicher "Flapsus" von mir. Nur durch Koffeein-Mangel ist der wohl
nicht zu erklären geschweige denn zu entschuldigen, oder?
> PS: Deine Flag-Speicherung zwischen Threads aus dem anderen Post ist
> auch so ein Lapsus. Was bitte wird denn heute gemacht? Heute wird doch
> auch beim Threadwechsel der gesamte Registersatz wiederhergestellt inkl.
> Statusregister ergo Flags.
Weiss denn ich, wie moderne Prozessoren aufgebaut sind, und wo/wie das
das Overflow-Bit gespeichert wird? Meine Assembler-Zeiten sind seit ca.
15 Jahren vorbei und bezogen sich auf den 68000er - ohne extra FPU,
IIRC. Meine (hoffentlich nicht zu missverständliche) Aussage war ja: Es
*könnte* sein, dass das auf verschiedenen Prozessoren
Performance-Nachteile mit sich bringt (also *zusätzliche*
Performance-Nachteile zu denen, die es bei einem Thread-Wechsel ohnehin
schon gibt.) Wenn *Du* sagst, dass es bei den "üblichen", modernen
Prozessoren ausgeschlossen ist, dass es solche Nachteile gibt, dann
glaube ich dir das. (Aber auch nur, weil Du es bist.) "Offensichtlich"
finde ich das aber keineswegs - an der Stelle verbitte ich mir also den
"Lapsus" (wir sind ja hier in keines Assembler-NG).
Ciao,
Ingo
PS: Hattest Du nicht vor etlichen Posts angekündigt, dich aus diesem
Thread raushalten zu wollen? ;-)
> > PS: Deine Flag-Speicherung zwischen Threads aus dem anderen Post ist
> > auch so ein [CENSORED:]. Was bitte wird denn heute gemacht? Heute wird doch
> > auch beim Threadwechsel der gesamte Registersatz wiederhergestellt inkl.
> > Statusregister ergo Flags.
>
> Weiss denn ich, wie moderne Prozessoren aufgebaut sind, und wo/wie das
> das Overflow-Bit gespeichert wird?
Übrigens fiel mir gerade nach meiner Antwort auf Wanja auf, daß die
Kenntnis der CPU-Flags einem Java-Programmierer nichts, aber auch
gleich (fast) gar nichts nützen würde.
z.B. 2 + (-1) --> carry
2 + (-3) ---> kein carry
0x7fffffff + 0x7fffffff --> kein carry
Kurz gesagt ist das Carry-Flag nur gut, um *unsigned* overflow
festzustellen, bei der signed-Arithmetik von Java kommt man auch mit
carry-Flag um eine Analyse der Operanden nicht herum.
Ähnlich das SignBit, es taugt eigentlich nur dazu, um festzustellen,
ob das Ergebnis "negativ" ist. Ob es mathematisch korrekt negativ ist,
kann man wieder nur durch Analyse der Operanden herausfinden.
Um also OverflowExceptions werfen zu können, müßte nach jeder int-
Operation in der VM eine Analyse ablaufen, die (meiner Schätzung nach)
nicht weniger als 10 Maschinenbefehle bräuchte, inkl. schwer
prognostizierbarer Sprünge. Ob dann native int-Arithmetik noch
wesentlich schneller bliebe als gut optimierte BigIntegers wage ich zu
bezweifeln. Im Grunde muß das gleiche ablaufen, nur muß bei BigInt für
jedes Teilergebnis ein neues Objekt gemacht werden. Dies könnte aber,
glaube ich, extremst optimiert werden.
> > Ändert aber nichts an meiner grundsäzlichen Kritik, dass der Compiler
> > IMO auf einen Cast verzichten könnte, wenn zur Compile-Zeit klar ist,
> > dass das Ergebnis in das Ziel passt.
> > ... Ich finde es, egal was im Bytecode draus wird, störend,
> > dass ich bei "byte op byte -> short" einen narrowing cast einsetzen
> > muss.
>
> Dir ist aber auch klar (?), dass Du damit ganz neue Doppeldeutigkeiten
> schaffst, weswegen deine *grundsätzliche* Idee IMHO indiskutabel ist:
>
> byte b1=...
> byte b2=...
> foo(b1+b2);
>
> void foo(byte b) {...}
> void foo(short b) {...}
Wo ist das Problem?
In diesem Fall kann sich der Compiler ja beschweren und den cast
fordern, der sonst ohnehin nötig wäre, aber der halt in allen
eindeutigen Situationen fehlen kann.
> > > Wollte man IntegerOverflow hieße das auf den meisten CPUs prüfen des
> > > Carry- und des Sign-Flags nach *jeder* Operation, bei CPUs ohne
> > > entsprechende Flags wird es noch schlimmer, es ist ja keineswegs ganz
> > > kostenlos vorherzusagen, ob diese Flags gesetzt würden, wenn es sie
> > > gäbe.
> >
> > Interessant wäre eine Möglichkeit das Flag aus Java raus zu testen.
> > Ich wüsste allerdings nicht, wie man das elegant in die Sprache rein
> > bekäme.
>
> Ja. Das ist natürlich schwierig, da diese Bits ständig herumflippen.
Nicht deswegen. Der Prozessor muss den Zustand des Carry-Bit nach einer
Operation ja kennen.
> Selbst wenn man sowas wie Integer.isCarrySet() hätte, wäre es wohl
> nicht ganz einfach zu garantieren, daß dies das Carry-Bit der letzten
> (in diesem Thread) in Java-Code ausgeführten Integer-Rechenoperation
> ist. Ohne diese Garantie wäre es schlicht nutzlos.
> Deshalb, meine Idee nochmal modifiziert:
>
> try {
> // code
> } catch (CarryBitException cb) { ...
> } catch (...BitException sb) { ...
> }
Wäre IMO ein Misbrauch von Exceptions.
Die einzige Möglichkeit, die mir einfiele, das irgendwie elegant in die
Sprache zu integrieren, wäre durch eine (IMO) etwas unschöne Erweiterung
der Syntax:
boolean carry;
int c = a + b > carry;
>>Weiss denn ich, wie moderne Prozessoren aufgebaut sind, und wo/wie das
>>das Overflow-Bit gespeichert wird?
>
>Übrigens fiel mir gerade nach meiner Antwort auf Wanja auf, daß die
>Kenntnis der CPU-Flags einem Java-Programmierer nichts, aber auch
>gleich (fast) gar nichts nützen würde.
Da es mir (fast) wie Ingo geht, und meine Assembler-Zeiten lange vorbei
sind, weiß ich auch nicht wie das bei modernen Prozessoren ist, aber mein
68000er Handbook von Gerry Kane, sagt mir dass der Prozessor schon vor 26
Jahren die Ergebnisse von Lade- und Rechen-Operationen mit *vier*
Statusflags bewertet hat:
C arry
o V erflow
Z ero
N egative
Mit diesen vier Flags sind alle denkbaren Prüfungen machbar, beim 68000er
sind 16 implementiert:
CC Carry Clear !C
CS Carry Set C
EQ Equal Z
F False 0
GE Greater than or Equal (N & V) | (!N & !V)
GT Greater Than (N & V & Z) | (!N & !V & !Z)
HI HIgher than !C & !Z
LE Less than or Equal Z | (N & V) | (!N & !V)
LS Lower than or Same C | Z
LT Less Than (N & !V) | (!N & V)
MI MInus (i.e., negative) N
NE Not Equal !Z
PL PLus (i.e., positive) !N
T True 1
VC oVerflow Clear !V
VS oVerflow Set V
Wobei CC auch als HS (Higher than or Same) interpretiert werden kann und
gleichermaßen CS auch als LO (LOwer than), sodass sich zwei
Vergleichsgruppen bilden lassen:
vorzeichenbehaftet: GE GT LE LT und
vorzeichenlos: HS/CC HI LS LO/CS
Eine kurze Googlesuche findet mir diese Wiki-Seite [1] für Intels x86
Architektur und zeigt, dass es dort genau die gleichen Flags gibt:
Bit#0 CF Carry Flag
Bit#6 ZF Zero Flag
Bit#7 SF Sign Flag
Bit#11 OF Overflow Flag
Ich denke, wenn man vergleichbare Flags für eine Java VM einführen wollte,
wird man auf dieselben 4 Flags kommen, und zwar zum einen weil sie wohl
von allen Prozessoren angeboten werden, und zum anderen weil genauere
Überlegungen vermutlich zeigen, dass jedes weitere denkbare Flag sich als
Kombination dieser vier darstellen lässt, und diese vier sich nicht auf
weniger als vier unabhängige Flags reduzieren lassen. (Jedenfalls kann ich
mich dunkel erinnern sowas vor zwanzig Jahren mal gelernt zu haben.)
Dass also ein Flag alleine nichts nützen würde ist klar, Ingo. Und deine
Analyse ist soweit auch korrekt, nur dass du die Existenz des
Overflow-Flags übersehen hast, bei dem dann in Kombination mit den anderen
Flags und dem Wissen über die Art der letzten Operation eben doch einfach
ein Overflow festgestellt werden könnte.
cu
Wanja Gayk wrote:
>>Dir ist aber auch klar (?), dass Du damit ganz neue Doppeldeutigkeiten
>>schaffst, weswegen deine *grundsätzliche* Idee IMHO indiskutabel ist:
>>
>>byte b1=...
>>byte b2=...
>>foo(b1+b2);
>>
>>void foo(byte b) {...}
>>void foo(int b) {...}
>
> [Tippfehler korrigiert]
>
> Wo ist das Problem?
> In diesem Fall kann sich der Compiler ja beschweren und den cast
> fordern, der sonst ohnehin nötig wäre, aber der halt in allen
> eindeutigen Situationen fehlen kann.
Das Problem ist, dass nach momentaner Spec kein cast nötig ist. Nach
deiner neuen Spec wäre ein cast nötig. Deine neue Spec erfordert also
zusätzliche casts. An anderer Stelle sparst Du zwar (wie Du im
vorletzten Posting dieses Sub-Threads gezeigt hast) andere casts ein,
aber ich wage zu bezweifeln, dass in summa ein Gewinn dabei heraus kommt.
Das ist aber nur ein Nachteil. Der andere Nachteil ist, dass es unnötig
kompliziert würde:
final static byte a=1;
byte b=1;
foo(1+1); // ruft foo(byte) auf
foo(255+1); // ruft foo(int) auf
foo(a+a); // ruft foo(byte) auf
foo(b+b); // ruft foo(int) auf
Grausig!
Ciao,
Ingo
>Das ist aber nur ein Nachteil. Der andere Nachteil ist, dass es unnötig
>kompliziert würde:
Vor allem wenn man bedenkt, dass im Laufe der Entwicklung sich vielleicht
die Typen einiger Variablen ändern, so hat man vielleicht am Anfang:
void foo(short x) {...}
byte a,b;
short c;
foo(a+b); // ruft foo(short)
// foo(a+c); // ruft foo(int) daher:
foo((short)(a+c));
Und nun ändert sich b auf short und c auf byte:
foo(a+b); // erzeugt nun einen Fehler: foo(short) n/a for foo(int)
foo((short)(a+c)); // erzeugt nun eine Warnung: unecessary cast
Dagegen beim Status quo bleibt der Code vom Typ von a,b,c unabhängig:
foo((short)(a+b));
foo((short)(a+c));
Es kommt zwar auch in bestimmten Fällen zu den beiden oben gezeigten
Fehlern bzw. Warnungen, aber viel seltener als mit Wanjas Idee.
cu
Nach momentaner Spec gibt es auch keine Zweideutigkeit, weil immer ein
narrowing cast nötig ist.