Ist meine Codierungsfunktion richtig?


#1

Moin, Sorry das ihr am 1. Weihnachtsfeiertag meinerseits gestört werdet…

    double intsToDouble(int i1, int i2) {
        return ((i1 << 1) + i2) / 13.0;
    }

    int[] doubleToInts(double d) {
        int r = (int) Math.round(d * 13);
        int i1 = r >> 1;
        int i2 = r % 2;
        return new int[]{i1, i2};
    }

Meine Frage wäre einfach ob diese Funktionen äquivalent sind also die eine Umkehrfunktion der anderen wäre oder ob was falsch ist?

Und die andere Frage wäre, wie man diese Funktionen schrittweise aufstellen kann und deren Korrektheit beweist?

Und letzte Frage ist :smile: Wie vermeidet man das new int[]… also dass man extra ein neues Array anlegen muss? Aus anderen Programmiersprachen kenne ich das, dass man zum Besenstiel einfach ein 2-Tupel zurückgeben kann. :confused:


#2

Das ist keine direkte Umkehrfunktion. Eine notwendige Einschränkung wäre, dass i2 zwischen 0 und 1 liegen muss.
Darüber hinaus würde ich gefühlsmäßig sagen, dass Math.round (rundet zur “näheren” Ganzzahl) anderen Regeln folgt als die Integerdivision (rundet immer ab). Klarheit schafft hier entweder genaueres Nachdenken oder ein einfacher Test.

Das formal zu beweisen ist nicht ganz trivial, aber machbar (bei diesem simplen Beispiel). Aus der Uni ist hierzu das Hoare-Kalkül vom Namen her hängen geblieben.

Das new int vermeidet man sinnvollerweise, indem man eine separate Klasse (ein Tupel) definiert. Dies kann man entweder generisch machen (oder eine der unzähligen Tupel-Bibliotheken nutzen - @Landei hilft hier sicher gern weiter), oder man erstellt sich eine domänenspezifische Tupel-Klasse (würde ich so machen), aus der zumindest in den JavaDocs die Semantik der Klasse hervorgeht.


#3

vavr.io, Guava, functionaljava.org

Java 8…10 hat auch ein Pair, wenn auch etwas versteckt (und mit dem Nachteil, dass JavaFX seit Java 11 eigenständig ist, das Pair also wieder verschwindet): javafx.util.Pair


#4

Ich würde mal sagen, dass diese Funktionen definitiv nicht äquivalent sind. Bei der bitweisen Verschiebung nach Links geht möglicherweise ein gesetztes Bit verloren, welches bei der bitweisen Verschiebung nach rechts nicht wieder kommt.
Bis auf eine Kleinigkeit wäre

double intsToDouble(int a, int b) {
  long t = (long) a<<32 + b;
  return Double.longBitsToDouble(t);
}

int[] doubleToInts(double d) {
  long t = Double.doubleToLongBits(a);
  int[] rc = new int[2];
  rc[0] = (int) (t >> 32);
  rc[1 = (int) t;
  return rc;
}

bis auf wenige Ausnahmen (a=0x7FF00000, a=0xFFF00000 und a=0x7FF80000) um Einiges äquivalenter.


#5

Hm. Es gibt zwar immer wieder mal einen Fall, wo ein

class IntPair {
    int i0;
    int i1;
}

als lokale Klasse (d.h. entweder als private static innere Klasse, oder sogar lokal in einer Methode) praktisch sein kann. Aber man kann sich mal die Optionen ansehen:

  • Eigene IntPair-Klasse. Ist die immutable? Sollte sie wohl sein. (Dann verhindert sie aber das new nicht). Ist die public? Muss sie sein, wenn man die Methode von außen benutzen will. Ist die geJavaDoct und geUnitTestet? Herrjeh…
  • Dependency zu einer Tuple-Lib. Ähm. Sich eine Dependency ans Bein zu binden, um die Erstellung eines int[2] zu vermeiden, geht IMHO in die falsche Richtung. Zumal alle Tuple-Libs, die ich kenne oder bisher gesehen habe, nur Referenztypen unterstützen, und keine Primitiven. (Eine “Ausnahme” ist da meine grandiose “ND”-Lib, die z.B. ein https://github.com/javagl/ND/blob/master/nd-tuples/src/main/java/de/javagl/nd/tuples/i/IntTuple.java enthält, aber eben für “hochdimensionale” Tupel
  • Man könnte https://docs.oracle.com/javase/8/docs/api/java/awt/Point.html verwenden. Das ist eine Klasse der Standard-API, die einfach nur zwei int-Werte enthält. Das ist das, was dem gesuchten wohl am nächsten kommt.
  • Man erstellt den albernen int[]-Array, wo ist das Problem?

Ja, das Problem ist, dass wenn man das “oft” macht, trotz guter Escape-Analysis ein Haufen Garbage entstehen kann. In solchen Fällen verwende ich oft dieses Muster

// Fills the result array and returns it, or creates a 
// new one if the given array is null
public static int[] computeSomething(double x, int result[]) {
    int localResult[] = result;
    if (localResult == null) localResult = new int[2];
    ....
    return localResult;
}

Damit kann man dann recht bequem sowas machen wie

int result[] = computeSomething(123.456, null);
System.out.println("Gotit: "+result[0]+" and "+result[1]);

oder, wenn es darauf ankommt:

int result[]  = null;
for (int i=0; i<severalMillion; i++) {
    result = computeSomething(value, result);
    System.out.println("Gotit: "+result[0]+" and "+result[1]);
}

so dass dort im Endeffekt recht leicht (und “transparent”) nur ein Array erstellt und immer wieder verwendet wird.

Bei einem int[2] müsste man sich aber schon “Mühe” geben, damit sich das lohnt. Angelehnt ist dieses Muster eher an die Fälle, wo es um “komplexere” Ziel-Objekte geht, wie etwa https://docs.oracle.com/javase/8/docs/api/java/awt/image/BufferedImageOp.html#filter-java.awt.image.BufferedImage-java.awt.image.BufferedImage-


#6

@Spacerat und @Marco13 lagen nicht daneben,

denn:

for (int i = 0; i < 6; i++) {
    for (int j = 0; j < 2; j++) {
        System.out.println(i + " " + j + " " + Arrays.toString(fc2.doubleToInts(fc2.intsToDouble(i, j) )));
    }
}

, kommt zu:

0 0 [0, 0]
0 1 [0, 1]
1 0 [1, 0]
1 1 [1, 1]
2 0 [2, 0]
2 1 [2, 1]
3 0 [3, 0]
3 1 [3, 1]
4 0 [4, 0]
4 1 [4, 1]
5 0 [5, 0]
5 1 [5, 1]

Aber abweichen tut:

for (int i = 0; i < 6; i++) {
    for (int j = 0; j < 2; j++) {
        System.out.println(i + " " + j + " " + Arrays.toString(fc2.doubleToInts(fc2.intsToDouble(i, j) + Math.random() - 0.5 )));
    }
}

, zum Besentiel:

0 0 [2, 1]
0 1 [-1, -1]
1 0 [0, 1]
1 1 [2, 1]
2 0 [2, 1]
2 1 [1, 0]
3 0 [5, 0]
3 1 [2, 1]
4 0 [6, 0]
4 1 [7, 1]
5 0 [3, 0]
5 1 [8, 1]

< 1 liegt nicht mehr im Toleranzbereich… Welch Wunder.


Ich probiere nochmal etwas… Bis dann!


#7

#8

CB du hast eine Sperre. Eigentlich solltest du ganz genau wissen, was das bedeutet. Das Thema ist ab hier dicht und deine Sperre verlängert sich.