Skip to content

Bug: _searchObjectList uses raw (unnormalized) key weights, causing score underflow for extreme weight values #833

Description

@JSap0914

Problem

_searchObjectList builds its key list from FuseIndex.keys, which holds the raw user-specified weights. KeyStore normalises those weights (so they sum to 1), but _searchObjectList never consults KeyStore — it reads the original numbers directly.

This has two observable effects:

1. Score underflow for high key weights

A single key always normalises to weight = 1.0 (the only key contributes 100 % of the weight). Yet when a user writes weight: 100, the raw value is used in the exponent:

// Exact match (score === 0) → uses Number.EPSILON as base
Math.pow(Number.EPSILON, 100 * fieldNorm)  // underflows to exactly 0
const fuseW1  = new Fuse([{ text: 'hello' }], { keys: [{ name: 'text', weight: 1   }], includeScore: true })
const fuseW100 = new Fuse([{ text: 'hello' }], { keys: [{ name: 'text', weight: 100 }], includeScore: true })

fuseW1.search('hello')[0].score   // ~2.2e-16  (correct)
fuseW100.search('hello')[0].score // 0         ← wrong: underflow due to raw weight=100

After normalisation both instances have key.weight = 1.0, so the scores must be equal.

2. Score inconsistency between string queries and logical-expression queries

_searchObjectList (used for plain string queries) uses raw weights, while _searchLogical (used for Expression objects) fetches keys from this._keyStore.get(keyId) — the normalised version. The same search over the same data produces different absolute score values depending on query form.

Root cause

// src/core/index.ts — _searchObjectList
const { keys, records } = this._myIndex   // ← FuseIndex.keys = raw weights

should use this._keyStore.keys() (normalised), consistent with _searchLogical.

Minimal reproduction

import Fuse from 'fuse.js'

// Single-key: weight:1 and weight:100 both normalise to 1.0
const f1   = new Fuse([{ text: 'hello' }], { keys: [{ name: 'text', weight: 1   }], includeScore: true })
const f100 = new Fuse([{ text: 'hello' }], { keys: [{ name: 'text', weight: 100 }], includeScore: true })

console.log(f1.search('hello')[0].score)   // ~2.2e-16
console.log(f100.search('hello')[0].score) // 0  ← underflow

Expected

Both calls return the same score (normalised weight = 1.0 for a single key).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions