Skip to content

Commit 4f1f303

Browse files
Support -0.0 in Firestore (#2751)
1 parent a90acfa commit 4f1f303

File tree

5 files changed

+32
-9
lines changed

5 files changed

+32
-9
lines changed

packages/firestore/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# Unreleased
2+
- [fixed] Fixed an issue where the number value `-0.0` would lose its sign when
3+
stored in Firestore.
4+
5+
# 1.10.0
26
- [feature] Implemented `Timestamp.valueOf()` so that `Timestamp` objects can be
37
compared for relative ordering using the JavaScript arithmetic comparison
48
operators (#2632).

packages/firestore/src/remote/serializer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ export class JsonProtoSerializer {
438438
return { doubleValue: 'Infinity' } as {};
439439
} else if (doubleValue === -Infinity) {
440440
return { doubleValue: '-Infinity' } as {};
441+
} else if (typeUtils.isNegativeZero(doubleValue)) {
442+
return { doubleValue: '-0' } as {};
441443
}
442444
}
443445
return { doubleValue: val.value() };
@@ -487,6 +489,8 @@ export class JsonProtoSerializer {
487489
return fieldValue.DoubleValue.POSITIVE_INFINITY;
488490
} else if ((obj.doubleValue as {}) === '-Infinity') {
489491
return fieldValue.DoubleValue.NEGATIVE_INFINITY;
492+
} else if ((obj.doubleValue as {}) === '-0') {
493+
return new fieldValue.DoubleValue(-0);
490494
}
491495
}
492496

packages/firestore/src/util/types.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ export function isNullOrUndefined(value: unknown): boolean {
2727
return value === null || value === undefined;
2828
}
2929

30+
/** Returns whether the value represents -0. */
31+
export function isNegativeZero(value: number) : boolean {
32+
// Detect if the value is -0.0. Based on polyfill from
33+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
34+
return value === -0 && 1 / value === 1 / -0;
35+
}
36+
3037
/**
3138
* Returns whether a value is an integer and in the safe integer range
3239
* @param value The value to test for being an integer and in the safe range
@@ -35,7 +42,8 @@ export function isSafeInteger(value: unknown): boolean {
3542
return (
3643
typeof value === 'number' &&
3744
Number.isInteger(value) &&
38-
(value as number) <= Number.MAX_SAFE_INTEGER &&
39-
(value as number) >= Number.MIN_SAFE_INTEGER
45+
!isNegativeZero(value) &&
46+
value <= Number.MAX_SAFE_INTEGER &&
47+
value >= Number.MIN_SAFE_INTEGER
4048
);
4149
}

packages/firestore/test/integration/api/type.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ apiDescribe('Firestore', (persistence: boolean) => {
4747
});
4848
});
4949

50+
it('can read and write number fields', () => {
51+
return withTestDb(persistence, db => {
52+
return expectRoundtrip(db, { a: 1, b: NaN, c: Infinity, d: -0.0 });
53+
});
54+
});
55+
5056
it('can read and write array fields', () => {
5157
return withTestDb(persistence, db => {
5258
return expectRoundtrip(db, { array: [1, 'foo', { deep: true }, null] });

packages/firestore/test/util/equality_matcher.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,21 @@ export interface Equatable<T> {
3434
*/
3535

3636
function customDeepEqual(left: unknown, right: unknown): boolean {
37-
/**
38-
* START: Custom compare logic
39-
*/
4037
if (typeof left === 'object' && left && 'isEqual' in left) {
4138
return (left as Equatable<unknown>).isEqual(right);
4239
}
4340
if (typeof right === 'object' && right && 'isEqual' in right) {
4441
return (right as Equatable<unknown>).isEqual(left);
4542
}
46-
/**
47-
* END: Custom compare logic
48-
*/
4943
if (left === right) {
50-
return true;
44+
if (left === 0.0 && right === 0.0) {
45+
// Firestore treats -0.0 and +0.0 as not equals, even though JavaScript
46+
// treats them as equal by default. Implemented based on MDN's Object.is()
47+
// polyfill.
48+
return 1 / left === 1 / right;
49+
} else {
50+
return true;
51+
}
5152
}
5253
if (
5354
typeof left === 'number' &&

0 commit comments

Comments
 (0)