vitest icon indicating copy to clipboard operation
vitest copied to clipboard

Vitest considers `-0` and `+0` to be different values

Open psychedelicious opened this issue 10 months ago • 5 comments

Describe the bug

I have a utility that may return -0.

This is technically a distinct value from +0, but per IEEE_754, "negative zero" and "positive zero" should be considered to be equal in numerical comparisons.

But vitest's .toBe() and .toEqual() methods appears to not consider them to be equal, causing unit tests to fail.

Reproduction

https://stackblitz.com/edit/vitest-dev-vitest-dgy7dtwc?file=test%2Fbasic.test.ts

System Info

The issue is reproduced in StackBlitz above, but here is my system info.

  System:
    OS: Linux 6.8 Ubuntu 22.04.4 LTS 22.04.4 LTS (Jammy Jellyfish)
    CPU: (20) x64 13th Gen Intel(R) Core(TM) i5-13600K
    Memory: 19.04 GB / 31.12 GB
    Container: Yes
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 20.13.1 - ~/.nvm/versions/node/v20.13.1/bin/node
    Yarn: 1.22.22 - ~/.nvm/versions/node/v20.13.1/bin/yarn
    npm: 10.5.2 - ~/.nvm/versions/node/v20.13.1/bin/npm
    pnpm: 8.15.9 - ~/.nvm/versions/node/v20.13.1/bin/pnpm
  npmPackages:
    @vitejs/plugin-react-swc: ^3.8.0 => 3.8.0 
    @vitest/coverage-v8: ^3.0.6 => 3.0.6 
    @vitest/ui: ^3.0.6 => 3.0.6 
    vite: ^6.1.0 => 6.1.0 
    vitest: ^3.0.6 => 3.0.6

Used Package Manager

pnpm

Validations

psychedelicious avatar Mar 13 '25 06:03 psychedelicious

Digging into vitest's codebase, the root cause is clear:

  • .toBe() uses Object.is(), which finds -0 and +0 to not be the same: https://github.com/vitest-dev/vitest/blob/470cbec1f91bd3cb0aa604077fa288c4a6e1c2b9/packages/expect/src/jest-expect.ts#L146
  • .toEqual() also uses Object.is(): https://github.com/vitest-dev/vitest/blob/470cbec1f91bd3cb0aa604077fa288c4a6e1c2b9/packages/expect/src/jest-utils.ts#L121-L123

I think .toBe() kinda makes sense to fail here, because -0 truly is not +0. But I think toEqual() should pass.

psychedelicious avatar Mar 13 '25 06:03 psychedelicious

I'm not sure if it's obvious to say which is right 🤔 I'd assume Jest's equality has same behavior. On node, equal/deepEqual passes since it uses ==, while strictEqual/strictDeepEqual fails since it uses Object.is. https://nodejs.org/docs/v22.14.0/api/assert.html#assertdeepequalactual-expected-message

> assert.equal(+0, -0)
undefined
> assert.strictEqual(+0, -0)
Uncaught AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
+ actual - expected

+ 0
- -0

Quick workaround would be to use custom equality tester like this https://stackblitz.com/edit/vitest-dev-vitest-firxqjfs?file=test%2Fbasic.test.ts

expect.addEqualityTesters([
  function (a, b) {
    if (typeof a === 'number' && typeof b === 'number') {
      return a === b;
    }
  },
]);

hi-ogawa avatar Mar 13 '25 07:03 hi-ogawa

Thanks! Adding a custom equality tester is a good solution for my use case.

I noticed a code comment saying the .toEqual() logic was copied from underscore. I suspect the existing behaviour is ubiquitous and am therefore wary of changing it, even if it feels wrong. Who knows what implicitly relies on it.

I'm happy to close this issue if y'all prefer to leave the logic as-is.

psychedelicious avatar Mar 13 '25 08:03 psychedelicious

If this issue needs to be resolved, we can refer to the issue #5744 and PR for handling -0 and -NaN in the View.

Docs Ref

IEEE 754

pengooseDev avatar Mar 14 '25 02:03 pengooseDev

We have experienced this issue to. Our fix is to add the desired sign (e.g. -0), but our formatter now removes the -.

shivan-eyespace avatar Jul 07 '25 04:07 shivan-eyespace