-
Notifications
You must be signed in to change notification settings - Fork 645
Description
GEOHASH returns incorrect last character due to missing 52-bit truncation handling
Problem
Garnet's GEOHASH command produces a different final character than Redis for certain coordinates. For example:
GEOADD mygeo 13.361389 38.115556 "Palermo"
GEOHASH mygeo Palermo
| Server | Result |
|---|---|
| Redis | sqc8b49rny0 |
| Garnet | sqc8b49rnys |
The first 10 characters match, but the 11th diverges.
Root Cause
A standard geohash string is 11 base32 characters, which requires 55 bits (11 × 5). Both Redis and Garnet store geo data as sorted se
t scores using 64-bit doubles, which only have 52 bits of significand — 3 bits short of what the final character needs.
Redis accounts for this in src/geo.c lines 921–925 by setti
ng the 11th character to '0', since there aren't enough stored bits to determine it:
if (i == 10) {
/* We have just 52 bits, but the API used to output
* an 11 bytes geohash. For compatibility we assume
* zero. */
idx = 0;
}Garnet's GetGeoHashCode
doesn't have this special case — it shifts the remaining 2 bits into the high bits of a 5-bit index, so the last character ends up re
flecting bit positions that don't carry real precision:
for (var i = 0; i < chars.Length; i++)
{
chars[i] = (char)base32Chars[(int)(hash >> (BitsOfPrecision - 5)) & 0x1F];
hash <<= 5;
}Why It Matters
Clients and applications that compare, cache, or index geohash strings will see mismatches against values produced by Redis or other
standard implementations. Since the 52-bit score can't fully determine the 11th character, outputting '0' — as Redis does — is the
established convention.
Suggested Fix
On the last iteration of GetGeoHashCode, set the index to 0 rather than reading the remaining bits:
for (var i = 0; i < chars.Length; i++)
{
var idx = i < chars.Length - 1
? (int)(hash >> (BitsOfPrecision - 5)) & 0x1F
: 0;
chars[i] = (char)base32Chars[idx];
hash <<= 5;
}