Repository navigation
Indexed double equality misses -0.0: IndexKey(double) gives the two zeros different keys #249
Description
Found by a differential test that runs ~330 LINQ shapes over the same documents on a typed collection with and without indexes and with LINQ-to-Objects as the reference (PR to follow). Two rows disagree, on the indexed collection only.
Repro
// DiffDoc has double Ratio with .HasIndex(x => x.Ratio).
await ctx.Docs.InsertBulkAsync([
new DiffDoc { Name = "d1", Ratio = -0.0 },
new DiffDoc { Name = "d2", Ratio = 0.0 },
new DiffDoc { Name = "d3", Ratio = 1.0 }]);
ctx.Docs.AsQueryable().Where(x => x.Ratio == 0.0).ToList(); // expected [d1, d2], actual [d2]
ctx.Docs.AsQueryable().Count(x => x.Ratio == 0.0); // expected 2, actual 1Expected: IEEE 754 equality, which C#, LINQ-to-Objects and the BSON predicate on the plain collection all use, says -0.0 == 0.0.
Actual: only the +0.0 document when the property is indexed.
Mechanism (verified on 0450b97)
IndexKey(double) is an order-preserving bit encoding (src/BLite.Core/Indexing/IndexKey.cs:94-105): negative values flip all bits, positive values flip the sign bit. -0.0 has the sign bit set and encodes to 0x7FFF…FFFF; +0.0 encodes to 0x8000…0000. Adjacent but distinct keys, and the equality plan is an exact point lookup on the key of +0.0. By the same encoding x.Ratio >= 0.0 excludes -0.0 and x.Ratio < 0.0 includes it, both unlike C# (not covered by a test row yet).
Affected versions: since the sort-preserving double encoding in f83e0d5 (2026-03-17, v3.7.0). Reproduced on v5.2.3 / 0450b97.
Fix
Normalise -0.0 to +0.0 in IndexKey(double) (if (value == 0) value = 0.0; before taking the bits), which fixes stored keys and query bounds together. Existing indexes holding a -0.0 key would need a rebuild to be found by the new bound. NaN is a separate question (C# says NaN != NaN; the encoding gives it one or several keys) and is outside this issue.
Also noticed, not verified: DynamicCollection.BsonValueToIndexKey encodes doubles with BitConverter.GetBytes (DynamicCollection.cs:2725), little-endian and not order-preserving, unlike the typed path, so range bounds on a dynamic double index may be off. The differential test covers typed collections only.
Happy to send the fix if you want it.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
CaffeinatedCoder commentedon Oct 16, 2026
Fix in #252:
IndexKey(double)maps a zero to +0.0, and an index that still holds the former -0.0 key is rebuilt on open through the existing key-format migration (no KeyFormat bump). The dynamic-collection double encoding is not order-preserving (range queries on an indexed dynamic double return wrong results); the PR describes it, I will file it separately.test(oracles): model-based test of BTreeIndex with page accounting
test(oracles): model-based test of BTreeIndex with page accounting
mrdevrobot commentedon Oct 18, 2026
Reproduced on 0450b97 with the tests from #252. Edge value: medium.
fix(index): -0.0 and +0.0 share one index key (#249) (#252)