Почему моя проверка бита Java на байт нестабильна?

#java #byte #bit

Вопрос:

У меня был небольшой полезный метод для проверки того, установлен ли бит в байте:

 public static boolean get(byte b, int bit) {  return (b amp; (1 lt;lt; bit)) gt; 0; }  

В течение 4 лет эта штука, казалось, не доставляла мне никаких хлопот. Теперь в моем коде байтовый член класса инициализируется с помощью DataInput.ReadByte() ввода данных, переданного конструктору класса (класс является внутренним классом). У меня есть еще один фрагмент кода, который делает точно то же самое и никогда не доставлял никаких проблем. Все происходит примерно так:

 public class A {   public class B {   public byte x;   public B(DataInput in) throws IOException {  x = in.readByte();  if (Utility.get(x, 7)) {  y = in.readInt();  ...  etc.  }  }  } }  

Теперь с новым кодом метод утилиты в основном возвращает true, но иногда и false для тех же аргументов (при печати это b=-124 и bit=7). Я проверил, что никакие другие потоки не обращаются к переменной-члену byte, которая передается как » b » (в любом случае это невозможно, так как все это происходит в конструкторе класса). Также, если я распечатаю переданное значение для » b » в методе утилиты (например), проблема исчезнет. Похоже, это связано со временем (вот почему я подумал о потоках).

После прочтения я понял, что (b amp; 0b10000000), приведенное в качестве байта, равно -128, но (b amp; 0b10000000), приведенное в качестве int, равно 128. Тем не менее, я бы ожидал, что служебный метод вернет тот же результат для тех же » b » и «bit». Теперь, после изменения моего метода утилиты на:

 public static boolean get(byte b, int bit) {  return (b amp; (1 lt;lt; bit)) != 0; }  

Кажется, все работает нормально. Мой вопрос, почему первая версия дает разные результаты для одних и тех же аргументов? Для b=-124 и bit=7, по-видимому, оценивается (b amp; (1 lt;?? в зависимости от??? Я запускаю открытый JDK 12.0.2.

EDIT1: I don’t have any code I can simply paste here that does what I state (yet) but here’s some code to demonstrate what I mean by the sign of an expression changing depending on casting to byte or int (This isn’t an issue for me, I understand that the Java byte primitive is signed etc.):

 byte b = -124;  System.out.println((int)(b amp; 0b10000000));  System.out.println((byte)(b amp; 0b10000000));  System.out.println(((int)(b amp; 0b10000000) gt; 0));  System.out.println(((byte)(b amp; 0b10000000) gt; 0));  

Result:

 128 -128 true false  

I’m just considering that for some reason, that part of the expression in my utility method is sometimes cast to byte and sometimes to int (because that would explain the different results).

EDIT2: I have something that can reproduce the issue. There are 3 class files (ByteCheckTest.java, Foo.java and Util.java) all in the bytechecktest package:

First ByteCheckTest.java:

 package bytechecktest;  import java.io.File; import java.net.URISyntaxException; import java.util.Random;  public class ByteCheckTest {   public static void main(String[] args) {  for (int n = 0; n lt; 100; n  ) {    File file = new File(applicationDirectory(ByteCheckTest.class).getAbsolutePath()   File.separator   "output.dat");    //Instantiate first Foo and initialize it with 100 random Bars.  Foo f1 = new Foo();  Random rnd = new Random(1);  for (int i = 0; i lt; 100; i  ) {  f1.addBar((byte)(-128   rnd.nextInt(256)));  }  //Save the first Foo.  f1.save(file);    //Load a new Foo instance from the saved data and compare it to the original Foo (f1).  Foo.load(file, f1, n);    }  System.out.println("SUCCESS");  }   //This is just to obtain the directory this app is in, to put the output.dat file in.  static File applicationDirectory(Classlt;?gt; main_class) {  File dir = null;  try {  dir = new File(main_class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile();  } catch (URISyntaxException e) {  e.printStackTrace();  }  return dir;  }  }  

Then Foo.java

 package bytechecktest;  import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List;  class Foo {   class Bar {    byte value;  Bar sub;    Bar(byte value) {  this.value = value;  if (isSet()) {  sub = new Bar((byte)0);  } else {  sub = null;  }  }    Bar(DataInput in) throws IOException {  value = in.readByte();  if (isSet()) {  sub = new Bar(in);  } else {  sub = null;  }  }    void write(DataOutput out) throws IOException {  out.writeByte(value);  if (isSet()) {  sub.write(out);  }  }    boolean isSet() {  return Util.get(value, 7);  }    void compare(String pre, Bar orig) {  if (value != orig.value) {  throw new RuntimeException("FAILED: "   pre   " "   value   "="   orig.value);  }  if (isSet()) {  sub.compare(pre   ".sub", orig.sub);  }  }  }   Listlt;Bargt; bars;    Foo() {  bars = new ArrayListlt;gt;();  }    Foo(DataInput in, Foo orig, int run) throws IOException {  this();  int n = in.readInt();  if (orig.bars.size() != n) {  throw new RuntimeException("FAILED: run"   run   " Loading Foo bars.size="   n   " orig.bars.size="   orig.bars.size());  }  for (int i = 0; i lt; n; i  ) {  Bar b = new Bar(in);  b.compare("run"   run   ".bar["   i   "]", orig.bars.get(i));  bars.add(b);  }  }   void write(DataOutput out) throws IOException {  out.writeInt(bars.size());  for (Bar b : bars) {  b.write(out);  }  }    void save(File f) {  try {  if (f.exists()) {  f.delete();  }  DataOutputStream out = null;  try {  out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f)));  write(out);  } finally {  if (out != null) {  out.close();  }  }   } catch (IOException e) {  throw new RuntimeException(e);  }  }    static Foo load(File f, Foo orig, int run) {  Foo foo;  try {  DataInputStream in = null;  try {  in = new DataInputStream(new BufferedInputStream(new FileInputStream(f)));  foo = new Foo(in, orig, run);  } finally {  if (in != null) {  in.close();  }  }  } catch (IOException e) {  throw new RuntimeException(e);  }  return foo;  }    void addBar(byte v) {  bars.add(new Bar(v));  }  }  

Последнее, Util.java:

 package bytechecktest;  public class Util {   public static boolean get(byte b, int bit) {  return (b amp; (1 lt;lt; bit)) gt; 0; //Changing 'gt;' here to '!=' makes the test succeed.  }   }  

ByteCheckTest.java выполняет один и тот же тест для одних и тех же значений 100 раз. Для меня, в OpenJDK 12.0.2, это не удается при некотором запуске теста (например, у меня не сразу первый запуск, а итерация 55).

Но если я изменю оператор «больше, чем» в Util.get(byte, int) for != , то тест будет выполняться нормально все 100 раз.

Комментарии:

1. предложение: return ((1 lt;lt; bit) amp; b) != 0

2. @paulsm4 почему это лучше?

3. Можете ли вы добавить полный пример кода, который возвращает недетерминированный результат? Мне очень трудно поверить, что выполнение одного и того же кода дважды без внешнего ввода или случайных чисел приведет к другому результату. Если добавление инструкции печати изменяет результат, пожалуйста, вставьте два разных примера кода.

4. @Pieter12345: Я полностью согласен — нам нужен полный пример. Утверждение ОП о том, что его выражение «недетерминировано», является чепухой. Я просто предположил, что проверять «gt;0» глупо … если все, что ему нужно знать, — это «0» или «ненулевое значение» … и если возврат к отрицательному int может быть фактором.

5. Кстати, 0x10000000-это шестнадцатеричный литерал. Вы имели в виду 0b10000000

Ответ №1:

Я почти уверен, что проблема связана с ошибкой в OpenJDK 12.0.2. Я протестировал вышеуказанную программу в jre1.8.0.241, openJDK12.0.2 и openJDK17.0.1 (используя 1000 итераций), и запуск ее только на openJDK12.0.2 завершается неудачей (на 55-й итерации), если я заменю»! = «на»gt;». Я не могу найти, какая это могла быть ошибка.

Ответ №2:

Эти два метода возвращают одни и те же значения.

 public static boolean get1(byte b, int bit) {  return (b amp; (1 lt;lt; bit)) gt; 0; }  public static boolean get2(byte b, int bit) {  return (b amp; (1 lt;lt; bit)) != 0; }  public static void main(String[] args) throws IOException {  for (int i = Byte.MIN_VALUE; i lt;= Byte.MAX_VALUE;   i) {  byte b = (byte)i;  for (int k = 0; k lt;= 8;   k)  if (get1(b, k) != get2(b, k))  System.out.println(b   " "   k);  } }  

выход:

 (nothing)  

Существует разница, когда значение bit равно 31.

Комментарии:

1. Спасибо за усилия! Я добавил код выше, который для меня воспроизводит проблему.