views:

164

answers:

2

I'm putting a float in an Android based SQLite database, like so:

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);

When I query the table:

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

the cursor comes back empty. If I change the value of the float to 37.0 it works properly, returning the record in the cursor.

I've tested this at some length, changing the database column spec from "REAL" to "float", etc. AS long as I have any fractional portion after the decimal point, the cursor returns empty. What is going on?

Thanks in advance!

+2  A: 

37.0 is exactly representable in binary floating point, so you don't have any of the normal issues. 37.3 is not exactly representable - your query isn't matching because the value in the database isn't exactly 37.3.

Options:

  • Use a decimal-based numeric type, if such a thing exists in SQLite (I'll check in a minute)
  • Make your query use a tolerance, e.g. "LATITUDE > 37.29 AND LATITUDE < 37.31". Comparing binary floating point values for exact equality is just asking for trouble, I'm afraid.

EDIT: I've just checked the docs, and it looks like SQLite doesn't support decimal non-integer types. I'll leave that suggestion above as it would be relevant for the same issue in other databases though.

EDIT: Pascal's solution of effectively rolling your own numeric type is a good one in many cases. Work out what level of precision you want, and multiply/divide accordingly... so if you want 2 decimal places of precision, multiply by 100 when you store a value and then divide by 100 after fetching it. Your queries will need to be in the "multiplied" form, of course.

Jon Skeet
Thank you Jon. I've been aware of the floating-point demons but refused to accept that they could enter in at such shallow precision: I'd tried applying val = (Math.round(val*1000)) / 1000; before going into the DB, which is useless... more @ pascal.
DJC
@DJC: Just try working out how 0.1 should be represented in binary... you can get very close, but you'll never get *exactly* to 0.1.
Jon Skeet
+1  A: 

Granted, it's a bad idea to compare float values for equality.

Howver, I see that SQLite uses 8-byte floating point values (which is like a DOUBLE), so it's odd that 37.0 is considered equal to 37.3. Unless you modified for your example the values used in the actual code?

You could store your LATITUDE as integers, in tenth of degree, applying the precision yourself, and converting the value on read/write...

pascal
I assumed that the "it works with 37.0" bit was having changed both parts.
Jon Skeet
Thank you pascal. I had applied your solution, multiplying values by 1000 and placing the integral portion into the DB, which works fine. Unfortunately the android.widget.SimpleCursorAdapter that I am using to populate a list for the UI is of course unaware of a need to convert the number back, so I have either to tweak that or else to shift to storing strings. I am tempted to do the latter, thinking that at my required precision they are probably comparable in storage and precision to the doubles...
DJC
Oh yes.. FYI the problems were occurring at the hundredths.. not tenths.. I had simplified the example :)
DJC