Запрос SQLite, где предложение с числами с плавающей запятой терпит неудачу?

Я помещаю поплавок в базу данных SQLite на базе Android, например:

private static final String DATABASE_CREATE = 
    "create table " + DATABASE_TABLE + " ("  
                    + KEY_ID + " integer primary key autoincrement, "
                    + KEY_FLOAT  + " REAL, " + ...
...

content.put(KEY_FLOAT, 37.3f);
db.insert(DATABASE_TABLE, null, content);

Когда я запрашиваю таблицу:

Cursor cursor = db.query(false, DATABASE_TABLE, 
      new String[] { KEY_ID, KEY_FLOAT, ... },
      KEY_LATITUDE + "=37.3",
      null, null, null, null, null);

курсор возвращается пустым. Если я изменю значение поплавка на 37,0, он будет работать правильно, возвращая запись в курсоре.

Я протестировал это на некоторой длине, изменив спецификацию столбца базы данных с «REAL» на «float» и т. д. Пока у меня есть какая-либо дробная часть после десятичной точки, курсор возвращается пустым. Что здесь происходит?

Заранее спасибо!


person DJC    schedule 17.07.2010    source источник
comment
рекомендуемое чтение: floating-point-gui.de   -  person Michael Borgwardt    schedule 17.07.2010


Ответы (5)


37.0 точно представим в двоичном формате с плавающей запятой, поэтому у вас нет обычных проблем. 37,3 нет точного представления — ваш запрос не соответствует, потому что значение в базе данных не совсем равно 37,3.

Опции:

  • Используйте десятичный числовой тип, если такая вещь существует в SQLite (я проверю через минуту)
  • Заставьте ваш запрос использовать допуск, например. «ШИРОТА > 37,29 И ШИРОТА ‹ 37,31». Боюсь, сравнение двоичных значений с плавающей запятой для точного равенства просто напрашивается на неприятности.

РЕДАКТИРОВАТЬ: я только что проверил документы, и похоже, что SQLite не поддерживает десятичные не - целочисленные типы. Я оставлю это предложение выше, так как оно будет актуально для той же проблемы в других базах данных.

РЕДАКТИРОВАТЬ: решение Паскаля по эффективному преобразованию вашего собственного числового типа во многих случаях является хорошим. Определите, какой уровень точности вы хотите, и умножьте/делите соответственно... поэтому, если вам нужны 2 десятичных знака точности, умножьте на 100, когда вы сохраняете значение, а затем разделите на 100 после его извлечения. Конечно, ваши запросы должны быть в «умноженной» форме.

person Jon Skeet    schedule 17.07.2010
comment
Спасибо, Джон. Я знал о демонах с плавающей запятой, но отказывался признать, что они могут входить с такой незначительной точностью: я пытался применить val = (Math.round(val*1000)) / 1000; перед входом в БД, что бесполезно... больше @ pascal. - person DJC; 17.07.2010
comment
@DJC: Просто попробуйте решить, как 0,1 должно быть представлено в двоичном формате ... вы можете подойти очень близко, но вы никогда не получите точно 0,1. - person Jon Skeet; 17.07.2010

Конечно, сравнивать значения с плавающей запятой на равенство — плохая идея.

Однако я вижу, что SQLite использует 8-байтовые значения с плавающей запятой (что похоже на DOUBLE) , поэтому странно, что 37,0 считается равным 37,3. Разве вы не изменили для своего примера значения, используемые в реальном коде?

Вы можете хранить свои LATITUDE как целые числа в десятых долях, самостоятельно применяя точность и преобразовывая значение при чтении/записи...

person pascal    schedule 17.07.2010
comment
Я предположил, что он работает с битом 37.0, заменив обе части. - person Jon Skeet; 17.07.2010
comment
Спасибо, Паскаль. Я применил ваше решение, умножив значения на 1000 и поместив интегральную часть в БД, которая отлично работает. К сожалению, android.widget.SimpleCursorAdapter, который я использую для заполнения списка для пользовательского интерфейса, конечно, не знает о необходимости обратного преобразования числа, поэтому мне нужно либо настроить это, либо перейти к хранению строк. У меня возникает соблазн сделать последнее, думая, что при моей требуемой точности они, вероятно, сопоставимы по хранению и точности с двойниками... - person DJC; 17.07.2010
comment
Ах да... К вашему сведению, проблемы возникали на сотых... не десятых... Я упростил пример :) - person DJC; 17.07.2010

Я попробовал с <column> BETWEEN <min> AND <max>, и это сработало, толерантность LATITUDE > 37.29 AND LATITUDE < 37.31 у меня не сработала. Но я пробовал BETWEEN с этой подсказкой.

Мои наблюдения:

  1. Когда у меня есть одна запись, больше (>) не работает.
  2. Если имеется более одной записи, больше, чем (>), работает отлично.
person Aslam    schedule 04.10.2011

У меня сработало следующее:

Установите для типа столбца значение Double и вставьте строки, используя тип данных Double для Java. Как-то дабл сработал...

person Indraneel    schedule 20.02.2014

Я столкнулся с той же проблемой. Мой столбец Sqlite имел тип FLOAT. Мне пришлось привести свою переменную Java типа Float как двойную, чтобы сравнение работало.

person ls-xwing    schedule 09.03.2014