Skip to content

Fix CosineSimilarity precision so identical inputs give 1.0 and distance 0.0 - #789

Open
atharv1205 wants to merge 1 commit into
apache:masterfrom
atharv1205:fix-cosine-similarity-precision
Open

atharv1205 wants to merge 1 commit into
apache:masterfrom
atharv1205:fix-cosine-similarity-precision

Conversation

@atharv1205

@atharv1205 atharv1205 commented Nov 4, 2026 •

Copy link
Copy Markdown

What

CosineSimilarity.cosineSimilarity can return values slightly outside [0.0, 1.0] for identical inputs, so CosineDistance returns tiny non-zero, even negative, distances:

Input (identical on both sides) Before After
similarity, 2 tokens 0.9999999999999998 1.0
similarity, 3 / 6 / 12 tokens 1.0000000000000002 1.0
CosineDistance.apply("a b", "a b") 2.220446049250313E-16 0.0
CosineDistance.apply("a b c", "a b c") -2.220446049250313E-16 0.0
CosineDistance.apply("a b c d e f", "a b c d e f") -2.220446049250313E-16 0.0

Over 10,000 random identical token maps, 2093 returned a similarity above 1.0 before this change.

Why

The denominator was Math.sqrt(d1) * Math.sqrt(d2). Each square root is rounded separately and their product can be off by one ulp in either direction, so dot / norm misses 1.0 for identical vectors.

How

Compute dotProduct / Math.sqrt(d1 * d2). For identical vectors d1 * d2 is an exact square, and Math.sqrt is correctly rounded, so the result is exactly 1.0. The result is also clamped with Math.min(1.0, ...) so rounding on other inputs cannot exceed 1.0. The d1 <= 0 || d2 <= 0 branch is unchanged.

Tests

  • CosineSimilarityTest: identical vectors of several sizes give exactly 1.0; similarity stays within [0.0, 1.0] over generated identical and overlapping vectors.
  • CosineDistanceTest: identical inputs ("a", "a b", "a b c", "a b c d e f", ...) give exactly 0.0; distance stays within [0.0, 1.0] over generated inputs.
  • The new tests fail without the runtime change (10 of 18 fail) and pass with it. Existing tests are unchanged.

  • Read the contribution guidelines for this project.
  • Read the ASF Generative Tooling Guidance if you use Artificial Intelligence (AI).
  • Run a successful build using the default Maven goal with mvn; that's mvn on the command line by itself.
  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Each commit in the pull request should have a meaningful subject line and body.

CosineSimilarity divided the dot product by Math.sqrt(d1) * Math.sqrt(d2).
Rounding each square root separately can leave the product one ulp off, so
identical vectors gave 1.0000000000000002 or 0.9999999999999998 and
CosineDistance returned small non-zero, even negative, values such as
-2.220446049250313E-16 for "a b c".

Compute dotProduct / Math.sqrt(d1 * d2) instead. For identical vectors
d1 * d2 is an exact square, so the result is exactly 1.0. Clamp with
Math.min(1.0, ...) so rounding on other inputs cannot exceed 1.0.

Add tests that identical inputs give similarity 1.0 and distance 0.0, and
that both stay within [0.0, 1.0] over generated inputs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant