2016-06-01 21 views
5

Scusate in anticipo per la lunga domanda ma tutto dovrebbe essere semplice e chiaro cosa sta succedendo, grazie per dare un'occhiata. Si noti che questo non è in realtà codice, solo pseudo codice per comprendere l'implementazione dell'applicazione.Ampliamento da byte a Int (bug Marshmallow)

Problema

Byte di non stanno ampliando al vero valore numerico. Nota: level = -1 rappresenta un gioco che non è stato avviato. level == 24 rappresenta la fine del gioco.

Classe 1

private byte level = -1; 
private byte stage = 0; 

@NonNull private final Stages[][] stages = new Stages[25][13]; 

public byte getLevel() { 
    return level; 
} 

public void nextLevel() { 
// expect: 0 
    level++; 

// stages[ 128][ 0] (ArrayIndexOutOfBounds) 
    stages[level][stage] = new Stage(); 
} 

Classe 2 estende Classe 1

@NonNull private final byte[][] values = new byte[25][4]; 

public byte getScore (final byte level, final byte player) { 
    //  values[ 255][  2] (ArrayIndexOutOfBounds) 
    return values[level][player]; 
} 

// expecting: -1 >= 0 (false) 
// runtime: 255 >= 0 (true) 
if (Class1.getLevel() >= 0) 
    getScore(Class1.getLevel(), 2); 

binario

8 bit (byte)

-1 == 1111 1111 
-128 == 1000 0000 
127 == 0111 1111 

32 bit (intero)

-1 == 1111 1111 1111 1111 1111 1111 1111 1111 
127 == 0000 0000 0000 0000 0000 0000 0111 1111 
128 == 0000 0000 0000 0000 0000 0000 1000 0000 
255 == 0000 0000 0000 0000 0000 0000 1111 1111 

cosa ha funzionato

Utilizzando la classe wrapper

public Byte level = -1; 

domanda

Capisco il problema è la rappresentazione binaria del numero viene utilizzata direttamente quando si allarga da byte a int. Il mio numero va letteralmente da 8 bit 1111 1111 a 32 bit 0000 0000 0000 0000 0000 0000 1111 1111. La mia domanda è: perché Java (o perché non è in questa situazione/ambiente) converte il numero nel vero valore numerico quando si allarga invece di riempire gli zeri con la rappresentazione binaria grezza.

Questo sembra accadere solo a numeri negativi, presumo perché i numeri positivi hanno la stessa rappresentazione bit prima e dopo l'allargamento.

Perché il mio numero non passa da 8 bit 1111 1111 a 32 bit 1111 1111 1111 1111 1111 1111 1111 1111? Perché l'incremento postfisso crea un valore di 128 ..? C'è una soluzione migliore a questo problema oltre a quella con cui sono attualmente bloccato?

Preferisco non utilizzare questa soluzione senza conoscere il problema di sottolineatura perché il problema può tranquillamente (eseguire senza errori) interrompere l'algoritmo delle applicazioni; Quindi è con molto apprezzamento se qualcuno può spiegarmi questo problema per favore.

Grazie, Jay

Corrente di funzionamento Ambiente

JDK 1.8.076

OS X El Capitan

Android Studio 2.2 Preview 2

buildToolsVersion '23 .0.3'

classpath 'com.android.tools.build:gradle:2.2.0-alpha2'

Emulator Nexus 6P API 23 x86

Conclusione

Sono stato in grado di limitare il problema a dispositivi esclusivamente Android 23 (Marshmallow). Ho segnalato il bug a Google Inc. Grazie per l'aiuto di tutti, darò un avvertimento agli utenti di Android 23 (Marshmallow) poiché il bug non compare in Android N, Android 22 e versioni precedenti.

+0

Penso che la ragione forse non sia la rappresentazione di bit, ma il fatto che si usi il byte in un indice di array. Questo potrebbe cambiare le regole di conversione. Ho provato a trovare fatti nelle specifiche del linguaggio java ma non ho ancora trovato un suggerimento. Puoi provare ad assegnare i valori dell'indice a int locali prima di utilizzarli nell'indice dell'array? Cambia sth? – thst

+0

Ho trovato un suggerimento. Ancora bisogno di capire chiaramente: nel contesto dell'indice dell'array, viene applicata la conversione unaria. L'esempio mostra che 'byte b = -1; int i = -b; -> i = 0! '. – thst

+0

Ciao Jay, ho provato il tuo codice ora a casa e ha funzionato bene. Ho usato JDK 1.8.044 e proverò .77 e .91 (attualmente in download). Non ho potuto scaricare .76 per Windows però. Hai controllato l'aggiornamento di Java? Questo succede solo nell'emulatore Android? – thst

risposta

3

perché non tradurre i byte firmati in non firmati?

public static int signedByteToInt(byte b) { 
    return b & 0xFF; 
} 

per assicurarsi per voi qui è campioni di rappresentazione in byte con segno:

-3 - 11111101 
-2 - 11111110 
-1 - 11111111 
0 - 00000000 
1 - 00000001 
2 - 00000010 
3 - 00000011 

e quando si utilizza byte come int che Java in alcun modo rappresentare questo byte come firmato uno così da 11111111 (-1) Otterrai 11111111 11111111 11111111 11111111 (-1) e non vedo qui alcun problema.

Basta tenere a memoria:

from -1 to -128 is from 11111111 to 10000000 
and from 0 to 127 is from 00000000 to 01111111 

e fare la conversione corretta quando lo si utilizza come int.

così sotto zero rappresentazioni sono come contatore all'indietro per mezzo

e dal modo in cui questo non è solo in java)

qui:

if (Class1.getLevel() >= 0) 

confrontate byte con integer, cercare di marca:

if (Class1.getLevel() >= (byte)0) 

e sentirsi felici :)

+1

Anche se questo non risponde alla domanda, mi piace. Affidarti alla tua memoria di conversioni esoteriche non è il modo più produttivo per spendere le tue energie, ma piuttosto forzare il problema. Tuttavia, la sua domanda è valida - ne vale la pena per capire cosa sta succedendo. –

+0

risposta aggiornata) –

+0

Gli esempi che fornisci dovrebbero funzionare tutti senza casting esplicito. L'OP sta usando il fatto che -1 + 1 è 0. Questo non è un problema di byte, accadrà anche con int. Il problema è che, nel suo caso, il byte -1 è interpretato come int 127 e il risultato è improvvisamente 128. Se chiamate 'signedToUnsigned (level) + 1' sarete esattamente nella situazione descritta dall'op. – thst

2

Jay, ho visto il tuo codice e ho controllato la documentazione per il linguaggio Java.

Per me, il comportamento descritto è il seguente:

private byte level = -1; 
private byte stage = 0; 

si inizializza byte a -1 e 0. Più tardi, si chiama nextLevel() e si aspettano di init stage[0][0], ma, invece, si riceve un eccezione "fuori dai limiti". Quindi, il primo livello non viene mai raggiunto.

public void nextLevel() { 
// expect: 0 
    level++; 

// stages[ 128][ 0] (ArrayIndexOutOfBounds) 
    stages[level][stage] = new Stage(); 
} 

Se il codice non ha mostrato nel campione non creare effetti collaterali che producono la situazione, il seguente codice è un esempio di lavoro, che dovrebbe essere sufficiente per esporre il problema:

package com.bytemagic; 

import com.sun.istack.internal.NotNull; 

/** 
* Created by thst on 01.06.2016. 
*/ 
public class ByteMagic { 

    private class Stage { 
    } 

    private byte level = -1; 
    private byte stage = 0; 

    @NotNull 
    private final Stage[][] stages = new Stage[25][13]; 

    public byte getLevel() { 
     return level; 
    } 

    public void nextLevel() { 
    // expect: 0 
     level++; 

    // stages[ 128][ 0] (ArrayIndexOutOfBounds) 
     stages[level][stage] = new Stage(); 
    } 


    public static void main(String... args) { 
     ByteMagic me = new ByteMagic(); 

     me.nextLevel(); 
     System.out.println(Integer.toHexString(me.getLevel())); 
    } 
} 

Ho provato questo codice con JDK8u66, JDK8u77. Questo non esporre alcun problema. Il byte level verrà correttamente aumentato a 0 e stages viene inizializzato correttamente.

Ti piacerebbe provare a eseguire quel codice sul tuo computer e configurare? Questo codice espone il problema?

Dopo tutto: level non può contenere 128, può contenere -128, ma non è quello che descrivi.

level++ eseguirà tutti i tipi di conversioni divertenti. Secondo i documenti, per calcolare l'incremento, 1, level vengono convertiti in numero intero con un adeguato ampliamento se necessario (quindi 0xff -> 0xffffffff), quindi viene aggiunto 1, con conseguente 0x0 (int). Questo viene quindi ridotto a 0x0 (byte) e scritto in level. (https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.2)

L'accesso all'array utilizza una regola di conversione leggermente diversa. L'accesso all'array richiede che l'indice sia di tipo intero. La conversione da byte a int utilizza regole di promozione unaria numerica, che possono avere un effetto di conversione interessante quando si utilizza un unus meno, ma per il tuo caso, con un valore di byte di 0, non accadrà nulla di spettacolare o imprevisto.

(Per riferimento: https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.6.1 e https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html#jls-10.4 e https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.10.3)

Quindi, non posso riprodurre l'errore con il codice di campioni di dati, e il mio sospetto va nella direzione che questo è o qualche effetto collaterale di codice non riesco a vedere oppure è un bug nel JDK che usi o nell'ambiente (Dalvik VM).