Sfoglia il codice sorgente

feat: Add Decimal128Value (#799)

* WIP: Decimal128Value.

* WIP: decimal128 values. next: add quadruple + compare. next: add tests.

* Add quadruple and quadruple_builder.

* Implement comparison logic.

* Fix bug and add integration tests.

* Add unit tests.

* clang-format.

* Fix the NumberEquals logic.

* Port the missing tests from Android.

* Port more integration tests from Android.

* Address feedback.

* Update Quadruple library and re-enable tests (passing now).

* Fix FIRDecimal128Value.isEqual to handle -/+0.
Ehsan 9 mesi fa
parent
commit
303caf149a
30 ha cambiato i file con 2843 aggiunte e 151 eliminazioni
  1. 15 0
      FirebaseFirestoreInternal/FirebaseFirestore/FIRDecimal128Value.h
  2. 1 1
      Firestore/CHANGELOG.md
  3. 34 0
      Firestore/Example/Tests/API/FIRBsonTypesUnitTests.mm
  4. 66 0
      Firestore/Source/API/FIRDecimal128Value.mm
  5. 18 0
      Firestore/Source/API/FSTUserDataReader.mm
  6. 26 6
      Firestore/Source/API/FSTUserDataWriter.mm
  7. 46 0
      Firestore/Source/Public/FirebaseFirestore/FIRDecimal128Value.h
  8. 1 0
      Firestore/Swift/Source/Codable/CodablePassThroughTypes.swift
  9. 62 0
      Firestore/Swift/Source/Codable/Decimal128Value+Codable.swift
  10. 255 22
      Firestore/Swift/Tests/Integration/BsonTypesIntegrationTests.swift
  11. 6 0
      Firestore/Swift/Tests/Integration/CodableIntegrationTests.swift
  12. 40 0
      Firestore/Swift/Tests/Integration/SnapshotListenerSourceTests.swift
  13. 94 1
      Firestore/Swift/Tests/Integration/TypeTest.swift
  14. 33 12
      Firestore/core/src/index/firestore_index_value_writer.cc
  15. 96 6
      Firestore/core/src/model/value_util.cc
  16. 13 3
      Firestore/core/src/model/value_util.h
  17. 244 0
      Firestore/core/src/util/quadruple.cc
  18. 100 0
      Firestore/core/src/util/quadruple.h
  19. 913 0
      Firestore/core/src/util/quadruple_builder.cc
  20. 98 0
      Firestore/core/src/util/quadruple_builder.h
  21. 10 0
      Firestore/core/test/unit/bundle/bundle_serializer_test.cc
  22. 109 0
      Firestore/core/test/unit/index/index_value_writer_test.cc
  23. 212 38
      Firestore/core/test/unit/local/leveldb_index_manager_test.cc
  24. 237 26
      Firestore/core/test/unit/local/leveldb_local_store_test.cc
  25. 5 2
      Firestore/core/test/unit/model/document_test.cc
  26. 29 23
      Firestore/core/test/unit/model/object_value_test.cc
  27. 63 11
      Firestore/core/test/unit/model/value_util_test.cc
  28. 12 0
      Firestore/core/test/unit/remote/serializer_test.cc
  29. 4 0
      Firestore/core/test/unit/testutil/testutil.cc
  30. 1 0
      Firestore/core/test/unit/testutil/testutil.h

+ 15 - 0
FirebaseFirestoreInternal/FirebaseFirestore/FIRDecimal128Value.h

@@ -0,0 +1,15 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <FirebaseFirestoreInternal/FIRDecimal128Value.h>

+ 1 - 1
Firestore/CHANGELOG.md

@@ -1,6 +1,6 @@
 # Unreleased
 - [feature] Adds support for the following new types: `MinKey`, `MaxKey`, `RegexValue`,
-  `Int32Value`, `BSONObjectId`, `BSONTimestamp`, and `BSONBinaryData`. (#14800)
+  `Int32Value`, `Decimal128Value`, `BSONObjectId`, `BSONTimestamp`, and `BSONBinaryData`. (#14800)
 
 # 11.12.0
 - [fixed] Fixed the `null` value handling in `isNotEqualTo` and `notIn` filters.

+ 34 - 0
Firestore/Example/Tests/API/FIRBsonTypesUnitTests.mm

@@ -17,6 +17,7 @@
 #import <FirebaseFirestore/FIRBSONBinaryData.h>
 #import <FirebaseFirestore/FIRBSONObjectId.h>
 #import <FirebaseFirestore/FIRBSONTimestamp.h>
+#import <FirebaseFirestore/FIRDecimal128Value.h>
 #import <FirebaseFirestore/FIRFieldValue.h>
 #import <FirebaseFirestore/FIRInt32Value.h>
 #import <FirebaseFirestore/FIRMaxKey.h>
@@ -75,6 +76,39 @@ NS_ASSUME_NONNULL_BEGIN
   XCTAssertFalse([val1 isEqual:val3]);
 }
 
+- (void)testCreateAndReadAndCompareDecimal128Value {
+  FIRDecimal128Value *val1 = [[FIRDecimal128Value alloc] initWithValue:@"1.2e3"];
+  FIRDecimal128Value *val2 = [[FIRDecimal128Value alloc] initWithValue:@"12e2"];
+  FIRDecimal128Value *val3 = [[FIRDecimal128Value alloc] initWithValue:@"0.12e4"];
+  FIRDecimal128Value *val4 = [[FIRDecimal128Value alloc] initWithValue:@"12000e-1"];
+  FIRDecimal128Value *val5 = [[FIRDecimal128Value alloc] initWithValue:@"1.2"];
+  FIRDecimal128Value *val6 = [[FIRDecimal128Value alloc] initWithValue:@"NaN"];
+  FIRDecimal128Value *val7 = [[FIRDecimal128Value alloc] initWithValue:@"Infinity"];
+  FIRDecimal128Value *val8 = [[FIRDecimal128Value alloc] initWithValue:@"-Infinity"];
+  FIRDecimal128Value *val9 = [[FIRDecimal128Value alloc] initWithValue:@"NaN"];
+  FIRDecimal128Value *val10 = [[FIRDecimal128Value alloc] initWithValue:@"-0"];
+  FIRDecimal128Value *val11 = [[FIRDecimal128Value alloc] initWithValue:@"0"];
+  FIRDecimal128Value *val12 = [[FIRDecimal128Value alloc] initWithValue:@"-0.0"];
+  FIRDecimal128Value *val13 = [[FIRDecimal128Value alloc] initWithValue:@"0.0"];
+
+  // Test reading the value back
+  XCTAssertEqual(@"1.2e3", val1.value);
+
+  // Test isEqual
+  XCTAssertTrue([val1 isEqual:val2]);
+  XCTAssertTrue([val1 isEqual:val3]);
+  XCTAssertTrue([val1 isEqual:val4]);
+  XCTAssertFalse([val1 isEqual:val5]);
+
+  // Test isEqual for special values.
+  XCTAssertTrue([val6 isEqual:val9]);
+  XCTAssertFalse([val7 isEqual:val8]);
+  XCTAssertFalse([val7 isEqual:val9]);
+  XCTAssertTrue([val10 isEqual:val11]);
+  XCTAssertTrue([val10 isEqual:val12]);
+  XCTAssertTrue([val10 isEqual:val13]);
+}
+
 - (void)testCreateAndReadAndCompareBsonObjectId {
   FIRBSONObjectId *val1 = [[FIRBSONObjectId alloc] initWithValue:@"abcd"];
   FIRBSONObjectId *val2 = [[FIRBSONObjectId alloc] initWithValue:@"abcd"];

+ 66 - 0
Firestore/Source/API/FIRDecimal128Value.mm

@@ -0,0 +1,66 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/Source/Public/FirebaseFirestore/FIRDecimal128Value.h"
+
+#include "Firestore/core/src/util/quadruple.h"
+#include "Firestore/core/src/util/string_apple.h"
+
+using firebase::firestore::util::MakeString;
+using firebase::firestore::util::Quadruple;
+
+@implementation FIRDecimal128Value
+
+- (instancetype)initWithValue:(NSString *)value {
+  self = [super init];
+  if (self) {
+    _value = [value copy];
+  }
+  return self;
+}
+
+- (BOOL)isEqual:(nullable id)object {
+  if (self == object) {
+    return YES;
+  }
+
+  if (![object isKindOfClass:[FIRDecimal128Value class]]) {
+    return NO;
+  }
+
+  FIRDecimal128Value *other = (FIRDecimal128Value *)object;
+
+  Quadruple lhs = Quadruple();
+  Quadruple rhs = Quadruple();
+  lhs.Parse(MakeString(self.value));
+  rhs.Parse(MakeString(other.value));
+
+  // Firestore considers +0 and -0 to be equal, but `Quadruple::Compare()` does not.
+  if (lhs.Compare(Quadruple(-0.0)) == 0) lhs = Quadruple();
+  if (rhs.Compare(Quadruple(-0.0)) == 0) rhs = Quadruple();
+
+  return lhs.Compare(rhs) == 0;
+}
+
+- (id)copyWithZone:(__unused NSZone *_Nullable)zone {
+  return [[FIRDecimal128Value alloc] initWithValue:self.value];
+}
+
+- (NSString *)description {
+  return [NSString stringWithFormat:@"<FIRDecimal128Value: (%@)>", self.value];
+}
+
+@end

+ 18 - 0
Firestore/Source/API/FSTUserDataReader.mm

@@ -27,6 +27,7 @@
 #import "FIRBSONBinaryData.h"
 #import "FIRBSONObjectId.h"
 #import "FIRBSONTimestamp.h"
+#import "FIRDecimal128Value.h"
 #import "FIRGeoPoint.h"
 #import "FIRInt32Value.h"
 #import "FIRMaxKey.h"
@@ -452,6 +453,20 @@ NS_ASSUME_NONNULL_BEGIN
   return std::move(result);
 }
 
+- (Message<google_firestore_v1_Value>)parseDecimal128Value:(FIRDecimal128Value *)decimal128
+                                                   context:(ParseContext &&)context {
+  __block Message<google_firestore_v1_Value> result;
+  result->which_value_type = google_firestore_v1_Value_map_value_tag;
+  result->map_value = {};
+  result->map_value.fields_count = 1;
+  result->map_value.fields = nanopb::MakeArray<google_firestore_v1_MapValue_FieldsEntry>(1);
+  result->map_value.fields[0].key = nanopb::CopyBytesArray(model::kDecimal128TypeFieldValue);
+  result->map_value.fields[0].value =
+      *[self encodeStringValue:MakeString(decimal128.value)].release();
+
+  return std::move(result);
+}
+
 - (Message<google_firestore_v1_Value>)parseBsonObjectId:(FIRBSONObjectId *)oid
                                                 context:(ParseContext &&)context {
   __block Message<google_firestore_v1_Value> result;
@@ -723,6 +738,9 @@ NS_ASSUME_NONNULL_BEGIN
   } else if ([input isKindOfClass:[FIRInt32Value class]]) {
     FIRInt32Value *value = input;
     return [self parseInt32Value:value context:std::move(context)];
+  } else if ([input isKindOfClass:[FIRDecimal128Value class]]) {
+    FIRDecimal128Value *value = input;
+    return [self parseDecimal128Value:value context:std::move(context)];
   } else if ([input isKindOfClass:[FIRBSONObjectId class]]) {
     FIRBSONObjectId *oid = input;
     return [self parseBsonObjectId:oid context:std::move(context)];

+ 26 - 6
Firestore/Source/API/FSTUserDataWriter.mm

@@ -26,6 +26,7 @@
 #include "Firestore/Source/Public/FirebaseFirestore/FIRBSONBinaryData.h"
 #include "Firestore/Source/Public/FirebaseFirestore/FIRBSONObjectId.h"
 #include "Firestore/Source/Public/FirebaseFirestore/FIRBSONTimestamp.h"
+#include "Firestore/Source/Public/FirebaseFirestore/FIRDecimal128Value.h"
 #include "Firestore/Source/Public/FirebaseFirestore/FIRInt32Value.h"
 #include "Firestore/Source/Public/FirebaseFirestore/FIRMaxKey.h"
 #include "Firestore/Source/Public/FirebaseFirestore/FIRMinKey.h"
@@ -58,6 +59,8 @@ using firebase::firestore::google_firestore_v1_Value;
 using firebase::firestore::google_protobuf_Timestamp;
 using firebase::firestore::model::kRawBsonTimestampTypeIncrementFieldValue;
 using firebase::firestore::model::kRawBsonTimestampTypeSecondsFieldValue;
+using firebase::firestore::model::kRawDecimal128TypeFieldValue;
+using firebase::firestore::model::kRawInt32TypeFieldValue;
 using firebase::firestore::model::kRawRegexTypeOptionsFieldValue;
 using firebase::firestore::model::kRawRegexTypePatternFieldValue;
 using firebase::firestore::model::kRawVectorValueFieldKey;
@@ -109,7 +112,12 @@ NS_ASSUME_NONNULL_BEGIN
       return value.boolean_value ? @YES : @NO;
     case TypeOrder::kNumber:
       if (value.which_value_type == google_firestore_v1_Value_map_value_tag) {
-        return [self convertedInt32:value.map_value];
+        absl::string_view key = MakeStringView(value.map_value.fields[0].key);
+        if (key.compare(absl::string_view(kRawInt32TypeFieldValue)) == 0) {
+          return [self convertedInt32:value.map_value];
+        } else if (key.compare(absl::string_view(kRawDecimal128TypeFieldValue)) == 0) {
+          return [self convertedDecimal128Value:value.map_value];
+        }
       }
       return value.which_value_type == google_firestore_v1_Value_integer_value_tag
                  ? @(value.integer_value)
@@ -157,7 +165,7 @@ NS_ASSUME_NONNULL_BEGIN
   for (pb_size_t i = 0; i < mapValue.fields_count; ++i) {
     absl::string_view key = MakeStringView(mapValue.fields[i].key);
     const google_firestore_v1_Value &value = mapValue.fields[i].value;
-    if ((0 == key.compare(absl::string_view(kRawVectorValueFieldKey))) &&
+    if ((key.compare(absl::string_view(kRawVectorValueFieldKey)) == 0) &&
         value.which_value_type == google_firestore_v1_Value_array_value_tag) {
       return [FIRFieldValue vectorWithArray:[self convertedArray:value.array_value]];
     }
@@ -174,11 +182,11 @@ NS_ASSUME_NONNULL_BEGIN
       for (pb_size_t i = 0; i < innerValue.map_value.fields_count; ++i) {
         absl::string_view key = MakeStringView(innerValue.map_value.fields[i].key);
         const google_firestore_v1_Value &value = innerValue.map_value.fields[i].value;
-        if ((0 == key.compare(absl::string_view(kRawRegexTypePatternFieldValue))) &&
+        if ((key.compare(absl::string_view(kRawRegexTypePatternFieldValue)) == 0) &&
             value.which_value_type == google_firestore_v1_Value_string_value_tag) {
           pattern = MakeNSString(MakeStringView(value.string_value));
         }
-        if ((0 == key.compare(absl::string_view(kRawRegexTypeOptionsFieldValue))) &&
+        if ((key.compare(absl::string_view(kRawRegexTypeOptionsFieldValue)) == 0) &&
             value.which_value_type == google_firestore_v1_Value_string_value_tag) {
           options = MakeNSString(MakeStringView(value.string_value));
         }
@@ -198,6 +206,18 @@ NS_ASSUME_NONNULL_BEGIN
   return [[FIRInt32Value alloc] initWithValue:value];
 }
 
+- (FIRDecimal128Value *)convertedDecimal128Value:(const google_firestore_v1_MapValue &)mapValue {
+  NSString *decimalString = @"";
+  if (mapValue.fields_count == 1) {
+    const google_firestore_v1_Value &decimalValue = mapValue.fields[0].value;
+    if (decimalValue.which_value_type == google_firestore_v1_Value_string_value_tag) {
+      decimalString = MakeNSString(MakeStringView(decimalValue.string_value));
+    }
+  }
+
+  return [[FIRDecimal128Value alloc] initWithValue:decimalString];
+}
+
 - (FIRBSONObjectId *)convertedBsonObjectId:(const google_firestore_v1_MapValue &)mapValue {
   NSString *oid = @"";
   if (mapValue.fields_count == 1) {
@@ -219,12 +239,12 @@ NS_ASSUME_NONNULL_BEGIN
       for (pb_size_t i = 0; i < innerValue.map_value.fields_count; ++i) {
         absl::string_view key = MakeStringView(innerValue.map_value.fields[i].key);
         const google_firestore_v1_Value &value = innerValue.map_value.fields[i].value;
-        if ((0 == key.compare(absl::string_view(kRawBsonTimestampTypeSecondsFieldValue))) &&
+        if ((key.compare(absl::string_view(kRawBsonTimestampTypeSecondsFieldValue)) == 0) &&
             value.which_value_type == google_firestore_v1_Value_integer_value_tag) {
           // The value from the server is guaranteed to fit in a 32-bit unsigned integer.
           seconds = static_cast<uint32_t>(value.integer_value);
         }
-        if ((0 == key.compare(absl::string_view(kRawBsonTimestampTypeIncrementFieldValue))) &&
+        if ((key.compare(absl::string_view(kRawBsonTimestampTypeIncrementFieldValue)) == 0) &&
             value.which_value_type == google_firestore_v1_Value_integer_value_tag) {
           // The value from the server is guaranteed to fit in a 32-bit unsigned integer.
           increment = static_cast<uint32_t>(value.integer_value);

+ 46 - 0
Firestore/Source/Public/FirebaseFirestore/FIRDecimal128Value.h

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Represents a 128-bit decimal number type in Firestore documents.
+ */
+NS_SWIFT_SENDABLE
+NS_SWIFT_NAME(Decimal128Value)
+__attribute__((objc_subclassing_restricted))
+@interface FIRDecimal128Value : NSObject<NSCopying>
+
+/** The string representation of the 128-bit decimal value. */
+@property(nonatomic, copy, readonly) NSString *value;
+
+/** :nodoc: */
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Creates a `Decimal128Value` with the given value.
+ * @param value The string representation of the number to be stored.
+ */
+- (instancetype)initWithValue:(NSString *)value NS_SWIFT_NAME(init(_:));
+
+/** Returns true if the given object is equal to this, and false otherwise. */
+- (BOOL)isEqual:(nullable id)object;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 1 - 0
Firestore/Swift/Source/Codable/CodablePassThroughTypes.swift

@@ -37,6 +37,7 @@ struct FirestorePassthroughTypes: StructureCodingPassthroughTypeResolver {
       t is MaxKey ||
       t is RegexValue ||
       t is Int32Value ||
+      t is Decimal128Value ||
       t is BSONObjectId ||
       t is BSONTimestamp ||
       t is BSONBinaryData

+ 62 - 0
Firestore/Swift/Source/Codable/Decimal128Value+Codable.swift

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if SWIFT_PACKAGE
+  @_exported import FirebaseFirestoreInternalWrapper
+#else
+  @_exported import FirebaseFirestoreInternal
+#endif // SWIFT_PACKAGE
+
+/**
+ * A protocol describing the encodable properties of a Decimal128Value.
+ *
+ * Note: this protocol exists as a workaround for the Swift compiler: if the Decimal128Value class
+ * was extended directly to conform to Codable, the methods implementing the protocol would be need
+ * to be marked required but that can't be done in an extension. Declaring the extension on the
+ * protocol sidesteps this issue.
+ */
+private protocol CodableDecimal128Value: Codable {
+  var value: String { get }
+
+  init(_ value: String)
+}
+
+/** The keys in a Decimal128Value. Must match the properties of Decimal128Value. */
+private enum Decimal128ValueKeys: String, CodingKey {
+  case value
+}
+
+/**
+ * An extension of Decimal128Value that implements the behavior of the Codable protocol.
+ *
+ * Note: this is implemented manually here because the Swift compiler can't synthesize these methods
+ * when declaring an extension to conform to Codable.
+ */
+extension CodableDecimal128Value {
+  public init(from decoder: Decoder) throws {
+    let container = try decoder.container(keyedBy: Decimal128ValueKeys.self)
+    let value = try container.decode(String.self, forKey: .value)
+    self.init(value)
+  }
+
+  public func encode(to encoder: Encoder) throws {
+    var container = encoder.container(keyedBy: Decimal128ValueKeys.self)
+    try container.encode(value, forKey: .value)
+  }
+}
+
+/** Extends Decimal128Value to conform to Codable. */
+extension FirebaseFirestore.Decimal128Value: FirebaseFirestore.CodableDecimal128Value {}

+ 255 - 22
Firestore/Swift/Tests/Integration/BsonTypesIntegrationTests.swift

@@ -107,6 +107,7 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       "binary": BSONBinaryData(subtype: 1, data: Data([1, 2, 3])),
       "objectId": BSONObjectId("507f191e810c19729de860ea"),
       "int32": Int32Value(1),
+      "decimal128": Decimal128Value("1.2e3"),
       "min": MinKey.shared,
       "max": MaxKey.shared,
       "regex": RegexValue(pattern: "^foo", options: "i"),
@@ -127,6 +128,10 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       snapshot.get("int32") as? Int32Value,
       Int32Value(2)
     )
+    XCTAssertEqual(
+      snapshot.get("decimal128") as? Decimal128Value,
+      Decimal128Value("1.2e3")
+    )
     XCTAssertEqual(
       snapshot.get("min") as? MinKey,
       MinKey.shared
@@ -160,6 +165,7 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       "binary": BSONBinaryData(subtype: 1, data: Data([1, 2, 3])),
       "objectId": BSONObjectId("507f191e810c19729de860ea"),
       "int32": Int32Value(1),
+      "decimal128": Decimal128Value("-1.23e-4"),
       "min": MinKey.shared,
       "max": MaxKey.shared,
       "regex": RegexValue(pattern: "^foo", options: "i"),
@@ -179,6 +185,10 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       snapshot.get("int32") as? Int32Value,
       Int32Value(2)
     )
+    XCTAssertEqual(
+      snapshot.get("decimal128") as? Decimal128Value,
+      Decimal128Value("-1.23e-4")
+    )
     XCTAssertEqual(
       snapshot.get("min") as? MinKey,
       MinKey.shared
@@ -268,6 +278,209 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
     )
   }
 
+  func testCanFilterAndOrderDecimal128Values() async throws {
+    let testDocs: [String: [String: Any]] = [
+      "a": ["key": Decimal128Value("-Infinity")],
+      "b": ["key": Decimal128Value("NaN")],
+      "c": ["key": Decimal128Value("-0")],
+      "d": ["key": Decimal128Value("0")],
+      "e": ["key": Decimal128Value("0.0")],
+      "f": ["key": Decimal128Value("-01.23e-4")],
+      "g": ["key": Decimal128Value("1.5e6")],
+      "h": ["key": Decimal128Value("Infinity")],
+    ]
+
+    let collection = collectionRef()
+    await setDocumentData(testDocs, toCollection: collection)
+
+    var query = collection
+      .whereField("key", isGreaterThanOrEqualTo: Decimal128Value("0"))
+      .order(by: "key", descending: true)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["h", "g", "e", "d", "c"]
+    )
+
+    query = collection
+      .whereField("key", isNotEqualTo: Decimal128Value("0"))
+      .order(by: "key")
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["b", "a", "f", "g", "h"]
+    )
+
+    query = collection
+      .whereField("key", isNotEqualTo: Decimal128Value("NaN"))
+      .order(by: "key")
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["a", "f", "c", "d", "e", "g", "h"]
+    )
+
+    query = collection
+      .whereField("key", isEqualTo: Decimal128Value("-01.23e-4"))
+      .order(by: "key")
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["f"]
+    )
+
+    query = collection
+      .whereField("key", isNotEqualTo: Decimal128Value("-01.23e-4"))
+      .order(by: "key")
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["b", "a", "c", "d", "e", "g", "h"]
+    )
+
+    query = collection
+      .whereField("key", in: [Decimal128Value("0")])
+      .order(by: "key", descending: true)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["e", "d", "c"]
+    )
+
+    // Note: The server sends document `b` incorrectly, but the client filters
+    // it out. Currently `FieldFilter.NOT_IN` with `NaN` in the list does not
+    // behave the same as `UnaryFilter.IS_NOT_NAN`.
+    query = collection
+      .whereField("key", notIn: [Decimal128Value("NaN"), Decimal128Value("Infinity")])
+      .order(by: "key", descending: true)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["g", "e", "d", "c", "f", "a"]
+    )
+  }
+
+  func testCanFilterAndOrderNumericalValues() async throws {
+    let testDocs: [String: [String: Any]] = [
+      "a": ["key": Decimal128Value("-1.2e3")],
+      "b": ["key": Int32Value(0)],
+      "c": ["key": Decimal128Value("1")],
+      "d": ["key": Int32Value(1)],
+      "e": ["key": 1],
+      "f": ["key": 1.0],
+      "g": ["key": Decimal128Value("1.2e-3")],
+      "h": ["key": Int32Value(2)],
+      "i": ["key": Decimal128Value("NaN")],
+      "j": ["key": Decimal128Value("-Infinity")],
+      "k": ["key": Double.nan],
+      "l": ["key": Decimal128Value("Infinity")],
+    ]
+
+    let collection = collectionRef()
+    await setDocumentData(testDocs, toCollection: collection)
+
+    let query = collection.order(by: "key", descending: true)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: [
+        "l", // Infinity
+        "h", // 2
+        "f", // 1.0
+        "e", // 1
+        "d", // 1
+        "c", // 1
+        "g", // 0.0012
+        "b", // 0
+        "a", // -1200
+        "j", // -Infinity
+        "k", // NaN
+        "i", // NaN
+      ]
+    )
+
+    let query2 = collection
+      .whereField("key", isNotEqualTo: Decimal128Value("1.0"))
+      .order(by: "key", descending: true)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query2,
+      expectedResult: ["l", "h", "g", "b", "a", "j", "k", "i"]
+    )
+
+    let query3 = collection
+      .whereField("key", isEqualTo: 1)
+      .order(by: "key", descending: true)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query3,
+      expectedResult: ["f", "e", "d", "c"]
+    )
+  }
+
+  func testDecimal128ValuesWithNo2sComplementRepresentation() async throws {
+    let testDocs: [String: [String: Any]] = [
+      "a": ["key": Decimal128Value("-1.1e-3")], // -0.0011
+      "b": ["key": Decimal128Value("1.1")],
+      "c": ["key": 1.1],
+      "d": ["key": 1.0],
+      "e": ["key": Decimal128Value("1.1e-3")], // 0.0011
+    ]
+
+    let collection = collectionRef()
+    await setDocumentData(testDocs, toCollection: collection)
+
+    let query = collection
+      .whereField("key", isEqualTo: Decimal128Value("1.1"))
+      .order(by: "key", descending: true)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["b"]
+    )
+
+    let query2 = collection
+      .whereField("key", isNotEqualTo: Decimal128Value("1.1"))
+      .order(by: "key", descending: false)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query2,
+      expectedResult: ["a", "e", "d", "c"]
+    )
+
+    let query3 = collection
+      .whereField("key", isEqualTo: 1.1)
+      .order(by: "key", descending: false)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query3,
+      expectedResult: ["c"]
+    )
+
+    let query4 = collection
+      .whereField("key", isNotEqualTo: 1.1)
+      .order(by: "key", descending: false)
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query4,
+      expectedResult: ["a", "e", "d", "b"]
+    )
+  }
+
   func testCanFilterAndOrderTimestampValues() async throws {
     let testDocs: [String: [String: Any]] = [
       "a": ["key": BSONTimestamp(seconds: 1, increment: 1)],
@@ -387,17 +600,15 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       expectedResult: ["b", "a"]
     )
 
-    // TODO(b/410032145): This currently fails, and is fixed by
-    // PR #14704. Uncomment this when moving to the main branch.
-    // var query2 = collection
-    //   .whereField("key", isNotEqualTo: MinKey.shared))
-    //   .order(by: "key")
-    // try await assertSdkQueryResultsConsistentWithBackend(
-    //   testDocs,
-    //   collection: collection,
-    //   query: query2,
-    //   expectedResult: ["d", "e"]
-    // )
+    query = collection
+      .whereField("key", isNotEqualTo: MinKey.shared)
+      .order(by: "key")
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["d", "e"]
+    )
 
     query = collection
       .whereField("key", isGreaterThanOrEqualTo: MinKey.shared)
@@ -472,17 +683,15 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       expectedResult: ["c", "d"]
     )
 
-    // TODO(b/410032145): This currently fails, and is fixed by
-    // PR #14704. Uncomment this when moving to the main branch.
-    // query = collection
-    //   .whereField("key", isNotEqualTo: MaxKey.shared))
-    //   .order(by: "key")
-    // try await assertSdkQueryResultsConsistentWithBackend(
-    //   testDocs,
-    //   collection: collection,
-    //   query: query,
-    //   expectedResult: ["a", "b"]
-    // )
+    query = collection
+      .whereField("key", isNotEqualTo: MaxKey.shared)
+      .order(by: "key")
+    try await assertSdkQueryResultsConsistentWithBackend(
+      testDocs,
+      collection: collection,
+      query: query,
+      expectedResult: ["a", "b"]
+    )
 
     query = collection
       .whereField("key", isGreaterThanOrEqualTo: MaxKey.shared)
@@ -584,9 +793,17 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       "bsonBinary1": ["key": BSONBinaryData(subtype: 1, data: Data([1, 2, 3]))],
       "bsonBinary2": ["key": BSONBinaryData(subtype: 1, data: Data([1, 2, 4]))],
       "bsonBinary3": ["key": BSONBinaryData(subtype: 2, data: Data([1, 2, 2]))],
+      "decimal128Value1": ["key": Decimal128Value("NaN")],
+      "decimal128Value2": ["key": Decimal128Value("-Infinity")],
+      "decimal128Value3": ["key": Decimal128Value("-1.0")],
       "int32Value1": ["key": Int32Value(-1)],
+      "decimal128Value4": ["key": Decimal128Value("1.0")],
       "int32Value2": ["key": Int32Value(1)],
+      "decimal128Value5": ["key": Decimal128Value("-0.0")],
+      "decimal128Value6": ["key": Decimal128Value("0.0")],
       "int32Value3": ["key": Int32Value(0)],
+      "decimal128Value7": ["key": Decimal128Value("1.23e-4")],
+      "decimal128Value8": ["key": Decimal128Value("Infinity")],
       "minKey1": ["key": MinKey.shared],
       "minKey2": ["key": MinKey.shared],
       "maxKey1": ["key": MaxKey.shared],
@@ -614,9 +831,17 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
                                                            "bsonTimestamp1",
                                                            "bsonTimestamp2",
                                                            "bsonTimestamp3",
+                                                           "decimal128Value8",
                                                            "int32Value2",
+                                                           "decimal128Value4",
+                                                           "decimal128Value7",
                                                            "int32Value3",
+                                                           "decimal128Value6",
+                                                           "decimal128Value5",
                                                            "int32Value1",
+                                                           "decimal128Value3",
+                                                           "decimal128Value2",
+                                                           "decimal128Value1",
                                                            "minKey2",
                                                            "minKey1",
                                                          ])
@@ -631,9 +856,13 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       "minValue": ["key": MinKey.shared],
       "booleanValue": ["key": true],
       "nanValue": ["key": Double.nan],
+      "nanValue2": ["key": Decimal128Value("NaN")],
+      "negativeInfinity": ["key": Decimal128Value("-Infinity")],
       "int32Value": ["key": Int32Value(1)],
       "doubleValue": ["key": 2.0],
       "integerValue": ["key": 3],
+      "decimal128Value": ["key": Decimal128Value("3.4e-5")],
+      "infinity": ["key": Decimal128Value("Infinity")],
       "timestampValue": ["key": Timestamp(seconds: 100, nanoseconds: 123_456_000)],
       "bsonTimestampValue": ["key": BSONTimestamp(seconds: 1, increment: 2)],
       "stringValue": ["key": "string"],
@@ -660,9 +889,13 @@ class BsonTypesIntegrationTests: FSTIntegrationTestCase {
       "minValue",
       "booleanValue",
       "nanValue",
+      "nanValue2",
+      "negativeInfinity",
+      "decimal128Value",
       "int32Value",
       "doubleValue",
       "integerValue",
+      "infinity",
       "timestampValue",
       "bsonTimestampValue",
       "stringValue",

+ 6 - 0
Firestore/Swift/Tests/Integration/CodableIntegrationTests.swift

@@ -113,6 +113,7 @@ class CodableIntegrationTests: FSTIntegrationTestCase {
       var vector: VectorValue
       var regex: RegexValue
       var int32: Int32Value
+      var decimal128: Decimal128Value
       var minKey: MinKey
       var maxKey: MaxKey
       var bsonOjectId: BSONObjectId
@@ -128,6 +129,7 @@ class CodableIntegrationTests: FSTIntegrationTestCase {
                       vector: FieldValue.vector([0.7, 0.6]),
                       regex: RegexValue(pattern: "^foo", options: "i"),
                       int32: Int32Value(1),
+                      decimal128: Decimal128Value("1.5"),
                       minKey: MinKey.shared,
                       maxKey: MaxKey.shared,
                       bsonOjectId: BSONObjectId("507f191e810c19729de860ec"),
@@ -251,6 +253,10 @@ class CodableIntegrationTests: FSTIntegrationTestCase {
     try assertCanWriteAndReadCodableValueWithAllFlavors(value: Int32Value(123))
   }
 
+  func testDecimal128Value() throws {
+    try assertCanWriteAndReadCodableValueWithAllFlavors(value: Decimal128Value("1.2e3"))
+  }
+
   func testBsonObjectId() throws {
     try assertCanWriteAndReadCodableValueWithAllFlavors(
       value: BSONObjectId("507f191e810c19729de860ec")

+ 40 - 0
Firestore/Swift/Tests/Integration/SnapshotListenerSourceTests.swift

@@ -800,6 +800,7 @@ class SnapshotListenerSourceTests: FSTIntegrationTestCase {
       testData["a"]!["key"]
     )
 
+    // Add a 32-bit int value.
     let newData = ["key": Int32Value(2)]
     collection.document("g").setData(newData)
 
@@ -834,6 +835,45 @@ class SnapshotListenerSourceTests: FSTIntegrationTestCase {
       testData["a"]!["key"]
     )
 
+    // Add a 128-bit decimal value.
+    let decimalData = ["key": Decimal128Value("-4.123e-5")]
+    collection.document("h").setData(decimalData)
+
+    querySnap = eventAccumulator.awaitEvent(withName: "snapshot") as! QuerySnapshot
+    XCTAssertEqual(querySnap.isEmpty, false)
+    XCTAssertEqual(
+      querySnap.documents[0].data()["key"] as! MinKey,
+      testData["b"]!["key"]
+    )
+    XCTAssertEqual(
+      querySnap.documents[1].data()["key"] as! Decimal128Value,
+      decimalData["key"]!
+    )
+    XCTAssertEqual(
+      querySnap.documents[2].data()["key"] as! Int32Value,
+      newData["key"]!
+    )
+    XCTAssertEqual(
+      querySnap.documents[3].data()["key"] as! BSONTimestamp,
+      testData["c"]!["key"]
+    )
+    XCTAssertEqual(
+      querySnap.documents[4].data()["key"] as! BSONBinaryData,
+      testData["e"]!["key"]
+    )
+    XCTAssertEqual(
+      querySnap.documents[5].data()["key"] as! BSONObjectId,
+      testData["d"]!["key"]
+    )
+    XCTAssertEqual(
+      querySnap.documents[6].data()["key"] as! RegexValue,
+      testData["f"]!["key"]
+    )
+    XCTAssertEqual(
+      querySnap.documents[7].data()["key"] as! MaxKey,
+      testData["a"]!["key"]
+    )
+
     registration.remove()
   }
 }

+ 94 - 1
Firestore/Swift/Tests/Integration/TypeTest.swift

@@ -137,6 +137,24 @@ class TypeTest: FSTIntegrationTestCase {
     XCTAssertTrue(v1 != v3)
   }
 
+  func testDecimal128ValueEquality() {
+    let v1 = Decimal128Value("1.2e3")
+    let v2 = Decimal128Value("12e2")
+    let v3 = Decimal128Value("0.12e4")
+    let v4 = Decimal128Value("12000e-1")
+    let v5 = Decimal128Value("1.2")
+
+    XCTAssertTrue(v1 == v2)
+    XCTAssertTrue(v1 == v3)
+    XCTAssertTrue(v1 == v4)
+    XCTAssertFalse(v1 == v5)
+
+    XCTAssertFalse(v1 != v2)
+    XCTAssertFalse(v1 != v3)
+    XCTAssertFalse(v1 != v4)
+    XCTAssertTrue(v1 != v5)
+  }
+
   func testBsonTimestampEquality() {
     let v1 = BSONTimestamp(seconds: 1, increment: 1)
     let v2 = BSONTimestamp(seconds: 1, increment: 1)
@@ -207,6 +225,27 @@ class TypeTest: FSTIntegrationTestCase {
     )
   }
 
+  func testCanReadAndWriteDecimal128Fields() async throws {
+    _ = try await expectRoundtrip(
+      coll: collectionRef(),
+      data: ["map": [
+        "decimalSciPositive": Decimal128Value("1.2e3"),
+        "decimalSciNegative": Decimal128Value("-1.2e3"),
+        "decimalSciNegativeExponent": Decimal128Value("1.2e-3"),
+        "decimalSciNegativeValueAndExponent": Decimal128Value("-1.2e-3"),
+        "decimalSciExplicitPositiveExponent": Decimal128Value("1.2e+3"),
+        "decimalFloatPositive": Decimal128Value("1.1"),
+        "decimalIntNegative": Decimal128Value("-1"),
+        "decimalZeroNegative": Decimal128Value("-0"),
+        "decimalZeroInt": Decimal128Value("0"),
+        "decimalZeroFloat": Decimal128Value("0.0"),
+        "decimalNaN": Decimal128Value("NaN"),
+        "decimalInfinityPositive": Decimal128Value("Infinity"),
+        "decimalInfinityNegative": Decimal128Value("-Infinity"),
+      ]]
+    )
+  }
+
   func testCanReadAndWriteBsonTimestampFields() async throws {
     _ = try await expectRoundtrip(
       coll: collectionRef(),
@@ -244,6 +283,7 @@ class TypeTest: FSTIntegrationTestCase {
         BSONObjectId("507f191e810c19729de860ea"),
         BSONTimestamp(seconds: 123, increment: 456),
         Int32Value(1),
+        Decimal128Value("1.2e3"),
         MinKey.shared,
         MaxKey.shared,
         RegexValue(pattern: "^foo", options: "i"),
@@ -254,11 +294,12 @@ class TypeTest: FSTIntegrationTestCase {
   func testCanReadAndWriteBsonFieldsInAnObject() async throws {
     _ = try await expectRoundtrip(
       coll: collectionRef(),
-      data: ["array": [
+      data: ["map": [
         "binary": BSONBinaryData(subtype: 1, data: Data([1, 2, 3])),
         "objectId": BSONObjectId("507f191e810c19729de860ea"),
         "bsonTimestamp": BSONTimestamp(seconds: 123, increment: 456),
         "int32": Int32Value(1),
+        "decimal128": Decimal128Value("-Infinity"),
         "min": MinKey.shared,
         "max": MaxKey.shared,
         "regex": RegexValue(pattern: "^foo", options: "i"),
@@ -303,6 +344,50 @@ class TypeTest: FSTIntegrationTestCase {
     }
   }
 
+  func testInvalidDecimal128ValuesGetsRejected() async throws {
+    let docRef = collectionRef().document("test-doc")
+    var errorMessage: String?
+
+    do {
+      try await docRef.setData(["key": Decimal128Value("")])
+      XCTFail("Expected error for invalid Decimal128Value")
+    } catch {
+      errorMessage = (error as NSError).userInfo[NSLocalizedDescriptionKey] as? String
+      XCTAssertNotNil(errorMessage)
+      XCTAssertTrue(errorMessage!.contains("Invalid decimal128 string"))
+    }
+
+    errorMessage = nil
+    do {
+      try await docRef.setData(["key": Decimal128Value("abc")])
+      XCTFail("Expected error for invalid Decimal128Value")
+    } catch {
+      errorMessage = (error as NSError).userInfo[NSLocalizedDescriptionKey] as? String
+      XCTAssertNotNil(errorMessage)
+      XCTAssertTrue(errorMessage!.contains("Invalid decimal128 string"))
+    }
+
+    errorMessage = nil
+    do {
+      try await docRef.setData(["key": Decimal128Value("1 23.45")])
+      XCTFail("Expected error for invalid Decimal128Value")
+    } catch {
+      errorMessage = (error as NSError).userInfo[NSLocalizedDescriptionKey] as? String
+      XCTAssertNotNil(errorMessage)
+      XCTAssertTrue(errorMessage!.contains("Invalid decimal128 string"))
+    }
+
+    errorMessage = nil
+    do {
+      try await docRef.setData(["key": Decimal128Value("1e1234567890")])
+      XCTFail("Expected error for invalid Decimal128Value")
+    } catch {
+      errorMessage = (error as NSError).userInfo[NSLocalizedDescriptionKey] as? String
+      XCTAssertNotNil(errorMessage)
+      XCTAssertTrue(errorMessage!.contains("Invalid decimal128 string"))
+    }
+  }
+
   func testCanOrderValuesOfDifferentTypeOrderTogether() async throws {
     let collection = collectionRef()
     let testDocs: [String: [String: Any?]] = [
@@ -310,9 +395,13 @@ class TypeTest: FSTIntegrationTestCase {
       "minValue": ["key": MinKey.shared],
       "booleanValue": ["key": true],
       "nanValue": ["key": Double.nan],
+      "nanValue2": ["key": Decimal128Value("NaN")],
+      "negativeInfinity": ["key": Decimal128Value("-Infinity")],
       "int32Value": ["key": Int32Value(1)],
       "doubleValue": ["key": 2.0],
       "integerValue": ["key": 3],
+      "decimal128Value": ["key": Decimal128Value("345e-2")],
+      "infinity": ["key": Decimal128Value("Infinity")],
       "timestampValue": ["key": Timestamp(seconds: 100, nanoseconds: 123_456_000)],
       "bsonTimestampValue": ["key": BSONTimestamp(seconds: 1, increment: 2)],
       "stringValue": ["key": "string"],
@@ -340,9 +429,13 @@ class TypeTest: FSTIntegrationTestCase {
       "minValue",
       "booleanValue",
       "nanValue",
+      "nanValue2",
+      "negativeInfinity",
       "int32Value",
       "doubleValue",
       "integerValue",
+      "decimal128Value",
+      "infinity",
       "timestampValue",
       "bsonTimestampValue",
       "stringValue",

+ 33 - 12
Firestore/core/src/index/firestore_index_value_writer.cc

@@ -202,6 +202,35 @@ void WriteIndexInt32Value(const google_firestore_v1_MapValue& map_index_value,
   encoder->WriteDouble(map_index_value.fields[0].value.integer_value);
 }
 
+void WriteIndexDoubleValue(double number,
+                           DirectionalIndexByteEncoder* encoder) {
+  if (std::isnan(number)) {
+    WriteValueTypeLabel(encoder, IndexType::kNan);
+    return;
+  }
+
+  WriteValueTypeLabel(encoder, IndexType::kNumber);
+  if (number == -0.0) {
+    // -0.0, 0 and 0.0 are all considered the same
+    encoder->WriteDouble(0.0);
+  } else {
+    encoder->WriteDouble(number);
+  }
+}
+
+void WriteIndexDecimal128Value(
+    const google_firestore_v1_MapValue& map_index_value,
+    DirectionalIndexByteEncoder* encoder) {
+  // Note: We currently give up some precision and store the 128-bit decimal as
+  // a 64-bit double for client-side indexing purposes. We could consider
+  // improving this in the future.
+  // Note: std::stod is able to parse 'NaN', '-NaN', 'Infinity' and '-Infinity',
+  // with different string cases.
+  const double number = std::stod(
+      nanopb::MakeString(map_index_value.fields[0].value.string_value));
+  WriteIndexDoubleValue(number, encoder);
+}
+
 void WriteIndexValueAux(const google_firestore_v1_Value& index_value,
                         DirectionalIndexByteEncoder* encoder) {
   switch (index_value.which_value_type) {
@@ -215,18 +244,7 @@ void WriteIndexValueAux(const google_firestore_v1_Value& index_value,
       break;
     }
     case google_firestore_v1_Value_double_value_tag: {
-      double number = index_value.double_value;
-      if (std::isnan(number)) {
-        WriteValueTypeLabel(encoder, IndexType::kNan);
-        break;
-      }
-      WriteValueTypeLabel(encoder, IndexType::kNumber);
-      if (number == -0.0) {
-        // -0.0, 0 and 0.0 are all considered the same
-        encoder->WriteDouble(0.0);
-      } else {
-        encoder->WriteDouble(number);
-      }
+      WriteIndexDoubleValue(index_value.double_value, encoder);
       break;
     }
     case google_firestore_v1_Value_integer_value_tag: {
@@ -292,6 +310,9 @@ void WriteIndexValueAux(const google_firestore_v1_Value& index_value,
       } else if (model::IsBsonObjectId(index_value)) {
         WriteIndexBsonObjectId(index_value.map_value, encoder);
         break;
+      } else if (model::IsDecimal128Value(index_value)) {
+        WriteIndexDecimal128Value(index_value.map_value, encoder);
+        break;
       } else if (model::IsInt32Value(index_value)) {
         WriteIndexInt32Value(index_value.map_value, encoder);
         break;

+ 96 - 6
Firestore/core/src/model/value_util.cc

@@ -30,6 +30,7 @@
 #include "Firestore/core/src/nanopb/nanopb_util.h"
 #include "Firestore/core/src/util/comparison.h"
 #include "Firestore/core/src/util/hard_assert.h"
+#include "Firestore/core/src/util/quadruple.h"
 #include "absl/strings/escaping.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/str_join.h"
@@ -41,6 +42,7 @@ namespace model {
 
 using nanopb::Message;
 using util::ComparisonResult;
+using util::Quadruple;
 
 /** The smallest reference value. */
 pb_bytes_array_s* kMinimumReferenceValue =
@@ -96,6 +98,11 @@ const char* kRawInt32TypeFieldValue = "__int__";
 pb_bytes_array_s* kInt32TypeFieldValue =
     nanopb::MakeBytesArray(kRawInt32TypeFieldValue);
 
+/** The key of a decimal128 in a map proto. */
+const char* kRawDecimal128TypeFieldValue = "__decimal128__";
+pb_bytes_array_s* kDecimal128TypeFieldValue =
+    nanopb::MakeBytesArray(kRawDecimal128TypeFieldValue);
+
 /** The key of a BSON ObjectId in a map proto. */
 const char* kRawBsonObjectIdTypeFieldValue = "__oid__";
 pb_bytes_array_s* kBsonObjectIdTypeFieldValue =
@@ -148,6 +155,8 @@ MapType DetectMapType(const google_firestore_v1_Value& value) {
     return MapType::kMaxKey;
   } else if (IsRegexValue(value)) {
     return MapType::kRegex;
+  } else if (IsDecimal128Value(value)) {
+    return MapType::kDecimal128;
   } else if (IsInt32Value(value)) {
     return MapType::kInt32;
   } else if (IsBsonObjectId(value)) {
@@ -206,6 +215,7 @@ TypeOrder GetTypeOrder(const google_firestore_v1_Value& value) {
         case MapType::kRegex:
           return TypeOrder::kRegex;
         case MapType::kInt32:
+        case MapType::kDecimal128:
           return TypeOrder::kNumber;
         case MapType::kBsonObjectId:
           return TypeOrder::kBsonObjectId;
@@ -251,8 +261,54 @@ void SortFields(google_firestore_v1_Value& value) {
   }
 }
 
+Quadruple ConvertNumericValueToQuadruple(
+    const google_firestore_v1_Value& value) {
+  if (value.which_value_type == google_firestore_v1_Value_double_value_tag) {
+    return Quadruple(value.double_value);
+  } else if (value.which_value_type ==
+             google_firestore_v1_Value_integer_value_tag) {
+    return Quadruple(value.integer_value);
+  } else if (IsInt32Value(value)) {
+    return Quadruple(value.map_value.fields[0].value.integer_value);
+  } else if (IsDecimal128Value(value)) {
+    Quadruple result;
+    result.Parse(
+        nanopb::MakeString(value.map_value.fields[0].value.string_value));
+    return result;
+  }
+
+  HARD_FAIL(
+      "ConvertNumericValueToQuadruple was called with non-numeric value: %s",
+      value.ToString());
+}
+
+ComparisonResult Compare128BitNumbers(const google_firestore_v1_Value& left,
+                                      const google_firestore_v1_Value& right) {
+  Quadruple lhs = ConvertNumericValueToQuadruple(left);
+  Quadruple rhs = ConvertNumericValueToQuadruple(right);
+  if (lhs.is_nan()) {
+    return rhs.is_nan() ? ComparisonResult::Same : ComparisonResult::Ascending;
+  } else if (rhs.is_nan()) {
+    // rhs is NaN, but lhs is not.
+    return ComparisonResult::Descending;
+  }
+
+  // Firestore considers +0 and -0 equal, but `Quadruple.Compare()` does not.
+  // SO, override negative zero to positive zero.
+  if (lhs.Compare(Quadruple(-0.0)) == 0) lhs = Quadruple();
+  if (rhs.Compare(Quadruple(-0.0)) == 0) rhs = Quadruple();
+
+  // Since `Compare` returns `-1`, `0`, and `1` with the same semantics as the
+  // `ComparisonResult` enum, we can safely cast it.
+  return static_cast<ComparisonResult>(lhs.Compare(rhs));
+}
+
 ComparisonResult CompareNumbers(const google_firestore_v1_Value& left,
                                 const google_firestore_v1_Value& right) {
+  if (IsDecimal128Value(left) || IsDecimal128Value(right)) {
+    return Compare128BitNumbers(left, right);
+  }
+
   if (left.which_value_type == google_firestore_v1_Value_double_value_tag) {
     double left_double = left.double_value;
     if (right.which_value_type == google_firestore_v1_Value_double_value_tag) {
@@ -655,7 +711,10 @@ bool NumberEquals(const google_firestore_v1_Value& left,
   } else if (IsInt32Value(left) && IsInt32Value(right)) {
     return left.map_value.fields[0].value.integer_value ==
            right.map_value.fields[0].value.integer_value;
+  } else if (IsDecimal128Value(left) && IsDecimal128Value(right)) {
+    return Compare128BitNumbers(left, right) == util::ComparisonResult::Same;
   }
+
   return false;
 }
 
@@ -906,8 +965,9 @@ google_firestore_v1_Value GetLowerBound(
         return MinBsonBinaryData();
       } else if (IsRegexValue(value)) {
         return MinRegex();
-      } else if (IsInt32Value(value)) {
-        // int32Value is treated the same as integerValue and doubleValue.
+      } else if (IsInt32Value(value) || IsDecimal128Value(value)) {
+        // Int32Value and Decimal128Value are treated the same as integerValue
+        // and doubleValue.
         return MinNumber();
       } else if (IsMinKeyValue(value)) {
         return MinKeyValue();
@@ -950,8 +1010,9 @@ google_firestore_v1_Value GetUpperBound(
         return MinMap();
       } else if (IsMinKeyValue(value)) {
         return MinBoolean();
-      } else if (IsInt32Value(value)) {
-        // int32Value is treated the same as integerValue and doubleValue.
+      } else if (IsInt32Value(value) || IsDecimal128Value(value)) {
+        // Int32Value and Decimal128Value are treated the same as integerValue
+        // and doubleValue.
         return MinTimestamp();
       } else if (IsBsonTimestamp(value)) {
         return MinString();
@@ -1372,11 +1433,40 @@ bool IsInt32Value(const google_firestore_v1_Value& value) {
   return true;
 }
 
+bool IsDecimal128Value(const google_firestore_v1_Value& value) {
+  // A Decimal128Value is expected to be a map as follows:
+  // {
+  //   "__decimal128__": 12345
+  // }
+
+  // Must be a map with 1 field.
+  if (value.which_value_type != google_firestore_v1_Value_map_value_tag ||
+      value.map_value.fields_count != 1) {
+    return false;
+  }
+
+  // Must have a "__decimal128__" key.
+  absl::optional<pb_size_t> field_index = IndexOfKey(
+      value.map_value, kRawDecimal128TypeFieldValue, kDecimal128TypeFieldValue);
+  if (!field_index.has_value()) {
+    return false;
+  }
+
+  // Must have a string value.
+  google_firestore_v1_Value& decimal_str = value.map_value.fields[0].value;
+  if (decimal_str.which_value_type !=
+      google_firestore_v1_Value_string_value_tag) {
+    return false;
+  }
+
+  return true;
+}
+
 bool IsBsonType(const google_firestore_v1_Value& value) {
-  MapType mapType = DetectMapType(value);
+  const MapType mapType = DetectMapType(value);
   return mapType == MapType::kMinKey || mapType == MapType::kMaxKey ||
          mapType == MapType::kRegex || mapType == MapType::kInt32 ||
-         mapType == MapType::kBsonObjectId ||
+         mapType == MapType::kDecimal128 || mapType == MapType::kBsonObjectId ||
          mapType == MapType::kBsonTimestamp ||
          mapType == MapType::kBsonBinaryData;
 }

+ 13 - 3
Firestore/core/src/model/value_util.h

@@ -81,6 +81,10 @@ extern pb_bytes_array_s* kRegexTypeOptionsFieldValue;
 extern const char* kRawInt32TypeFieldValue;
 extern pb_bytes_array_s* kInt32TypeFieldValue;
 
+/** The key of a decimal128 in a map proto. */
+extern const char* kRawDecimal128TypeFieldValue;
+extern pb_bytes_array_s* kDecimal128TypeFieldValue;
+
 /** The key of a BSON ObjectId in a map proto. */
 extern const char* kRawBsonObjectIdTypeFieldValue;
 extern pb_bytes_array_s* kBsonObjectIdTypeFieldValue;
@@ -145,9 +149,10 @@ enum class MapType {
   kMaxKey = 5,
   kRegex = 6,
   kInt32 = 7,
-  kBsonObjectId = 8,
-  kBsonTimestamp = 9,
-  kBsonBinaryData = 10
+  kDecimal128 = 8,
+  kBsonObjectId = 9,
+  kBsonTimestamp = 10,
+  kBsonBinaryData = 11
 };
 
 /** Returns the Map type for the given value. */
@@ -280,6 +285,11 @@ bool IsRegexValue(const google_firestore_v1_Value& value);
  */
 bool IsInt32Value(const google_firestore_v1_Value& value);
 
+/**
+ * Returns `true` if `value` represents a Decimal128Value.
+ */
+bool IsDecimal128Value(const google_firestore_v1_Value& value);
+
 /**
  * Returns `true` if `value` represents a BsonObjectId.
  */

+ 244 - 0
Firestore/core/src/util/quadruple.cc

@@ -0,0 +1,244 @@
+//  Copyright 2025 Google LLC
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+#include "quadruple.h"
+#include <ctype.h>
+#include <stdint.h>
+#include <cmath>
+#include <limits>
+#include "quadruple_builder.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace {
+constexpr int64_t kHashCodeOfNan = 7652541255;
+}
+
+Quadruple::Quadruple(double x) {
+  negative_ = signbit(x);
+  switch (fpclassify(x)) {
+    case FP_NAN:
+      negative_ = false;
+      exponent_ = kInfiniteExponent;
+      mantissa_hi_ = 1ULL << 63;
+      mantissa_lo_ = 0;
+      break;
+    case FP_INFINITE:
+      exponent_ = kInfiniteExponent;
+      mantissa_hi_ = 0;
+      mantissa_lo_ = 0;
+      break;
+    case FP_ZERO:
+      exponent_ = 0;
+      mantissa_hi_ = 0;
+      mantissa_lo_ = 0;
+      break;
+    case FP_SUBNORMAL:
+    case FP_NORMAL:
+      negative_ = x < 0;
+      int x_exponent;
+      double small = frexp(std::abs(x), &x_exponent);
+      exponent_ = static_cast<uint32_t>(x_exponent - 1) + kExponentBias;
+      // Scale 'small' to its 53-bit mantissa value as a long, then left-justify
+      // it with the leading 1 bit dropped in mantissa_hi (65-53=12).
+      mantissa_hi_ = static_cast<uint64_t>(ldexp(small, 53)) << 12;
+      mantissa_lo_ = 0;
+      break;
+  }
+}
+Quadruple::Quadruple(int64_t x) {
+  if (x == 0) {
+    negative_ = false;
+    exponent_ = 0;
+    mantissa_hi_ = 0;
+    mantissa_lo_ = 0;
+  } else if (x == std::numeric_limits<int64_t>::min()) {
+    // -2^63 cannot be negated, so special-case it.
+    negative_ = true;
+    exponent_ = 63 + kExponentBias;
+    mantissa_hi_ = 0;
+    mantissa_lo_ = 0;
+  } else {
+    negative_ = x < 0;
+    if (negative_) {
+      x = -x;
+    }
+    if (x == 1) {
+      // The shift below wraps around for x=1, so special-case it.
+      exponent_ = kExponentBias;
+      mantissa_hi_ = 0;
+      mantissa_lo_ = 0;
+    } else {
+      uint64_t ux = static_cast<uint64_t>(x);
+      int leading_zeros = __builtin_clzll(ux);
+      // Left-justify with the leading 1 dropped.
+      mantissa_hi_ = ux << (leading_zeros + 1);
+      mantissa_lo_ = 0;
+      exponent_ = static_cast<uint32_t>(63 - leading_zeros) + kExponentBias;
+    }
+  }
+}
+bool Quadruple::Parse(std::string s) {
+  if (s == "NaN") {
+    negative_ = false;
+    exponent_ = kInfiniteExponent;
+    mantissa_hi_ = 1LL << 63;
+    mantissa_lo_ = 0;
+    return true;
+  }
+  if (s == "-Infinity") {
+    negative_ = true;
+    exponent_ = kInfiniteExponent;
+    mantissa_hi_ = 0;
+    mantissa_lo_ = 0;
+    return true;
+  }
+  if (s == "Infinity" || s == "+Infinity") {
+    negative_ = false;
+    exponent_ = kInfiniteExponent;
+    mantissa_hi_ = 0;
+    mantissa_lo_ = 0;
+    return true;
+  }
+  bool negative = false;
+  int len = s.size();
+  uint8_t* digits = new uint8_t[len];
+  int i = 0;
+  int j = 0;
+  int64_t exponent = 0;
+  if (i < len) {
+    if (s[i] == '-') {
+      negative = true;
+      i++;
+    } else if (s[i] == '+') {
+      i++;
+    }
+  }
+  while (i < len && isdigit(s[i])) {
+    digits[j++] = static_cast<uint8_t>(s[i++] - '0');
+  }
+  if (i < len && s[i] == '.') {
+    int decimal = ++i;
+    while (i < len && isdigit(s[i])) {
+      digits[j++] = static_cast<uint8_t>(s[i++] - '0');
+    }
+    exponent = decimal - i;
+  }
+  if (i < len && (s[i] == 'e' || s[i] == 'E')) {
+    int64_t exponentValue = 0;
+    i++;
+    int exponentSign = 1;
+    if (i < len) {
+      if (s[i] == '-') {
+        exponentSign = -1;
+        i++;
+      } else if (s[i] == '+') {
+        i++;
+      }
+    }
+    int firstExponent = i;
+    while (i < len && isdigit(s[i])) {
+      exponentValue = exponentValue * 10 + s[i++] - '0';
+      if (i - firstExponent > 9) {
+        return false;
+      }
+    }
+    if (i == firstExponent) {
+      return false;
+    }
+    exponent += exponentValue * exponentSign;
+  }
+  if (j == 0 || i != len) {
+    return false;
+  }
+  std::vector<uint8_t> digits_copy(j);
+  for (int k = 0; k < j; k++) {
+    digits_copy[k] = digits[k];
+  }
+  QuadrupleBuilder parsed;
+  parsed.parseDecimal(digits_copy, exponent);
+  negative_ = negative;
+  exponent_ = parsed.exponent;
+  mantissa_hi_ = parsed.mantHi;
+  mantissa_lo_ = parsed.mantLo;
+  return true;
+}
+// Compare two quadruples, with -0 < 0, and NaNs larger than all numbers.
+int Quadruple::Compare(const Quadruple& other) const {
+  int lessThan;
+  int greaterThan;
+  if (negative_) {
+    if (!other.negative_) {
+      return -1;
+    }
+    lessThan = 1;
+    greaterThan = -1;
+  } else {
+    if (other.negative_) {
+      return 1;
+    }
+    lessThan = -1;
+    greaterThan = 1;
+  }
+  if (exponent_ < other.exponent_) {
+    return lessThan;
+  } else if (exponent_ > other.exponent_) {
+    return greaterThan;
+  } else if (mantissa_hi_ < other.mantissa_hi_) {
+    return lessThan;
+  } else if (mantissa_hi_ > other.mantissa_hi_) {
+    return greaterThan;
+  } else if (mantissa_lo_ < other.mantissa_lo_) {
+    return lessThan;
+  } else if (mantissa_lo_ > other.mantissa_lo_) {
+    return greaterThan;
+  } else {
+    return 0;
+  }
+}
+Quadruple::operator double() const {
+  switch (exponent_) {
+    case 0:
+      // zero or Quadruple subnormal
+      return negative_ ? -0.0 : 0.0;
+    case kInfiniteExponent: {
+      if (is_nan()) {
+        return NAN;
+      }
+      return negative_ ? -INFINITY : INFINITY;
+    }
+    default:
+      int32_t unbiased_exp = static_cast<int32_t>(exponent_ - kExponentBias);
+      return scalb((1LL << 52) | (mantissa_hi_ >> 12), -52 + unbiased_exp) *
+             (negative_ ? -1 : 1);
+  }
+}
+int64_t Quadruple::HashValue() const {
+  if (is_nan()) {
+    return kHashCodeOfNan;
+  }
+  const int64_t prime = 31;
+  int64_t result = 1;
+  result = prime * result + static_cast<uint64_t>(exponent_);
+  result = prime * result + static_cast<uint64_t>(mantissa_hi_);
+  result = prime * result + static_cast<uint64_t>(mantissa_lo_);
+  result = prime * result + (negative_ ? 1231 : 1237);
+  return result;
+}
+
+}  // namespace util
+}  // namespace firestore
+}  // namespace firebase

+ 100 - 0
Firestore/core/src/util/quadruple.h

@@ -0,0 +1,100 @@
+//  Copyright 2025 Google LLC
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+#include <stdint.h>
+#include <iomanip>
+#include <ios>
+#include <sstream>
+#include <string>
+
+#ifndef FIRESTORE_CORE_UTIL_QUADRUPLE_H_
+#define FIRESTORE_CORE_UTIL_QUADRUPLE_H_
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+// A minimal C++ implementation of a 128-bit mantissa / 32-bit exponent binary
+// floating point number equivalent to https://github.com/m-vokhm/Quadruple
+//
+// Supports:
+// - creation from string
+// - creation from serialised format (3 longs), long and double
+// - comparisons
+class Quadruple {
+ public:
+  // Initialises a Quadruple to +0.0
+  Quadruple() : Quadruple(0, 0, 0) {
+  }
+
+  Quadruple(uint64_t exponent_and_sign,
+            uint64_t mantissa_hi,
+            uint64_t mantissa_lo)
+      : negative_(exponent_and_sign >> 63),
+        exponent_(static_cast<uint32_t>(exponent_and_sign)),
+        mantissa_hi_(mantissa_hi),
+        mantissa_lo_(mantissa_lo) {
+  }
+  explicit Quadruple(double x);
+  explicit Quadruple(int64_t x);
+  // Updates this Quadruple with the decimal number specified in s.
+  // Returns true for valid numbers, false for invalid numbers.
+  // The Quadruple is unchanged if the result is false.
+  //
+  // The supported format (no whitespace allowed) is:
+  // - NaN, Infinity, +Infinity, -Infinity for the corresponding constants
+  // - a string matching [+-]?[0-9]*(.[0-9]*)?([eE][+-]?[0-9]+)?
+  //   with the exponent at most 9 characters, and the whole string not empty
+  bool Parse(std::string s);
+  // Rounds out-of-range numbers to +/- 0/HUGE_VAL. Rounds towards 0.
+  explicit operator double() const;
+  // Compare two quadruples, with -0 < 0, and NaNs larger than all numbers.
+  int Compare(const Quadruple& other) const;
+  bool operator==(const Quadruple& other) const {
+    return Compare(other) == 0;
+  }
+  bool is_nan() const {
+    return (exponent_ == kInfiniteExponent) &&
+           !(mantissa_hi_ == 0 && mantissa_lo_ == 0);
+  }
+  // The actual exponent is exponent_-kExponentBias.
+  static const uint32_t kExponentBias = 0x7FFFFFFF;
+  int64_t HashValue() const;
+  std::string DebugString() {
+    std::stringstream out;
+    if (negative_) {
+      out << "-";
+    }
+    out << "1x" << std::hex << std::setfill('0');
+    out << std::setw(16) << mantissa_hi_;
+    out << std::setw(16) << mantissa_lo_;
+    out << "*2^" << std::dec << exponent_ - static_cast<int64_t>(kExponentBias);
+    out << " =~ " << static_cast<double>(*this);
+
+    return out.str();
+  }
+
+ private:
+  static const uint32_t kInfiniteExponent = 0xFFFFFFFF;  // including its bias
+  bool negative_;
+  uint32_t exponent_;
+  uint64_t mantissa_hi_;
+  uint64_t mantissa_lo_;
+};
+
+}  // namespace util
+}  // namespace firestore
+}  // namespace firebase
+
+#endif  // FIRESTORE_CORE_UTIL_QUADRUPLE_H_

+ 913 - 0
Firestore/core/src/util/quadruple_builder.cc

@@ -0,0 +1,913 @@
+//  Copyright 2025 Google LLC
+//  Copyright 2021 M.Vokhmentsev
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+#include "quadruple_builder.h"
+
+#include <array>
+#include <cmath>
+#include <limits>
+
+namespace firebase {
+namespace firestore {
+namespace util {
+// 2^192 = 6.277e57, so the 58-th digit after point may affect the result
+static constexpr int32_t MAX_MANTISSA_LENGTH = 59;
+// Max value of the decimal exponent, corresponds to EXPONENT_OF_MAX_VALUE
+static constexpr int32_t MAX_EXP10 = 646456993;
+// Min value of the decimal exponent, corresponds to EXPONENT_OF_MIN_NORMAL
+static constexpr int32_t MIN_EXP10 = -646457032;
+// (2^63) / 10 =~ 9.223372e17
+static constexpr double TWO_POW_63_DIV_10 = 922337203685477580.0;
+// Just for convenience: 0x8000_0000_0000_0000L
+// static constexpr uint64_t HIGH_BIT = 0x8000000000000000L;
+// Just for convenience: 0x8000_0000L, 2^31
+static constexpr double POW_2_31 = 2147483648.0;
+// Just for convenience: 0x0000_0000_FFFF_FFFFL
+static constexpr uint64_t LOWER_32_BITS = 0x00000000FFFFFFFFL;
+// Just for convenience: 0xFFFF_FFFF_0000_0000L;
+static constexpr uint64_t HIGHER_32_BITS = 0xFFFFFFFF00000000L;
+// Approximate value of log<sub>2</sub>(10)
+static const double LOG2_10 = log(10) / log(2);
+// Approximate value of log<sub>2</sub>(e)
+static const double LOG2_E = 1 / log(2.0);
+// The value of the exponent (biased) corresponding to {@code 1.0 == 2^0};
+// equals to 2_147_483_647
+// ({@code 0x7FFF_FFFF}).
+static constexpr int32_t EXPONENT_BIAS = 0x7FFFFFFF;
+// The value of the exponent (biased), corresponding to {@code Infinity}, {@code
+// _Infinty}, and
+// {@code NaN}
+static constexpr uint64_t EXPONENT_OF_INFINITY = 0xFFFFFFFFL;
+// An array of positive powers of two, each value consists of 4 longs: decimal
+// exponent and 3 x 64 bits of mantissa, divided by ten Used to find an
+// arbitrary power of 2 (by powerOfTwo(long exp))
+static std::array<std::array<uint64_t, 4>, 33> POS_POWERS_OF_2 = {
+    {// 0: 2^0 =   1 = 0.1e1
+     {{static_cast<uint64_t>(1), 0x1999999999999999LL, 0x9999999999999999LL,
+       0x999999999999999aLL}},  // 1: 2^(2^0) =   2^1 =   2 = 0.2e1
+     {{static_cast<uint64_t>(1), 0x3333333333333333LL, 0x3333333333333333LL,
+       0x3333333333333334LL}},  // ***
+                                // 2: 2^(2^1) =   2^2 =   4 = 0.4e1
+     {{static_cast<uint64_t>(1), 0x6666666666666666LL, 0x6666666666666666LL,
+       0x6666666666666667LL}},  // ***
+                                // 3: 2^(2^2) =   2^4 =   16 = 0.16e2
+     {{static_cast<uint64_t>(2), 0x28f5c28f5c28f5c2LL, 0x8f5c28f5c28f5c28LL,
+       0xf5c28f5c28f5c290LL}},  // ***
+                                // 4: 2^(2^3) =   2^8 =   256 = 0.256e3
+     {{static_cast<uint64_t>(3), 0x4189374bc6a7ef9dLL, 0xb22d0e5604189374LL,
+       0xbc6a7ef9db22d0e6LL}},  // ***
+                                // 5: 2^(2^4) =   2^16 =   65536 = 0.65536e5
+     {{static_cast<uint64_t>(5), 0xa7c5ac471b478423LL, 0x0fcf80dc33721d53LL,
+       0xcddd6e04c0592104LL}},  // 6: 2^(2^5) =   2^32 =   4294967296 =
+                                // 0.4294967296e10
+     {{static_cast<uint64_t>(10), 0x6df37f675ef6eadfLL, 0x5ab9a2072d44268dLL,
+       0x97df837e6748956eLL}},  // 7: 2^(2^6) =   2^64 =   18446744073709551616
+                                // = 0.18446744073709551616e20
+     {{static_cast<uint64_t>(20), 0x2f394219248446baLL, 0xa23d2ec729af3d61LL,
+       0x0607aa0167dd94cbLL}},  // 8: 2^(2^7) =   2^128 =
+                                // 340282366920938463463374607431768211456 =
+                                // 0.340282366920938463463374607431768211456e39
+     {{static_cast<uint64_t>(39), 0x571cbec554b60dbbLL, 0xd5f64baf0506840dLL,
+       0x451db70d5904029bLL}},  // 9: 2^(2^8) =   2^256 =
+                                // 1.1579208923731619542357098500868790785326998466564056403945758401E+77
+                                // =
+                                // 0.11579208923731619542357098500868790785326998466564056403945758401e78
+     {{static_cast<uint64_t>(78), 0x1da48ce468e7c702LL, 0x6520247d3556476dLL,
+       0x1469caf6db224cfaLL}},  // ***
+                                // 10: 2^(2^9) =   2^512 =
+                                // 1.3407807929942597099574024998205846127479365820592393377723561444E+154
+                                // =
+                                // 0.13407807929942597099574024998205846127479365820592393377723561444e155
+     {{static_cast<uint64_t>(155), 0x2252f0e5b39769dcLL, 0x9ae2eea30ca3ade0LL,
+       0xeeaa3c08dfe84e30LL}},  // 11: 2^(2^10) =   2^1024 =
+                                // 1.7976931348623159077293051907890247336179769789423065727343008116E+308
+                                // =
+                                // 0.17976931348623159077293051907890247336179769789423065727343008116e309
+     {{static_cast<uint64_t>(309), 0x2e055c9a3f6ba793LL, 0x16583a816eb60a59LL,
+       0x22c4b0826cf1ebf7LL}},  // 12: 2^(2^11) =   2^2048 =
+                                // 3.2317006071311007300714876688669951960444102669715484032130345428E+616
+                                // =
+                                // 0.32317006071311007300714876688669951960444102669715484032130345428e617
+     {{static_cast<uint64_t>(617), 0x52bb45e9cf23f17fLL, 0x7688c07606e50364LL,
+       0xb34479aa9d449a57LL}},  // 13: 2^(2^12) =   2^4096 =
+                                // 1.0443888814131525066917527107166243825799642490473837803842334833E+1233
+                                // =
+                                // 0.10443888814131525066917527107166243825799642490473837803842334833e1234
+     {{static_cast<uint64_t>(1234), 0x1abc81c8ff5f846cLL, 0x8f5e3c9853e38c97LL,
+       0x45060097f3bf9296LL}},  // 14: 2^(2^13) =   2^8192 =
+                                // 1.0907481356194159294629842447337828624482641619962326924318327862E+2466
+                                // =
+                                // 0.10907481356194159294629842447337828624482641619962326924318327862e2467
+     {{static_cast<uint64_t>(2467), 0x1bec53b510daa7b4LL, 0x48369ed77dbb0eb1LL,
+       0x3b05587b2187b41eLL}},  // 15: 2^(2^14) =   2^16384 =
+                                // 1.1897314953572317650857593266280071307634446870965102374726748212E+4932
+                                // =
+                                // 0.11897314953572317650857593266280071307634446870965102374726748212e4933
+     {{static_cast<uint64_t>(4933), 0x1e75063a5ba91326LL, 0x8abfb8e460016ae3LL,
+       0x28008702d29e8a3cLL}},  // 16: 2^(2^15) =   2^32768 =
+                                // 1.4154610310449547890015530277449516013481307114723881672343857483E+9864
+                                // =
+                                // 0.14154610310449547890015530277449516013481307114723881672343857483e9865
+     {{static_cast<uint64_t>(9865), 0x243c5d8bb5c5fa55LL, 0x40c6d248c5881915LL,
+       0x4c0fd99fd5befc22LL}},  // 17: 2^(2^16) =   2^65536 =
+                                // 2.0035299304068464649790723515602557504478254755697514192650169737E+19728
+                                // =
+                                // 0.20035299304068464649790723515602557504478254755697514192650169737e19729
+     {{static_cast<uint64_t>(19729), 0x334a5570c3f4ef3cLL, 0xa13c36c43f979c90LL,
+       0xda7ac473555fb7a8LL}},  // 18: 2^(2^17) =   2^131072 =
+                                // 4.0141321820360630391660606060388767343771510270414189955825538065E+39456
+                                // =
+                                // 0.40141321820360630391660606060388767343771510270414189955825538065e39457
+     {{static_cast<uint64_t>(39457), 0x66c304445dd98f3bLL, 0xa8c293a20e47a41bLL,
+       0x4c5b03dc12604964LL}},  // 19: 2^(2^18) =   2^262144 =
+                                // 1.6113257174857604736195721184520050106440238745496695174763712505E+78913
+                                // =
+                                // 0.16113257174857604736195721184520050106440238745496695174763712505e78914
+     {{static_cast<uint64_t>(78914), 0x293ffbf5fb028cc4LL, 0x89d3e5ff44238406LL,
+       0x369a339e1bfe8c9bLL}},  // 20: 2^(2^19) =   2^524288 =
+                                // 2.5963705678310007761265964957268828277447343763484560463573654868E+157826
+                                // =
+                                // 0.25963705678310007761265964957268828277447343763484560463573654868e157827
+     {{static_cast<uint64_t>(157827), 0x427792fbb68e5d20LL,
+       0x7b297cd9fc154b62LL,
+       0xf09142114aa9a20cLL}},  // 21: 2^(2^20) =   2^1048576 =
+                                // 6.7411401254990734022690651047042454376201859485326882846944915676E+315652
+                                // =
+                                // 0.67411401254990734022690651047042454376201859485326882846944915676e315653
+     {{static_cast<uint64_t>(315653), 0xac92bc65ad5c08fcLL,
+       0x00beeb115a566c19LL,
+       0x4ba882d8a4622437LL}},  // 22: 2^(2^21) =   2^2097152 =
+                                // 4.5442970191613663099961595907970650433180103994591456270882095573E+631305
+                                // =
+                                // 0.45442970191613663099961595907970650433180103994591456270882095573e631306
+     {{static_cast<uint64_t>(631306), 0x745581440f92e80eLL,
+       0x4da822cf7f896f41LL,
+       0x509d598678164ecdLL}},  // 23: 2^(2^22) =   2^4194304 =
+                                // 2.0650635398358879243991194945816501695274360493029670347841664177E+1262611
+                                // =
+                                // 0.20650635398358879243991194945816501695274360493029670347841664177e1262612
+     {{static_cast<uint64_t>(1262612), 0x34dd99b4c69523a5LL,
+       0x64bc2e8f0d8b1044LL,
+       0xb03b1c96da5dd349LL}},  // 24: 2^(2^23) =   2^8388608 =
+                                // 4.2644874235595278724327289260856157547554200794957122157246170406E+2525222
+                                // =
+                                // 0.42644874235595278724327289260856157547554200794957122157246170406e2525223
+     {{static_cast<uint64_t>(2525223), 0x6d2bbea9d6d25a08LL,
+       0xa0a4606a88e96b70LL,
+       0x182063bbc2fe8520LL}},  // 25: 2^(2^24) =   2^16777216 =
+                                // 1.8185852985697380078927713277749906189248596809789408311078112486E+5050445
+                                // =
+                                // 0.18185852985697380078927713277749906189248596809789408311078112486e5050446
+     {{static_cast<uint64_t>(5050446), 0x2e8e47d63bfdd6e3LL,
+       0x2b55fa8976eaa3e9LL,
+       0x1a6b9d3086412a73LL}},  // 26: 2^(2^25) =   2^33554432 =
+                                // 3.3072524881739831340558051919726975471129152081195558970611353362E+10100890
+                                // =
+                                // 0.33072524881739831340558051919726975471129152081195558970611353362e10100891
+     {{static_cast<uint64_t>(10100891), 0x54aa68efa1d719dfLL,
+       0xd8505806612c5c8fLL,
+       0xad068837fee8b43aLL}},  // 27: 2^(2^26) =   2^67108864 =
+                                // 1.0937919020533002449982468634925923461910249420785622990340704603E+20201781
+                                // =
+                                // 0.10937919020533002449982468634925923461910249420785622990340704603e20201782
+     {{static_cast<uint64_t>(20201782), 0x1c00464ccb7bae77LL,
+       0x9e3877784c77982cLL,
+       0xd94af3b61717404fLL}},  // 28: 2^(2^27) =   2^134217728 =
+                                // 1.1963807249973763567102377630870670302911237824129274789063323723E+40403562
+                                // =
+                                // 0.11963807249973763567102377630870670302911237824129274789063323723e40403563
+     {{static_cast<uint64_t>(40403563), 0x1ea099c8be2b6cd0LL,
+       0x8bfb6d539fa50466LL,
+       0x6d3bc37e69a84218LL}},  // 29: 2^(2^28) =   2^268435456 =
+                                // 1.4313268391452478724777126233530788980596273340675193575004129517E+80807124
+                                // =
+                                // 0.14313268391452478724777126233530788980596273340675193575004129517e80807125
+     {{static_cast<uint64_t>(80807125), 0x24a457f466ce8d18LL,
+       0xf2c8f3b81bc6bb59LL,
+       0xa78c757692e02d49LL}},  // 30: 2^(2^29) =   2^536870912 =
+                                // 2.0486965204575262773910959587280218683219330308711312100181276813E+161614248
+                                // =
+                                // 0.20486965204575262773910959587280218683219330308711312100181276813e161614249
+     {{static_cast<uint64_t>(161614249), 0x347256677aba6b53LL,
+       0x3fbf90d30611a67cLL,
+       0x1e039d87e0bdb32bLL}},  // 31: 2^(2^30) =   2^1073741824 =
+                                // 4.1971574329347753848087162337676781412761959309467052555732924370E+323228496
+                                // =
+                                // 0.41971574329347753848087162337676781412761959309467052555732924370e323228497
+     {{static_cast<uint64_t>(323228497), 0x6b727daf0fd3432aLL,
+       0x71f71121f9e4200fLL,
+       0x8fcd9942d486c10cLL}},  // 32: 2^(2^31) =   2^2147483648 =
+                                // 1.7616130516839633532074931497918402856671115581881347960233679023E+646456993
+                                // =
+                                // 0.17616130516839633532074931497918402856671115581881347960233679023e646456994
+     {{static_cast<uint64_t>(646456994), 0x2d18e84484d91f78LL,
+       0x4079bfe7829dec6fLL, 0x21551643e365abc6LL}}}};
+// An array of negative powers of two, each value consists of 4 longs: decimal
+// exponent and 3 x 64 bits of mantissa, divided by ten. Used to find an
+// arbitrary power of 2 (by powerOfTwo(long exp))
+static std::array<std::array<uint64_t, 4>, 33> NEG_POWERS_OF_2 = {
+    {// v18
+     // 0: 2^0 =   1 = 0.1e1
+     {{static_cast<uint64_t>(1), 0x1999999999999999LL, 0x9999999999999999LL,
+       0x999999999999999aLL}},  // 1: 2^-(2^0) =   2^-1 =   0.5 = 0.5e0
+     {{static_cast<uint64_t>(0), 0x8000000000000000LL, 0x0000000000000000LL,
+       0x0000000000000000LL}},  // 2: 2^-(2^1) =   2^-2 =   0.25 = 0.25e0
+                                //      {0, 0x4000_0000_0000_0000L,
+                                //      0x0000_0000_0000_0000L,
+                                //      0x0000_0000_0000_0000L},
+     {{static_cast<uint64_t>(0), 0x4000000000000000LL, 0x0000000000000000LL,
+       0x0000000000000001LL}},  // ***
+                                // 3: 2^-(2^2) =   2^-4 =   0.0625 = 0.625e-1
+     {{static_cast<uint64_t>(-1), 0xa000000000000000LL, 0x0000000000000000LL,
+       0x0000000000000000LL}},  // 4: 2^-(2^3) =   2^-8 =   0.00390625 =
+                                // 0.390625e-2
+     {{static_cast<uint64_t>(-2), 0x6400000000000000LL, 0x0000000000000000LL,
+       0x0000000000000000LL}},  // 5: 2^-(2^4) =   2^-16 =   0.0000152587890625
+                                // = 0.152587890625e-4
+     {{static_cast<uint64_t>(-4), 0x2710000000000000LL, 0x0000000000000000LL,
+       0x0000000000000001LL}},  // ***
+                                // 6: 2^-(2^5) =   2^-32
+                                // =   2.3283064365386962890625E-10 =
+                                // 0.23283064365386962890625e-9
+     {{static_cast<uint64_t>(-9), 0x3b9aca0000000000LL, 0x0000000000000000LL,
+       0x0000000000000001LL}},  // ***
+                                // 7: 2^-(2^6) =   2^-64
+                                // =   5.42101086242752217003726400434970855712890625E-20
+                                // =
+                                // 0.542101086242752217003726400434970855712890625e-19
+     {{static_cast<uint64_t>(-19), 0x8ac7230489e80000LL, 0x0000000000000000LL,
+       0x0000000000000000LL}},  // 8: 2^-(2^7) =   2^-128 =
+                                // 2.9387358770557187699218413430556141945466638919302188037718792657E-39
+                                // =
+                                // 0.29387358770557187699218413430556141945466638919302188037718792657e-38
+     {{static_cast<uint64_t>(-38), 0x4b3b4ca85a86c47aLL, 0x098a224000000000LL,
+       0x0000000000000001LL}},  // ***
+                                // 9: 2^-(2^8) =   2^-256 =
+                                // 8.6361685550944446253863518628003995711160003644362813850237034700E-78
+                                // =
+                                // 0.86361685550944446253863518628003995711160003644362813850237034700e-77
+     {{static_cast<uint64_t>(-77), 0xdd15fe86affad912LL, 0x49ef0eb713f39ebeLL,
+       0xaa987b6e6fd2a002LL}},  // 10: 2^-(2^9) =   2^-512 =
+                                // 7.4583407312002067432909653154629338373764715346004068942715183331E-155
+                                // =
+                                // 0.74583407312002067432909653154629338373764715346004068942715183331e-154
+     {{static_cast<uint64_t>(-154), 0xbeeefb584aff8603LL, 0xaafb550ffacfd8faLL,
+       0x5ca47e4f88d45371LL}},  // 11: 2^-(2^10) =   2^-1024 =
+                                // 5.5626846462680034577255817933310101605480399511558295763833185421E-309
+                                // =
+                                // 0.55626846462680034577255817933310101605480399511558295763833185421e-308
+     {{static_cast<uint64_t>(-308), 0x8e679c2f5e44ff8fLL, 0x570f09eaa7ea7648LL,
+       0x5961db50c6d2b888LL}},  // ***
+                                // 12: 2^-(2^11) =   2^-2048 =
+                                // 3.0943460473825782754801833699711978538925563038849690459540984582E-617
+                                // =
+                                // 0.30943460473825782754801833699711978538925563038849690459540984582e-616
+     {{static_cast<uint64_t>(-616), 0x4f371b3399fc2ab0LL, 0x8170041c9feb05aaLL,
+       0xc7c343447c75bcf6LL}},  // 13: 2^-(2^12) =   2^-4096 =
+                                // 9.5749774609521853579467310122804202420597417413514981491308464986E-1234
+                                // =
+                                // 0.95749774609521853579467310122804202420597417413514981491308464986e-1233
+     {{static_cast<uint64_t>(-1233), 0xf51e928179013fd3LL, 0xde4bd12cde4d985cLL,
+       0x4a573ca6f94bff14LL}},  // 14: 2^-(2^13) =   2^-8192 =
+                                // 9.1680193377742358281070619602424158297818248567928361864131947526E-2467
+                                // =
+                                // 0.91680193377742358281070619602424158297818248567928361864131947526e-2466
+     {{static_cast<uint64_t>(-2466), 0xeab388127bccaff7LL, 0x1667639142b9fbaeLL,
+       0x775ec9995e1039fbLL}},  // 15: 2^-(2^14) =   2^-16384 =
+                                // 8.4052578577802337656566945433043815064951983621161781002720680748E-4933
+                                // =
+                                // 0.84052578577802337656566945433043815064951983621161781002720680748e-4932
+     {{static_cast<uint64_t>(-4932), 0xd72cb2a95c7ef6ccLL, 0xe81bf1e825ba7515LL,
+       0xc2feb521d6cb5dcdLL}},  // 16: 2^-(2^15) =   2^-32768 =
+                                // 7.0648359655776364427774021878587184537374439102725065590941425796E-9865
+                                // =
+                                // 0.70648359655776364427774021878587184537374439102725065590941425796e-9864
+     {{static_cast<uint64_t>(-9864), 0xb4dc1be6604502dcLL, 0xd491079b8eef6535LL,
+       0x578d3965d24de84dLL}},  // ***
+                                // 17: 2^-(2^16) =   2^-65536 =
+                                // 4.9911907220519294656590574792132451973746770423207674161425040336E-19729
+                                // =
+                                // 0.49911907220519294656590574792132451973746770423207674161425040336e-19728
+     {{static_cast<uint64_t>(-19728), 0x7fc6447bee60ea43LL,
+       0x2548da5c8b125b27LL,
+       0x5f42d1142f41d349LL}},  // ***
+                                // 18: 2^-(2^17) =   2^-131072 =
+                                // 2.4911984823897261018394507280431349807329035271689521242878455599E-39457
+                                // =
+                                // 0.24911984823897261018394507280431349807329035271689521242878455599e-39456
+     {{static_cast<uint64_t>(-39456), 0x3fc65180f88af8fbLL,
+       0x6a6915f383349413LL,
+       0x063c3708b6ceb291LL}},  // ***
+                                // 19: 2^-(2^18) =   2^-262144 =
+                                // 6.2060698786608744707483205572846793091942192651991171731773832448E-78914
+                                // =
+                                // 0.62060698786608744707483205572846793091942192651991171731773832448e-78913
+     {{static_cast<uint64_t>(-78913), 0x9ee0197c8dcd55bfLL,
+       0x2b2b9b942c38f4a2LL,
+       0x0f8ba634e9c706aeLL}},  // 20: 2^-(2^19) =   2^-524288 =
+                                // 3.8515303338821801176537443725392116267291403078581314096728076497E-157827
+                                // =
+                                // 0.38515303338821801176537443725392116267291403078581314096728076497e-157826
+     {{static_cast<uint64_t>(-157826), 0x629963a25b8b2d79LL,
+       0xd00b9d2286f70876LL,
+       0xe97004700c3644fcLL}},  // ***
+                                // 21: 2^-(2^20) =   2^-1048576 =
+                                // 1.4834285912814577854404052243709225888043963245995136935174170977E-315653
+                                // =
+                                // 0.14834285912814577854404052243709225888043963245995136935174170977e-315652
+     {{static_cast<uint64_t>(-315652), 0x25f9cc308ceef4f3LL,
+       0x40f19543911a4546LL,
+       0xa2cd389452cfc366LL}},  // 22: 2^-(2^21) =   2^-2097152 =
+                                // 2.2005603854312903332428997579002102976620485709683755186430397089E-631306
+                                // =
+                                // 0.22005603854312903332428997579002102976620485709683755186430397089e-631305
+     {{static_cast<uint64_t>(-631305), 0x385597b0d47e76b8LL,
+       0x1b9f67e103bf2329LL,
+       0xc3119848595985f7LL}},  // 23: 2^-(2^22) =   2^-4194304 =
+                                // 4.8424660099295090687215589310713586524081268589231053824420510106E-1262612
+                                // =
+                                // 0.48424660099295090687215589310713586524081268589231053824420510106e-1262611
+     {{static_cast<uint64_t>(-1262611), 0x7bf795d276c12f66LL,
+       0x66a61d62a446659aLL,
+       0xa1a4d73bebf093d5LL}},  // ***
+                                // 24: 2^-(2^23) =   2^-8388608 =
+                                // 2.3449477057322620222546775527242476219043877555386221929831430440E-2525223
+                                // =
+                                // 0.23449477057322620222546775527242476219043877555386221929831430440e-2525222
+     {{static_cast<uint64_t>(-2525222), 0x3c07d96ab1ed7799LL,
+       0xcb7355c22cc05ac0LL,
+       0x4ffc0ab73b1f6a49LL}},  // ***
+                                // 25: 2^-(2^24) =   2^-16777216 =
+                                // 5.4987797426189993226257377747879918011694025935111951649826798628E-5050446
+                                // =
+                                // 0.54987797426189993226257377747879918011694025935111951649826798628e-5050445
+     {{static_cast<uint64_t>(-5050445), 0x8cc4cd8c3edefb9aLL,
+       0x6c8ff86a90a97e0cLL,
+       0x166cfddbf98b71bfLL}},  // ***
+                                // 26: 2^-(2^25) =   2^-33554432 =
+                                // 3.0236578657837068435515418409027857523343464783010706819696074665E-10100891
+                                // =
+                                // 0.30236578657837068435515418409027857523343464783010706819696074665e-10100890
+     {{static_cast<uint64_t>(-10100890), 0x4d67d81cc88e1228LL,
+       0x1d7cfb06666b79b3LL,
+       0x7b916728aaa4e70dLL}},  // ***
+                                // 27: 2^-(2^26) =   2^-67108864 =
+                                // 9.1425068893156809483320844568740945600482370635012633596231964471E-20201782
+                                // =
+                                // 0.91425068893156809483320844568740945600482370635012633596231964471e-20201781
+     {{static_cast<uint64_t>(-20201781), 0xea0c55494e7a552dLL,
+       0xb88cb9484bb86c61LL,
+       0x8d44893c610bb7dFLL}},  // ***
+                                // 28: 2^-(2^27) =   2^-134217728 =
+                                // 8.3585432221184688810803924874542310018191301711943564624682743545E-40403563
+                                // =
+                                // 0.83585432221184688810803924874542310018191301711943564624682743545e-40403562
+     {{static_cast<uint64_t>(-40403562), 0xd5fa8c821ec0c24aLL,
+       0xa80e46e764e0f8b0LL,
+       0xa7276bfa432fac7eLL}},  // 29: 2^-(2^28) =   2^-268435456 =
+                                // 6.9865244796022595809958912202005005328020601847785697028605460277E-80807125
+                                // =
+                                // 0.69865244796022595809958912202005005328020601847785697028605460277e-80807124
+     {{static_cast<uint64_t>(-80807124), 0xb2dae307426f6791LL,
+       0xc970b82f58b12918LL,
+       0x0472592f7f39190eLL}},  // 30: 2^-(2^29) =   2^-536870912 =
+                                // 4.8811524304081624052042871019605298977947353140996212667810837790E-161614249
+                                // =
+                                // 0.48811524304081624052042871019605298977947353140996212667810837790e-161614248
+                                //      {-161614248, 0x7cf5_1edd_8a15_f1c9L,
+                                //      0x656d_ab34_98f8_e697L,
+                                //      0x12da_a2a8_0e53_c809L},
+     {{static_cast<uint64_t>(-161614248), 0x7cf51edd8a15f1c9LL,
+       0x656dab3498f8e697LL,
+       0x12daa2a80e53c807LL}},  // 31: 2^-(2^30) =   2^-1073741824 =
+                                // 2.3825649048879510732161697817326745204151961255592397879550237608E-323228497
+                                // =
+                                // 0.23825649048879510732161697817326745204151961255592397879550237608e-323228496
+     {{static_cast<uint64_t>(-323228496), 0x3cfe609ab5883c50LL,
+       0xbec8b5d22b198871LL,
+       0xe18477703b4622b4LL}},  // 32: 2^-(2^31) =   2^-2147483648 =
+                                // 5.6766155260037313438164181629489689531186932477276639365773003794E-646456994
+                                // =
+                                // 0.56766155260037313438164181629489689531186932477276639365773003794e-646456993
+     {{static_cast<uint64_t>(-646456993), 0x9152447b9d7cda9aLL,
+       0x3b4d3f6110d77aadLL, 0xfa81bad1c394adb4LL}}}};
+// Buffers used internally
+// The order of words in the arrays is big-endian: the highest part is in
+// buff[0] (in buff[1] for buffers of 10 words)
+
+void QuadrupleBuilder::parse(std::vector<uint8_t>& digits, int32_t exp10) {
+  exp10 += static_cast<int32_t>((digits).size()) -
+           1;  // digits is viewed as x.yyy below.
+  this->exponent = 0;
+  this->mantHi = 0LL;
+  this->mantLo = 0LL;
+  // Finds numeric value of the decimal mantissa
+  std::array<uint64_t, 6>& mantissa = this->buffer6x32C;
+  int32_t exp10Corr = parseMantissa(digits, mantissa);
+  if (exp10Corr == 0 && isEmpty(mantissa)) {
+    // Mantissa == 0
+    return;
+  }
+  // takes account of the point position in the mant string and possible carry
+  // as a result of round-up (like 9.99e1 -> 1.0e2)
+  exp10 += exp10Corr;
+  if (exp10 < MIN_EXP10) {
+    return;
+  }
+  if (exp10 > MAX_EXP10) {
+    this->exponent = (static_cast<uint32_t>(EXPONENT_OF_INFINITY));
+    return;
+  }
+  double exp2 = findBinaryExponent(exp10, mantissa);
+  // Finds binary mantissa and possible exponent correction. Fills the fields.
+  findBinaryMantissa(exp10, exp2, mantissa);
+}
+int32_t QuadrupleBuilder::parseMantissa(std::vector<uint8_t>& digits,
+                                        std::array<uint64_t, 6>& mantissa) {
+  for (int32_t i = (0); i < (6); i++) {
+    mantissa[i] = 0LL;
+  }
+  // Skip leading zeroes
+  int32_t firstDigit = 0;
+  while (firstDigit < static_cast<int32_t>((digits).size()) &&
+         digits[firstDigit] == 0) {
+    firstDigit += 1;
+  }
+  if (firstDigit == static_cast<int32_t>((digits).size())) {
+    return 0;  // All zeroes
+  }
+  int32_t expCorr = -firstDigit;
+  // Limit the string length to avoid unnecessary fuss
+  if (static_cast<int32_t>((digits).size()) - firstDigit >
+      MAX_MANTISSA_LENGTH) {
+    bool carry =
+        digits[MAX_MANTISSA_LENGTH] >= 5;  // The highest digit to be truncated
+    std::vector<uint8_t> truncated(MAX_MANTISSA_LENGTH);
+    for (int32_t i = (0); i < (MAX_MANTISSA_LENGTH); i++) {
+      truncated[i] = digits[i + firstDigit];
+    }
+    if (carry) {  // Round-up: add carry
+      expCorr += addCarry(
+          truncated);  // May add an extra digit in front of it (99..99 -> 100)
+    }
+    digits = truncated;
+    firstDigit = 0;
+  }
+  for (int32_t i = (static_cast<int32_t>((digits).size())) - 1;
+       i >= (firstDigit); i--) {  // digits, starting from the last
+    mantissa[0] |= (static_cast<uint64_t>(digits[i])) << 32LL;
+    divBuffBy10(mantissa);
+  }
+  return expCorr;
+}
+// Divides the unpacked value stored in the given buffer by 10
+// @param buffer contains the unpacked value to divide (32 least significant
+// bits are used)
+template <std::size_t N>
+void QuadrupleBuilder::divBuffBy10(std::array<uint64_t, N>& buffer) {
+  int32_t maxIdx = static_cast<int32_t>((buffer).size());
+  // big/endian
+  for (int32_t i = (0); i < (maxIdx); i++) {
+    uint64_t r = buffer[i] % 10LL;
+    buffer[i] = ((buffer[i]) / (10LL));
+    if (i + 1 < maxIdx) {
+      buffer[i + 1] += r << 32LL;
+    }
+  }
+}
+// Checks if the buffer is empty (contains nothing but zeros)
+// @param buffer the buffer to check
+// @return {@code true} if the buffer is empty, {@code false} otherwise
+template <std::size_t N>
+bool QuadrupleBuilder::isEmpty(std::array<uint64_t, N>& buffer) {
+  for (int32_t i = (0); i < (static_cast<int32_t>((buffer).size())); i++) {
+    if (buffer[i] != 0LL) {
+      return false;
+    }
+  }
+  return true;
+}
+// Adds one to a decimal number represented as a sequence of decimal digits.
+// propagates carry as needed, so that {@code addCarryTo("6789") = "6790",
+// addCarryTo("9999") = "10000"} etc.
+// @return 1 if an additional higher "1" was added in front of the number as a
+// result of
+//     rounding-up, 0 otherwise
+int32_t QuadrupleBuilder::addCarry(std::vector<uint8_t>& digits) {
+  for (int32_t i = (static_cast<int32_t>((digits).size())) - 1; i >= (0);
+       i--) {  // starting with the lowest digit
+    uint8_t c = digits[i];
+    if (c == 9) {
+      digits[i] = 0;
+    } else {
+      digits[i] = (static_cast<uint8_t>(digits[i] + 1));
+      return 0;
+    }
+  }
+  digits[0] = 1;
+  return 1;
+}
+// Finds binary exponent, using decimal exponent and mantissa.<br>
+// exp2 = exp10 * log<sub>2</sub>(10) + log<sub>2</sub>(mant)<br>
+// @param exp10 decimal exponent
+// @param mantissa array of longs containing decimal mantissa (divided by 10)
+// @return found value of binary exponent
+double QuadrupleBuilder::findBinaryExponent(int32_t exp10,
+                                            std::array<uint64_t, 6>& mantissa) {
+  uint64_t mant10 =
+      mantissa[0] << 31LL |
+      ((mantissa[1]) >> (1LL));  // Higher 63 bits of the mantissa, in range
+  // 0x0CC..CCC -- 0x7FF..FFF (2^63/10 -- 2^63-1)
+  // decimal value of the mantissa in range 1.0..9.9999...
+  double mant10d = (static_cast<double>(mant10)) / TWO_POW_63_DIV_10;
+  return floor((static_cast<double>(exp10)) * LOG2_10 +
+               log2(mant10d));  // Binary exponent
+}
+// Calculates log<sub>2</sub> of the given x
+// @param x argument that can't be 0
+// @return the value of log<sub>2</sub>(x)
+double QuadrupleBuilder::log2(double x) {
+  // x can't be 0
+  return LOG2_E * log(x);
+}
+void QuadrupleBuilder::findBinaryMantissa(int32_t exp10,
+                                          double exp2,
+                                          std::array<uint64_t, 6>& mantissa) {
+  // pow(2, -exp2): division by 2^exp2 is multiplication by 2^(-exp2) actually
+  std::array<uint64_t, 4>& powerOf2 = this->buffer4x64B;
+  powerOfTwo(-exp2, powerOf2);
+  std::array<uint64_t, 12>& product =
+      this->buffer12x32;  // use it for the product (M * 10^E / 2^e)
+  multUnpacked6x32byPacked(mantissa, powerOf2,
+                           product);  // product in buff_12x32
+  multBuffBy10(product);  // "Quasidecimals" are numbers divided by 10
+  // The powerOf2[0] is stored as an unsigned value
+  if ((static_cast<uint64_t>(powerOf2[0])) != (static_cast<uint64_t>(-exp10))) {
+    // For some combinations of exp2 and exp10, additional multiplication needed
+    // (see mant2_from_M_E_e.xls)
+    multBuffBy10(product);
+  }
+  // compensate possible inaccuracy of logarithms used to compute exp2
+  exp2 += normalizeMant(product);
+  exp2 += EXPONENT_BIAS;  // add bias
+  // For subnormal values, exp2 <= 0. We just return 0 for them, as they are
+  // far from any range we are interested in.
+  if (exp2 <= 0) {
+    return;
+  }
+  exp2 += roundUp(product);  // round up, may require exponent correction
+  if ((static_cast<uint64_t>(exp2)) >= EXPONENT_OF_INFINITY) {
+    this->exponent = (static_cast<uint32_t>(EXPONENT_OF_INFINITY));
+  } else {
+    this->exponent = (static_cast<uint32_t>(exp2));
+    this->mantHi = (static_cast<uint64_t>((product[0] << 32LL) + product[1]));
+    this->mantLo = (static_cast<uint64_t>((product[2] << 32LL) + product[3]));
+  }
+}
+// Calculates the required power and returns the result in the quasidecimal
+// format (an array of longs, where result[0] is the decimal exponent of the
+// resulting value, and result[1] -- result[3] contain 192 bits of the mantissa
+// divided by ten (so that 8 looks like <pre>{@code {1, 0xCCCC_.._CCCCL,
+// 0xCCCC_.._CCCCL, 0xCCCC_.._CCCDL}}}</pre> uses arrays <b><i>buffer4x64B</b>,
+// buffer6x32A, buffer6x32B, buffer12x32</i></b>,
+// @param exp the power to raise 2 to
+// @param power (result) the value of {@code2^exp}
+void QuadrupleBuilder::powerOfTwo(double exp, std::array<uint64_t, 4>& power) {
+  if (exp == 0) {
+    array_copy(POS_POWERS_OF_2[0], power);
+    return;
+  }
+  // positive powers of 2 (2^0, 2^1, 2^2, 2^4, 2^8 ... 2^(2^31) )
+  std::array<std::array<uint64_t, 4>, 33>* powers = (&(POS_POWERS_OF_2));
+  if (exp < 0) {
+    exp = -exp;
+    powers = (&(NEG_POWERS_OF_2));  // positive powers of 2 (2^0, 2^-1, 2^-2,
+                                    // 2^-4, 2^-8 ... 2^30)
+  }
+  // 2^31 = 0x8000_0000L; a single bit that will be shifted right at every
+  // iteration
+  double currPowOf2 = POW_2_31;
+  int32_t idx = 32;  // Index in the table of powers
+  bool first_power = true;
+  // if exp = b31 * 2^31 + b30 * 2^30 + .. + b0 * 2^0, where b0..b31 are the
+  // values of the bits in exp, then 2^exp = 2^b31 * 2^b30 ... * 2^b0. Find the
+  // product, using a table of powers of 2.
+  while (exp > 0) {
+    if (exp >= currPowOf2) {  // the current bit in the exponent is 1
+      if (first_power) {
+        // 4 longs, power[0] -- decimal (?) exponent, power[1..3] -- 192 bits of
+        // mantissa
+        array_copy((*(powers))[idx], power);
+        first_power = false;
+      } else {
+        // Multiply by the corresponding power of 2
+        multPacked3x64_AndAdjustExponent(power, (*(powers))[idx], power);
+      }
+      exp -= currPowOf2;
+    }
+    idx -= 1;
+    currPowOf2 = currPowOf2 * 0.5;  // Note: this is exact
+  }
+}
+// Copies from into to.
+template <std::size_t N>
+void QuadrupleBuilder::array_copy(std::array<uint64_t, N>& source,
+                                  std::array<uint64_t, 4>& dest) {
+  for (int32_t i = (0); i < (static_cast<int32_t>((dest).size())); i++) {
+    dest[i] = source[i];
+  }
+}
+// Multiplies two quasidecimal numbers contained in buffers of 3 x 64 bits with
+// exponents, puts the product to <b><i>buffer4x64B</i></b><br> and returns it.
+// Both each of the buffers and the product contain 4 longs - exponent and 3 x
+// 64 bits of mantissa. If the higher word of mantissa of the product is less
+// than 0x1999_9999_9999_9999L (i.e. mantissa is less than 0.1) multiplies
+// mantissa by 10 and adjusts the exponent respectively.
+void QuadrupleBuilder::multPacked3x64_AndAdjustExponent(
+    std::array<uint64_t, 4>& factor1,
+    std::array<uint64_t, 4>& factor2,
+    std::array<uint64_t, 4>& result) {
+  multPacked3x64_simply(factor1, factor2, this->buffer12x32);
+  int32_t expCorr = correctPossibleUnderflow(this->buffer12x32);
+  pack_6x32_to_3x64(this->buffer12x32, result);
+  // result[0] is a signed int64 value stored in an uint64
+  result[0] =
+      factor1[0] + factor2[0] +
+      (static_cast<uint64_t>(expCorr));  // product.exp = f1.exp + f2.exp
+}
+// Multiplies mantissas of two packed quasidecimal values (each is an array of 4
+// longs, exponent + 3 x 64 bits of mantissa) Returns the product as unpacked
+// buffer of 12 x 32 (12 x 32 bits of product) uses arrays <b><i>buffer6x32A,
+// buffer6x32B</b></i>
+// @param factor1 an array of longs containing factor 1 as packed quasidecimal
+// @param factor2 an array of longs containing factor 2 as packed quasidecimal
+// @param result an array of 12 longs filled with the product of mantissas
+void QuadrupleBuilder::multPacked3x64_simply(std::array<uint64_t, 4>& factor1,
+                                             std::array<uint64_t, 4>& factor2,
+                                             std::array<uint64_t, 12>& result) {
+  for (int32_t i = (0); i < (static_cast<int32_t>((result).size())); i++) {
+    result[i] = 0LL;
+  }
+  // TODO2 19.01.16 21:23:06 for the next version -- rebuild the table of powers
+  // to make the numbers unpacked, to avoid packing/unpacking
+  unpack_3x64_to_6x32(factor1, this->buffer6x32A);
+  unpack_3x64_to_6x32(factor2, this->buffer6x32B);
+  for (int32_t i = (6) - 1; i >= (0); i--) {  // compute partial 32-bit products
+    for (int32_t j = (6) - 1; j >= (0); j--) {
+      uint64_t part = this->buffer6x32A[i] * this->buffer6x32B[j];
+      result[j + i + 1] =
+          (static_cast<uint64_t>(result[j + i + 1] + (part & LOWER_32_BITS)));
+      result[j + i] =
+          (static_cast<uint64_t>(result[j + i] + ((part) >> (32LL))));
+    }
+  }
+  // Carry higher bits of the product to the lower bits of the next word
+  for (int32_t i = (12) - 1; i >= (1); i--) {
+    result[i - 1] =
+        (static_cast<uint64_t>(result[i - 1] + ((result[i]) >> (32LL))));
+    result[i] &= LOWER_32_BITS;
+  }
+}
+// Corrects possible underflow of the decimal mantissa, passed in in the {@code
+// mantissa}, by multiplying it by a power of ten. The corresponding value to
+// adjust the decimal exponent is returned as the result
+// @param mantissa a buffer containing the mantissa to be corrected
+// @return a corrective (addition) that is needed to adjust the decimal exponent
+// of the number
+template <std::size_t N>
+int32_t QuadrupleBuilder::correctPossibleUnderflow(
+    std::array<uint64_t, N>& mantissa) {
+  int32_t expCorr = 0;
+  while (isLessThanOne(mantissa)) {  // Underflow
+    multBuffBy10(mantissa);
+    expCorr -= 1;
+  }
+  return expCorr;
+}
+// Checks if the unpacked quasidecimal value held in the given buffer is less
+// than one (in this format, one is represented as { 0x1999_9999L, 0x9999_9999L,
+// 0x9999_9999L,...}
+// @param buffer a buffer containing the value to check
+// @return {@code true}, if the value is less than one
+template <std::size_t N>
+bool QuadrupleBuilder::isLessThanOne(std::array<uint64_t, N>& buffer) {
+  if (buffer[0] < 0x19999999LL) {
+    return true;
+  }
+  if (buffer[0] > 0x19999999LL) {
+    return false;
+  }
+  // A note regarding the coverage:
+  // Multiplying a 128-bit number by another 192-bit number,
+  // as well as multiplying of two 192-bit numbers,
+  // can never produce 320 (or 384 bits, respectively) of 0x1999_9999L,
+  // 0x9999_9999L,
+  for (int32_t i = (1); i < (static_cast<int32_t>((buffer).size())); i++) {
+    // so this loop can't be covered entirely
+    if (buffer[i] < 0x99999999LL) {
+      return true;
+    }
+    if (buffer[i] > 0x99999999LL) {
+      return false;
+    }
+  }
+  // and it can never reach this point in real life.
+  return false;  // Still Java requires the return statement here.
+}
+// Multiplies unpacked 192-bit value by a packed 192-bit factor <br>
+// uses static arrays <b><i>buffer6x32B</i></b>
+// @param factor1 a buffer containing unpacked quasidecimal mantissa (6 x 32
+// bits)
+// @param factor2 an array of 4 longs containing packed quasidecimal power of
+// two
+// @param product a buffer of at least 12 longs to hold the product
+void QuadrupleBuilder::multUnpacked6x32byPacked(
+    std::array<uint64_t, 6>& factor1,
+    std::array<uint64_t, 4>& factor2,
+    std::array<uint64_t, 12>& product) {
+  for (int32_t i = (0); i < (static_cast<int32_t>((product).size())); i++) {
+    product[i] = 0LL;
+  }
+  std::array<uint64_t, 6>& unpacked2 = this->buffer6x32B;
+  unpack_3x64_to_6x32(
+      factor2, unpacked2);  // It's the powerOf2, with exponent in 0'th word
+  int32_t maxFactIdx = static_cast<int32_t>((factor1).size());
+  for (int32_t i = (maxFactIdx)-1; i >= (0);
+       i--) {  // compute partial 32-bit products
+    for (int32_t j = (maxFactIdx)-1; j >= (0); j--) {
+      uint64_t part = factor1[i] * unpacked2[j];
+      product[j + i + 1] =
+          (static_cast<uint64_t>(product[j + i + 1] + (part & LOWER_32_BITS)));
+      product[j + i] =
+          (static_cast<uint64_t>(product[j + i] + ((part) >> (32LL))));
+    }
+  }
+  // Carry higher bits of the product to the lower bits of the next word
+  for (int32_t i = (12) - 1; i >= (1); i--) {
+    product[i - 1] =
+        (static_cast<uint64_t>(product[i - 1] + ((product[i]) >> (32LL))));
+    product[i] &= LOWER_32_BITS;
+  }
+}
+// Multiplies the unpacked value stored in the given buffer by 10
+// @param buffer contains the unpacked value to multiply (32 least significant
+// bits are used)
+template <std::size_t N>
+void QuadrupleBuilder::multBuffBy10(std::array<uint64_t, N>& buffer) {
+  int32_t maxIdx = static_cast<int32_t>((buffer).size()) - 1;
+  buffer[0] &= LOWER_32_BITS;
+  buffer[maxIdx] *= 10LL;
+  for (int32_t i = (maxIdx)-1; i >= (0); i--) {
+    buffer[i] =
+        (static_cast<uint64_t>(buffer[i] * 10LL + ((buffer[i + 1]) >> (32LL))));
+    buffer[i + 1] &= LOWER_32_BITS;
+  }
+}
+// Makes sure that the (unpacked) mantissa is normalized,
+// i.e. buff[0] contains 1 in bit 32 (the implied integer part) and higher 32 of
+// mantissa in bits 31..0, and buff[1]..buff[4] contain other 96 bits of
+// mantissa in their lower halves: <pre>0x0000_0001_XXXX_XXXXL,
+// 0x0000_0000_XXXX_XXXXL...</pre> If necessary, divides the mantissa by
+// appropriate power of 2 to make it normal.
+// @param mantissa a buffer containing unpacked mantissa
+// @return if the mantissa was not normal initially, a correction that should be
+// added to the result's exponent, or 0 otherwise
+template <std::size_t N>
+int32_t QuadrupleBuilder::normalizeMant(std::array<uint64_t, N>& mantissa) {
+  int32_t expCorr = 31 - __builtin_clzll(mantissa[0]);
+  if (expCorr != 0) {
+    divBuffByPower2(mantissa, expCorr);
+  }
+  return expCorr;
+}
+// Rounds up the contents of the unpacked buffer to 128 bits by adding unity one
+// bit lower than the lowest of these 128 bits. If carry propagates up to bit 33
+// of buff[0], shifts the buffer rightwards to keep it normalized.
+// @param mantissa the buffer to get rounded
+// @return 1 if the buffer was shifted, 0 otherwise
+template <std::size_t N>
+int32_t QuadrupleBuilder::roundUp(std::array<uint64_t, N>& mantissa) {
+  // due to the limited precision of the power of 2, a number with exactly half
+  // LSB in its mantissa (i.e that would have 0x8000_0000_0000_0000L in bits
+  // 128..191 if it were computed precisely), after multiplication by this power
+  // of 2, may get erroneous bits 185..191 (counting from the MSB), taking a
+  // value from 0xXXXX_XXXX_XXXX_XXXXL 0xXXXX_XXXX_XXXX_XXXXL
+  // 0x7FFF_FFFF_FFFF_FFD8L. to 0xXXXX_XXXX_XXXX_XXXXL 0xXXXX_XXXX_XXXX_XXXXL
+  // 0x8000_0000_0000_0014L, or something alike. To round it up, we first add
+  // 0x0000_0000_0000_0000L 0x0000_0000_0000_0000L 0x0000_0000_0000_0028L, to
+  // turn it into 0xXXXX_XXXX_XXXX_XXXXL 0xXXXX_XXXX_XXXX_XXXXL
+  // 0x8000_0000_0000_00XXL, and then add 0x0000_0000_0000_0000L
+  // 0x0000_0000_0000_0000L 0x8000_0000_0000_0000L, to provide carry to higher
+  // bits.
+  addToBuff(mantissa, 5, 100LL);  // to compensate possible inaccuracy
+  addToBuff(mantissa, 4,
+            0x80000000LL);  // round-up, if bits 128..159 >= 0x8000_0000L
+  if ((mantissa[0] & (HIGHER_32_BITS << 1LL)) != 0LL) {
+    // carry's got propagated beyond the highest bit
+    divBuffByPower2(mantissa, 1);
+    return 1;
+  }
+  return 0;
+}
+// converts 192 most significant bits of the mantissa of a number from an
+// unpacked quasidecimal form (where 32 least significant bits only used) to a
+// packed quasidecimal form (where buff[0] contains the exponent and
+// buff[1]..buff[3] contain 3 x 64 = 192 bits of mantissa)
+// @param unpackedMant a buffer of at least 6 longs containing an unpacked value
+// @param result a buffer of at least 4 long to hold the packed value
+// @return packedQD192 with words 1..3 filled with the packed mantissa.
+// packedQD192[0] is not
+//     affected.
+template <std::size_t N, std::size_t P>
+void QuadrupleBuilder::pack_6x32_to_3x64(std::array<uint64_t, N>& unpackedMant,
+                                         std::array<uint64_t, P>& result) {
+  result[1] = (unpackedMant[0] << 32LL) + unpackedMant[1];
+  result[2] = (unpackedMant[2] << 32LL) + unpackedMant[3];
+  result[3] = (unpackedMant[4] << 32LL) + unpackedMant[5];
+}
+// Unpacks the mantissa of a 192-bit quasidecimal (4 longs: exp10, mantHi,
+// mantMid, mantLo) to a buffer of 6 longs, where the least significant 32 bits
+// of each long contains respective 32 bits of the mantissa
+// @param qd192 array of 4 longs containing the number to unpack
+// @param buff_6x32 buffer of 6 long to hold the unpacked mantissa
+void QuadrupleBuilder::unpack_3x64_to_6x32(std::array<uint64_t, 4>& qd192,
+                                           std::array<uint64_t, 6>& buff_6x32) {
+  buff_6x32[0] = ((qd192[1]) >> (32LL));
+  buff_6x32[1] = qd192[1] & LOWER_32_BITS;
+  buff_6x32[2] = ((qd192[2]) >> (32LL));
+  buff_6x32[3] = qd192[2] & LOWER_32_BITS;
+  buff_6x32[4] = ((qd192[3]) >> (32LL));
+  buff_6x32[5] = qd192[3] & LOWER_32_BITS;
+}
+// Divides the contents of the buffer by 2^exp2<br>
+// (shifts the buffer rightwards by exp2 if the exp2 is positive, and leftwards
+// if it's negative), keeping it unpacked (only lower 32 bits of each element
+// are used, except the buff[0] whose higher half is intended to contain integer
+// part)
+// @param buffer the buffer to divide
+// @param exp2 the exponent of the power of two to divide by, expected to be
+template <std::size_t N>
+void QuadrupleBuilder::divBuffByPower2(std::array<uint64_t, N>& buffer,
+                                       int32_t exp2) {
+  int32_t maxIdx = static_cast<int32_t>((buffer).size()) - 1;
+  uint64_t backShift =
+      (static_cast<uint64_t>(32 - static_cast<int32_t>(labs(exp2))));
+  if (exp2 > 0) {  // Shift to the right
+    uint64_t exp2Shift = (static_cast<uint64_t>(exp2));
+    for (int32_t i = (maxIdx + 1) - 1; i >= (1); i--) {
+      buffer[i] = ((buffer[i]) >> (exp2Shift)) |
+                  ((buffer[i - 1] << backShift) & LOWER_32_BITS);
+    }
+    buffer[0] =
+        ((buffer[0]) >> (exp2Shift));  // Preserve the high half of buff[0]
+  } else if (exp2 < 0) {               // Shift to the left
+    uint64_t exp2Shift = (static_cast<uint64_t>(-exp2));
+    buffer[0] = (static_cast<uint64_t>(
+        (buffer[0] << exp2Shift) |
+        ((buffer[1]) >> (backShift))));  // Preserve the high half of buff[0]
+    for (int32_t i = (1); i < (maxIdx); i++) {
+      buffer[i] =
+          (static_cast<uint64_t>(((buffer[i] << exp2Shift) & LOWER_32_BITS) |
+                                 ((buffer[i + 1]) >> (backShift))));
+    }
+    buffer[maxIdx] = (buffer[maxIdx] << exp2Shift) & LOWER_32_BITS;
+  }
+}
+// Adds the summand to the idx'th word of the unpacked value stored in the
+// buffer and propagates carry as necessary
+// @param buff the buffer to add the summand to
+// @param idx  the index of the element to which the summand is to be added
+// @param summand the summand to add to the idx'th element of the buffer
+template <std::size_t N>
+void QuadrupleBuilder::addToBuff(std::array<uint64_t, N>& buff,
+                                 int32_t idx,
+                                 uint64_t summand) {
+  int32_t maxIdx = idx;
+  buff[maxIdx] = (static_cast<uint64_t>(
+      buff[maxIdx] + summand));  // Big-endian, the lowest word
+  for (int32_t i = (maxIdx + 1) - 1; i >= (1);
+       i--) {  // from the lowest word upwards, except the highest
+    if ((buff[i] & HIGHER_32_BITS) != 0LL) {
+      buff[i] &= LOWER_32_BITS;
+      buff[i - 1] += 1LL;
+    } else {
+      break;
+    }
+  }
+}
+
+}  // namespace util
+}  // namespace firestore
+}  // namespace firebase

+ 98 - 0
Firestore/core/src/util/quadruple_builder.h

@@ -0,0 +1,98 @@
+//  Copyright 2025 Google LLC
+//  Copyright 2021 M.Vokhmentsev
+//
+//  Licensed under the Apache License, Version 2.0 (the "License");
+//  you may not use this file except in compliance with the License.
+//  You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+
+#ifndef FIRESTORE_CORE_SRC_UTIL_QUADRUPLE_BUILDER_H_
+#define FIRESTORE_CORE_SRC_UTIL_QUADRUPLE_BUILDER_H_
+
+#include <math.h>
+#include <array>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+class QuadrupleBuilder {
+ public:
+  void parseDecimal(std::vector<uint8_t>& digits, int64_t exp10) {
+    parse(digits, exp10);
+  }
+  // The fields containing the value of the instance
+  uint32_t exponent;
+  uint64_t mantHi;
+  uint64_t mantLo;
+
+ private:
+  std::array<uint64_t, 4> buffer4x64B;
+  std::array<uint64_t, 6> buffer6x32A;
+  std::array<uint64_t, 6> buffer6x32B;
+  std::array<uint64_t, 6> buffer6x32C;
+  std::array<uint64_t, 12> buffer12x32;
+  void parse(std::vector<uint8_t>& digits, int32_t exp10);
+  int32_t parseMantissa(std::vector<uint8_t>& digits,
+                        std::array<uint64_t, 6>& mantissa);
+  template <std::size_t N>
+  void divBuffBy10(std::array<uint64_t, N>& buffer);
+  template <std::size_t N>
+  bool isEmpty(std::array<uint64_t, N>& buffer);
+  int32_t addCarry(std::vector<uint8_t>& digits);
+  double findBinaryExponent(int32_t exp10, std::array<uint64_t, 6>& mantissa);
+  double log2(double x);
+  void findBinaryMantissa(int32_t exp10,
+                          double exp2,
+                          std::array<uint64_t, 6>& mantissa);
+  void powerOfTwo(double exp, std::array<uint64_t, 4>& power);
+  template <std::size_t N>
+  void array_copy(std::array<uint64_t, N>& source,
+                  std::array<uint64_t, 4>& dest);
+  void multPacked3x64_AndAdjustExponent(std::array<uint64_t, 4>& factor1,
+                                        std::array<uint64_t, 4>& factor2,
+                                        std::array<uint64_t, 4>& result);
+  void multPacked3x64_simply(std::array<uint64_t, 4>& factor1,
+                             std::array<uint64_t, 4>& factor2,
+                             std::array<uint64_t, 12>& result);
+  template <std::size_t N>
+  int32_t correctPossibleUnderflow(std::array<uint64_t, N>& mantissa);
+  template <std::size_t N>
+  bool isLessThanOne(std::array<uint64_t, N>& buffer);
+  void multUnpacked6x32byPacked(std::array<uint64_t, 6>& factor1,
+                                std::array<uint64_t, 4>& factor2,
+                                std::array<uint64_t, 12>& product);
+  template <std::size_t N>
+  void multBuffBy10(std::array<uint64_t, N>& buffer);
+  template <std::size_t N>
+  int32_t normalizeMant(std::array<uint64_t, N>& mantissa);
+  template <std::size_t N>
+  int32_t roundUp(std::array<uint64_t, N>& mantissa);
+  template <std::size_t N, std::size_t P>
+  void pack_6x32_to_3x64(std::array<uint64_t, N>& unpackedMant,
+                         std::array<uint64_t, P>& result);
+  void unpack_3x64_to_6x32(std::array<uint64_t, 4>& qd192,
+                           std::array<uint64_t, 6>& buff_6x32);
+  template <std::size_t N>
+  void divBuffByPower2(std::array<uint64_t, N>& buffer, int32_t exp2);
+  template <std::size_t N>
+  void addToBuff(std::array<uint64_t, N>& buff, int32_t idx, uint64_t summand);
+};
+
+}  // namespace util
+}  // namespace firestore
+}  // namespace firebase
+
+#endif

+ 10 - 0
Firestore/core/test/unit/bundle/bundle_serializer_test.cc

@@ -653,6 +653,16 @@ TEST_F(BundleSerializerTest, DecodesInt32Value) {
   VerifyFieldValueRoundtrip(object);
 }
 
+TEST_F(BundleSerializerTest, DecodesDecimal128Value) {
+  ProtoValue decimal_value;
+  decimal_value.set_string_value("1.2e3");
+  ProtoValue object;
+  object.mutable_map_value()->mutable_fields()->insert(
+      {model::kRawDecimal128TypeFieldValue, decimal_value});
+
+  VerifyFieldValueRoundtrip(object);
+}
+
 TEST_F(BundleSerializerTest, DecodesRegexValue) {
   ProtoValue pattern_value;
   ProtoValue options_value;

+ 109 - 0
Firestore/core/test/unit/index/index_value_writer_test.cc

@@ -29,6 +29,7 @@ namespace {
 using testutil::BsonBinaryData;
 using testutil::BsonObjectId;
 using testutil::BsonTimestamp;
+using testutil::Decimal128;
 using testutil::Int32;
 using testutil::MaxKey;
 using testutil::MinKey;
@@ -296,6 +297,114 @@ TEST(IndexValueWriterTest, writeIndexValueSupportsLargestInt32) {
   EXPECT_EQ(actual_bytes, expected_bytes);
 }
 
+TEST(IndexValueWriterTest, writeIndexValueSupportsDecimal128) {
+  // Value
+  auto value = Decimal128("1.2e3");
+
+  // Actual
+  IndexEncodingBuffer encoder;
+  WriteIndexValue(*value, encoder.ForKind(model::Segment::Kind::kAscending));
+  auto& actual_bytes = encoder.GetEncodedBytes();
+
+  // Expected
+  IndexEncodingBuffer expected_encoder;
+  DirectionalIndexByteEncoder* index_byte_encoder =
+      expected_encoder.ForKind(model::Segment::Kind::kAscending);
+  index_byte_encoder->WriteLong(IndexType::kNumber);
+  // We currently store a 64-bit double representation in the client-side index.
+  index_byte_encoder->WriteDouble(1200.0);
+  index_byte_encoder->WriteInfinity();
+  auto& expected_bytes = expected_encoder.GetEncodedBytes();
+
+  EXPECT_EQ(actual_bytes, expected_bytes);
+}
+
+TEST(IndexValueWriterTest, writeIndexValueSupportsNegativeDecimal128) {
+  // Value
+  auto value = Decimal128("-1.2e3");
+
+  // Actual
+  IndexEncodingBuffer encoder;
+  WriteIndexValue(*value, encoder.ForKind(model::Segment::Kind::kAscending));
+  auto& actual_bytes = encoder.GetEncodedBytes();
+
+  // Expected
+  IndexEncodingBuffer expected_encoder;
+  DirectionalIndexByteEncoder* index_byte_encoder =
+      expected_encoder.ForKind(model::Segment::Kind::kAscending);
+  index_byte_encoder->WriteLong(IndexType::kNumber);
+  // We currently store a 64-bit double representation in the client-side index.
+  index_byte_encoder->WriteDouble(-1200.0);
+  index_byte_encoder->WriteInfinity();
+  auto& expected_bytes = expected_encoder.GetEncodedBytes();
+
+  EXPECT_EQ(actual_bytes, expected_bytes);
+}
+
+TEST(IndexValueWriterTest, writeIndexValueSupportsNaNDecimal128) {
+  // Value
+  auto value = Decimal128("NaN");
+
+  // Actual
+  IndexEncodingBuffer encoder;
+  WriteIndexValue(*value, encoder.ForKind(model::Segment::Kind::kAscending));
+  auto& actual_bytes = encoder.GetEncodedBytes();
+
+  // Expected
+  IndexEncodingBuffer expected_encoder;
+  DirectionalIndexByteEncoder* index_byte_encoder =
+      expected_encoder.ForKind(model::Segment::Kind::kAscending);
+  index_byte_encoder->WriteLong(IndexType::kNan);
+  index_byte_encoder->WriteInfinity();
+  auto& expected_bytes = expected_encoder.GetEncodedBytes();
+
+  EXPECT_EQ(actual_bytes, expected_bytes);
+}
+
+TEST(IndexValueWriterTest, writeIndexValueSupportsDecimal128Infinity) {
+  // Value
+  auto value = Decimal128("Infinity");
+
+  // Actual
+  IndexEncodingBuffer encoder;
+  WriteIndexValue(*value, encoder.ForKind(model::Segment::Kind::kAscending));
+  auto& actual_bytes = encoder.GetEncodedBytes();
+
+  // Expected
+  IndexEncodingBuffer expected_encoder;
+  DirectionalIndexByteEncoder* index_byte_encoder =
+      expected_encoder.ForKind(model::Segment::Kind::kAscending);
+  index_byte_encoder->WriteLong(IndexType::kNumber);
+  // We currently store a 64-bit double representation in the client-side index.
+  index_byte_encoder->WriteDouble(std::stod("Infinity"));
+  index_byte_encoder->WriteInfinity();
+  auto& expected_bytes = expected_encoder.GetEncodedBytes();
+
+  EXPECT_EQ(actual_bytes, expected_bytes);
+}
+
+TEST(IndexValueWriterTest, writeIndexValueSupportsDecimal128NegativeInfinity) {
+  // Value
+  auto value = Decimal128("-Infinity");
+
+  // Actual
+  IndexEncodingBuffer encoder;
+  WriteIndexValue(*value, encoder.ForKind(model::Segment::Kind::kAscending));
+  auto& actual_bytes = encoder.GetEncodedBytes();
+
+  // Expected
+  IndexEncodingBuffer expected_encoder;
+  DirectionalIndexByteEncoder* index_byte_encoder =
+      expected_encoder.ForKind(model::Segment::Kind::kAscending);
+  index_byte_encoder->WriteLong(IndexType::kNumber);
+  // We currently store a 64-bit double representation in the client-side index.
+  index_byte_encoder->WriteDouble(std::stod("-Infinity"));
+  index_byte_encoder->WriteInfinity();
+  auto& expected_bytes = expected_encoder.GetEncodedBytes();
+
+  EXPECT_EQ(actual_bytes, expected_bytes);
+}
+
 TEST(IndexValueWriterTest, writeIndexValueSupportsSmallestInt32) {
   // Value
   auto value = Int32(-2147483648);

+ 212 - 38
Firestore/core/test/unit/local/leveldb_index_manager_test.cc

@@ -45,6 +45,7 @@ using testutil::BsonBinaryData;
 using testutil::BsonObjectId;
 using testutil::BsonTimestamp;
 using testutil::CollectionGroupQuery;
+using testutil::Decimal128;
 using testutil::DeletedDoc;
 using testutil::Doc;
 using testutil::Filter;
@@ -1276,6 +1277,161 @@ TEST_F(LevelDbIndexManagerTest, IndexInt32Fields) {
   });
 }
 
+TEST_F(LevelDbIndexManagerTest, IndexDecimal128Fields) {
+  persistence_->Run("TestIndexDecimal128Fields", [&]() {
+    index_manager_->Start();
+    index_manager_->AddFieldIndex(
+        MakeFieldIndex("coll", "key", model::Segment::kAscending));
+
+    AddDoc("coll/doc1", Map("key", Decimal128("-Infinity")));
+    AddDoc("coll/doc2", Map("key", Decimal128("-0.0")));
+    AddDoc("coll/doc3", Map("key", Decimal128("0")));
+    AddDoc("coll/doc4", Map("key", Decimal128("1.3e-3")));
+    AddDoc("coll/doc5", Map("key", Decimal128("1.2e3")));
+    AddDoc("coll/doc6", Map("key", Decimal128("Infinity")));
+    AddDoc("coll/doc7", Map("key", Decimal128("NaN")));
+
+    auto base_query = Query("coll").AddingOrderBy(OrderBy("key"));
+
+    {
+      SCOPED_TRACE("no filter");
+      VerifyResults(base_query,
+                    {"coll/doc7", "coll/doc1", "coll/doc2", "coll/doc3",
+                     "coll/doc4", "coll/doc5", "coll/doc6"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with EqualTo filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", "==", Decimal128("1200")));
+      VerifyResults(query, {"coll/doc5"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with NotEqualTo filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", "!=", Decimal128("0")));
+      VerifyResults(query, {"coll/doc7", "coll/doc1", "coll/doc4", "coll/doc5",
+                            "coll/doc6"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with GreaterThanOrEqualTo filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", ">=", Decimal128("0.12e4")));
+      VerifyResults(query, {"coll/doc5", "coll/doc6"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with LessThanOrEqualTo filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", "<=", Decimal128("0.0")));
+      VerifyResults(query,
+                    {"coll/doc7", "coll/doc1", "coll/doc2", "coll/doc3"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with GreaterThan filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", ">", Decimal128("1200")));
+      VerifyResults(query, {"coll/doc6"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with LessThan filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", "<", Decimal128("-Infinity")));
+      VerifyResults(query, {"coll/doc7"});
+    }
+    {
+      SCOPED_TRACE(
+          "Query Decimal128 with GreaterThan filter and empty result set");
+      auto query =
+          base_query.AddingFilter(Filter("key", ">", Decimal128("Infinity")));
+      VerifyResults(query, {});
+    }
+    {
+      SCOPED_TRACE(
+          "Query Decimal128 with LessThan filter and empty result set");
+      auto query =
+          base_query.AddingFilter(Filter("key", "<", Decimal128("NaN")));
+      VerifyResults(query, {});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with GreaterThan NaN filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", ">", Decimal128("NaN")));
+      VerifyResults(query, {"coll/doc1", "coll/doc2", "coll/doc3", "coll/doc4",
+                            "coll/doc5", "coll/doc6"});
+    }
+  });
+}
+
+TEST_F(LevelDbIndexManagerTest, IndexDecimal128FieldsWithPrecisionLoss) {
+  persistence_->Run("TestIndexDecimal128Fields", [&]() {
+    index_manager_->Start();
+    index_manager_->AddFieldIndex(
+        MakeFieldIndex("coll", "key", model::Segment::kAscending));
+
+    AddDoc("coll/doc1",
+           Map("key",
+               Decimal128("-0.1234567890123456789")));  // will be rounded to
+                                                        // -0.12345678901234568
+    AddDoc("coll/doc2", Map("key", Decimal128("0")));
+    AddDoc("coll/doc3",
+           Map("key",
+               Decimal128("0.1234567890123456789")));  // will be rounded to
+                                                       // 0.12345678901234568
+
+    auto base_query = Query("coll").AddingOrderBy(OrderBy("key"));
+
+    {
+      SCOPED_TRACE("Query Decimal128 with EqualTo filter");
+      auto query = base_query.AddingFilter(
+          Filter("key", "==", Decimal128("0.1234567890123456789")));
+      VerifyResults(query, {"coll/doc3"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with EqualTo filter with rounding error");
+      // Mismatch behaviour caused by rounding error. Firestore fetches the doc3
+      // from LevelDb as doc3 rounds to the same number, even though the actual
+      // number in doc3 is different.
+      auto query = base_query.AddingFilter(
+          Filter("key", "==", Decimal128("0.12345678901234568")));
+      VerifyResults(query, {"coll/doc3"});
+    }
+
+    // Operations that doesn't go up to 17 decimal digits of precision wouldn't
+    // be affected by this rounding errors.
+    {
+      SCOPED_TRACE("Query Decimal128 with NotEqualTo filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", "!=", Decimal128("0.0")));
+      VerifyResults(query, {"coll/doc1", "coll/doc3"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with GreaterThanOrEqualTo filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", ">=", Decimal128("1.23e-1")));
+      VerifyResults(query, {"coll/doc3"});
+    }
+    {
+      SCOPED_TRACE("Query Decimal128 with LessThanOrEqualTo filter");
+      auto query =
+          base_query.AddingFilter(Filter("key", "<=", Decimal128("-1.23e-1")));
+      VerifyResults(query, {"coll/doc1"});
+    }
+    {
+      SCOPED_TRACE(
+          "Query Decimal128 with GreaterThan filter and empty result set");
+      auto query =
+          base_query.AddingFilter(Filter("key", ">", Decimal128("1.2e3")));
+      VerifyResults(query, {});
+    }
+    {
+      SCOPED_TRACE(
+          "Query Decimal128 with LessThan filter and empty result set");
+      auto query =
+          base_query.AddingFilter(Filter("key", "<", Decimal128("-1.2e3")));
+      VerifyResults(query, {});
+    }
+  });
+}
+
 TEST_F(LevelDbIndexManagerTest, IndexRegexFields) {
   persistence_->Run("TestIndexRegexFields", [&]() {
     index_manager_->Start();
@@ -1450,23 +1606,31 @@ TEST_F(LevelDbIndexManagerTest, IndexBsonTypesTogether) {
         MakeFieldIndex("coll", "key", model::Segment::kDescending));
 
     AddDoc("coll/doc1", Map("key", MinKey()));
-    AddDoc("coll/doc2", Map("key", Int32(2)));
-    AddDoc("coll/doc3", Map("key", Int32(1)));
-    AddDoc("coll/doc4", Map("key", BsonTimestamp(1, 2)));
-    AddDoc("coll/doc5", Map("key", BsonTimestamp(1, 1)));
-    AddDoc("coll/doc6", Map("key", BsonBinaryData(1, {1, 2, 4})));
-    AddDoc("coll/doc7", Map("key", BsonBinaryData(1, {1, 2, 3})));
-    AddDoc("coll/doc8", Map("key", BsonObjectId("507f191e810c19729de860eb")));
-    AddDoc("coll/doc9", Map("key", BsonObjectId("507f191e810c19729de860ea")));
-    AddDoc("coll/doc10", Map("key", Regex("a", "m")));
-    AddDoc("coll/doc11", Map("key", Regex("a", "i")));
-    AddDoc("coll/doc12", Map("key", MaxKey()));
+    AddDoc("coll/doc2", Map("key", Decimal128("NaN")));
+    AddDoc("coll/doc3", Map("key", Decimal128("-Infinity")));
+    AddDoc("coll/doc4", Map("key", Decimal128("Infinity")));
+    AddDoc("coll/doc5", Map("key", Decimal128("0")));
+    AddDoc("coll/doc6", Map("key", Decimal128("-1.2e3")));
+    AddDoc("coll/doc7", Map("key", Decimal128("2.3e-4")));
+    AddDoc("coll/doc8", Map("key", Int32(2)));
+    AddDoc("coll/doc9", Map("key", Int32(1)));
+    AddDoc("coll/doc10", Map("key", BsonTimestamp(1, 2)));
+    AddDoc("coll/doc11", Map("key", BsonTimestamp(1, 1)));
+    AddDoc("coll/doc12", Map("key", BsonBinaryData(1, {1, 2, 4})));
+    AddDoc("coll/doc13", Map("key", BsonBinaryData(1, {1, 2, 3})));
+    AddDoc("coll/doc14", Map("key", BsonObjectId("507f191e810c19729de860eb")));
+    AddDoc("coll/doc15", Map("key", BsonObjectId("507f191e810c19729de860ea")));
+    AddDoc("coll/doc16", Map("key", Regex("a", "m")));
+    AddDoc("coll/doc17", Map("key", Regex("a", "i")));
+    AddDoc("coll/doc18", Map("key", MaxKey()));
 
     auto query = Query("coll").AddingOrderBy(OrderBy("key", "desc"));
 
-    VerifyResults(query, {"coll/doc12", "coll/doc10", "coll/doc11", "coll/doc8",
-                          "coll/doc9", "coll/doc6", "coll/doc7", "coll/doc4",
-                          "coll/doc5", "coll/doc2", "coll/doc3", "coll/doc1"});
+    VerifyResults(query, {"coll/doc18", "coll/doc16", "coll/doc17",
+                          "coll/doc14", "coll/doc15", "coll/doc12",
+                          "coll/doc13", "coll/doc10", "coll/doc11", "coll/doc4",
+                          "coll/doc8", "coll/doc9", "coll/doc7", "coll/doc5",
+                          "coll/doc6", "coll/doc3", "coll/doc2", "coll/doc1"});
   });
 }
 
@@ -1476,33 +1640,43 @@ TEST_F(LevelDbIndexManagerTest, IndexAllTypesTogether) {
     index_manager_->AddFieldIndex(
         MakeFieldIndex("coll", "key", model::Segment::kDescending));
 
-    AddDoc("coll/a", Map("key", nullptr));
-    AddDoc("coll/b", Map("key", MinKey()));
-    AddDoc("coll/c", Map("key", true));
-    AddDoc("coll/d", Map("key", std::numeric_limits<double>::quiet_NaN()));
-    AddDoc("coll/e", Map("key", Int32(1)));
-    AddDoc("coll/f", Map("key", 2.0));
-    AddDoc("coll/g", Map("key", 3));
-    AddDoc("coll/h", Map("key", Timestamp(100, 123456000)));
-    AddDoc("coll/i", Map("key", BsonTimestamp(1, 2)));
-    AddDoc("coll/j", Map("key", "string"));
-    AddDoc("coll/k", Map("key", BlobValue(0, 1, 255)));
-    AddDoc("coll/l", Map("key", BsonBinaryData(1, {1, 2, 3})));
-    AddDoc("coll/m", Map("key", Ref("project", "coll/doc")));
-    AddDoc("coll/n", Map("key", BsonObjectId("507f191e810c19729de860ea")));
-    AddDoc("coll/o", Map("key", GeoPoint(0, 1)));
-    AddDoc("coll/p", Map("key", Regex("^foo", "i")));
-    AddDoc("coll/q", Map("key", Array(1, 2)));
-    AddDoc("coll/r", Map("key", VectorType(1, 2)));
-    AddDoc("coll/s", Map("key", Map("a", 1)));
-    AddDoc("coll/t", Map("key", MaxKey()));
+    AddDoc("coll/doc1", Map("key", nullptr));
+    AddDoc("coll/doc2", Map("key", MinKey()));
+    AddDoc("coll/doc3", Map("key", true));
+    AddDoc("coll/doc4", Map("key", std::numeric_limits<double>::quiet_NaN()));
+    AddDoc("coll/doc5", Map("key", Decimal128("NaN")));
+    AddDoc("coll/doc6", Map("key", Decimal128("-Infinifty")));
+    AddDoc("coll/doc7", Map("key", Decimal128("-1.2e-3")));
+    AddDoc("coll/doc8", Map("key", Decimal128("0")));
+    AddDoc("coll/doc9", Map("key", Int32(1)));
+    AddDoc("coll/doc10", Map("key", 2.0));
+    AddDoc("coll/doc11", Map("key", 3));
+    AddDoc("coll/doc12", Map("key", Decimal128("2.3e4")));
+    AddDoc("coll/doc13", Map("key", Decimal128("Infinifty")));
+    AddDoc("coll/doc14", Map("key", Timestamp(100, 123456000)));
+    AddDoc("coll/doc15", Map("key", BsonTimestamp(1, 2)));
+    AddDoc("coll/doc16", Map("key", "string"));
+    AddDoc("coll/doc17", Map("key", BlobValue(0, 1, 255)));
+    AddDoc("coll/doc18", Map("key", BsonBinaryData(1, {1, 2, 3})));
+    AddDoc("coll/doc19", Map("key", Ref("project", "coll/doc")));
+    AddDoc("coll/doc20", Map("key", BsonObjectId("507f191e810c19729de860ea")));
+    AddDoc("coll/doc21", Map("key", GeoPoint(0, 1)));
+    AddDoc("coll/doc22", Map("key", Regex("^foo", "i")));
+    AddDoc("coll/doc23", Map("key", Array(1, 2)));
+    AddDoc("coll/doc24", Map("key", VectorType(1, 2)));
+    AddDoc("coll/doc25", Map("key", Map("a", 1)));
+    AddDoc("coll/doc26", Map("key", MaxKey()));
 
     auto query = Query("coll").AddingOrderBy(OrderBy("key", "desc"));
 
-    VerifyResults(query, {"coll/t", "coll/s", "coll/r", "coll/q", "coll/p",
-                          "coll/o", "coll/n", "coll/m", "coll/l", "coll/k",
-                          "coll/j", "coll/i", "coll/h", "coll/g", "coll/f",
-                          "coll/e", "coll/d", "coll/c", "coll/b", "coll/a"});
+    VerifyResults(
+        query,
+        {"coll/doc26", "coll/doc25", "coll/doc24", "coll/doc23", "coll/doc22",
+         "coll/doc21", "coll/doc20", "coll/doc19", "coll/doc18", "coll/doc17",
+         "coll/doc16", "coll/doc15", "coll/doc14", "coll/doc13", "coll/doc12",
+         "coll/doc11", "coll/doc10", "coll/doc9",  "coll/doc8",  "coll/doc7",
+         "coll/doc6",  "coll/doc5",  "coll/doc4",  "coll/doc3",  "coll/doc2",
+         "coll/doc1"});
   });
 }
 

+ 237 - 26
Firestore/core/test/unit/local/leveldb_local_store_test.cc

@@ -42,6 +42,7 @@ using testutil::BlobValue;
 using testutil::BsonBinaryData;
 using testutil::BsonObjectId;
 using testutil::BsonTimestamp;
+using testutil::Decimal128;
 using testutil::DeletedDoc;
 using testutil::DeleteMutation;
 using testutil::Doc;
@@ -714,6 +715,188 @@ TEST_F(LevelDbLocalStoreTest, IndexesInt32) {
   FSTAssertQueryReturned("coll/doc1", "coll/doc2");
 }
 
+TEST_F(LevelDbLocalStoreTest, IndexesDecimal128) {
+  FieldIndex index = MakeFieldIndex("coll", 0, FieldIndex::InitialState(),
+                                    "key", model::Segment::Kind::kAscending);
+  ConfigureFieldIndexes({index});
+
+  WriteMutation(SetMutation("coll/doc1", Map("key", Decimal128("NaN"))));
+  WriteMutation(SetMutation("coll/doc2", Map("key", Decimal128("-Infinity"))));
+  WriteMutation(SetMutation("coll/doc3", Map("key", Decimal128("-1.2e3"))));
+  WriteMutation(SetMutation("coll/doc4", Map("key", Decimal128("0"))));
+  WriteMutation(SetMutation("coll/doc5", Map("key", Decimal128("2.3e-4"))));
+  WriteMutation(SetMutation("coll/doc6", Map("key", Decimal128("Infinity"))));
+
+  BackfillIndexes();
+
+  core::Query query =
+      testutil::Query("coll").AddingOrderBy(OrderBy("key", "asc"));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 6, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc1"), model::Mutation::Type::Set},
+                      {Key("coll/doc2"), model::Mutation::Type::Set},
+                      {Key("coll/doc3"), model::Mutation::Type::Set},
+                      {Key("coll/doc4"), model::Mutation::Type::Set},
+                      {Key("coll/doc5"), model::Mutation::Type::Set},
+                      {Key("coll/doc6"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc1", "coll/doc2", "coll/doc3", "coll/doc4",
+                         "coll/doc5", "coll/doc6");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "==", Decimal128("-1200")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 1, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc3"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc3");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "!=", Decimal128("0.0")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 5, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc1"), model::Mutation::Type::Set},
+                      {Key("coll/doc2"), model::Mutation::Type::Set},
+                      {Key("coll/doc3"), model::Mutation::Type::Set},
+                      {Key("coll/doc5"), model::Mutation::Type::Set},
+                      {Key("coll/doc6"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc1", "coll/doc2", "coll/doc3", "coll/doc5",
+                         "coll/doc6");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", ">=", Decimal128("1e-5")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 2, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc5"), model::Mutation::Type::Set},
+                      {Key("coll/doc6"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc5", "coll/doc6");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "<=", Decimal128("-1.2e3")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 3, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc1"), model::Mutation::Type::Set},
+                      {Key("coll/doc2"), model::Mutation::Type::Set},
+                      {Key("coll/doc3"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc1", "coll/doc2", "coll/doc3");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", ">", Decimal128("Infinity")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 0, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(OverlayTypeMap());
+  FSTAssertQueryReturned();
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "<", Decimal128("NaN")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 0, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(OverlayTypeMap());
+  FSTAssertQueryReturned();
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "in", Array(Decimal128("0"), Decimal128("2.3e-4"))));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 2, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc4"), model::Mutation::Type::Set},
+                      {Key("coll/doc5"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc4", "coll/doc5");
+}
+
+TEST_F(LevelDbLocalStoreTest, IndexesDecimal128WithPrecisionLoss) {
+  FieldIndex index = MakeFieldIndex("coll", 0, FieldIndex::InitialState(),
+                                    "key", model::Segment::Kind::kAscending);
+  ConfigureFieldIndexes({index});
+
+  WriteMutation(SetMutation(
+      "coll/doc1",
+      Map("key",
+          Decimal128("-0.1234567890123456789"))));  // will be rounded to
+                                                    // -0.12345678901234568
+  WriteMutation(SetMutation("coll/doc2", Map("key", Decimal128("0"))));
+  WriteMutation(SetMutation(
+      "coll/doc3",
+      Map("key", Decimal128("0.1234567890123456789"))));  // will be rounded to
+                                                          // 0.12345678901234568
+
+  BackfillIndexes();
+
+  core::Query query =
+      testutil::Query("coll").AddingOrderBy(OrderBy("key", "asc"));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 3, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc1"), model::Mutation::Type::Set},
+                      {Key("coll/doc2"), model::Mutation::Type::Set},
+                      {Key("coll/doc3"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc1", "coll/doc2", "coll/doc3");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "==", Decimal128("0.1234567890123456789")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 1, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc3"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc3");
+
+  // Mismatch behaviour caused by rounding error. Firestore fetches the doc3
+  // from LevelDB as doc3 rounds to the same number, but, it is not presented in
+  // the final query result.
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "==", Decimal128("0.12345678901234568")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 1, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc3"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned();
+
+  // Operations that doesn't go up to 17 decimal digits of precision wouldn't be
+  // affected by rounding errors.
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "!=", Decimal128("0")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 2, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc1"), model::Mutation::Type::Set},
+                      {Key("coll/doc3"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc1", "coll/doc3");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", ">=", Decimal128("1.23e-1")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 1, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc3"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc3");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "<=", Decimal128("-1.23e-1")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 1, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(
+      OverlayTypeMap({{Key("coll/doc1"), model::Mutation::Type::Set}}));
+  FSTAssertQueryReturned("coll/doc1");
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", ">", Decimal128("1.2e3")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 0, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(OverlayTypeMap());
+  FSTAssertQueryReturned();
+
+  query = testutil::Query("coll").AddingFilter(
+      Filter("key", "<", Decimal128("-1.2e3")));
+  ExecuteQuery(query);
+  FSTAssertOverlaysRead(/* byKey= */ 0, /* byCollection= */ 0);
+  FSTAssertOverlayTypes(OverlayTypeMap());
+  FSTAssertQueryReturned();
+}
+
 TEST_F(LevelDbLocalStoreTest, IndexesMinKey) {
   FieldIndex index = MakeFieldIndex("coll", 0, FieldIndex::InitialState(),
                                     "key", model::Segment::Kind::kAscending);
@@ -901,13 +1084,19 @@ TEST_F(LevelDbLocalStoreTest, IndexesAllBsonTypesTogether) {
   WriteMutation(SetMutation("coll/doc10", Map("key", Regex("^bar", "m"))));
   WriteMutation(SetMutation("coll/doc11", Map("key", Regex("^bar", "i"))));
   WriteMutation(SetMutation("coll/doc12", Map("key", MaxKey())));
+  WriteMutation(SetMutation("coll/doc13", Map("key", Decimal128("NaN"))));
+  WriteMutation(SetMutation("coll/doc14", Map("key", Decimal128("-Infinity"))));
+  WriteMutation(SetMutation("coll/doc15", Map("key", Decimal128("Infinity"))));
+  WriteMutation(SetMutation("coll/doc16", Map("key", Decimal128("0"))));
+  WriteMutation(SetMutation("coll/doc17", Map("key", Decimal128("-1.2e-3"))));
+  WriteMutation(SetMutation("coll/doc18", Map("key", Decimal128("1.2e3"))));
 
   BackfillIndexes();
 
   core::Query query =
       testutil::Query("coll").AddingOrderBy(OrderBy("key", "desc"));
   ExecuteQuery(query);
-  FSTAssertOverlaysRead(/* byKey= */ 12, /* byCollection= */ 0);
+  FSTAssertOverlaysRead(/* byKey= */ 18, /* byCollection= */ 0);
   FSTAssertOverlayTypes(
       OverlayTypeMap({{Key("coll/doc1"), model::Mutation::Type::Set},
                       {Key("coll/doc2"), model::Mutation::Type::Set},
@@ -920,11 +1109,19 @@ TEST_F(LevelDbLocalStoreTest, IndexesAllBsonTypesTogether) {
                       {Key("coll/doc9"), model::Mutation::Type::Set},
                       {Key("coll/doc10"), model::Mutation::Type::Set},
                       {Key("coll/doc11"), model::Mutation::Type::Set},
-                      {Key("coll/doc12"), model::Mutation::Type::Set}}));
+                      {Key("coll/doc12"), model::Mutation::Type::Set},
+                      {Key("coll/doc13"), model::Mutation::Type::Set},
+                      {Key("coll/doc14"), model::Mutation::Type::Set},
+                      {Key("coll/doc15"), model::Mutation::Type::Set},
+                      {Key("coll/doc16"), model::Mutation::Type::Set},
+                      {Key("coll/doc17"), model::Mutation::Type::Set},
+                      {Key("coll/doc18"), model::Mutation::Type::Set}}));
 
   FSTAssertQueryReturned("coll/doc12", "coll/doc10", "coll/doc11", "coll/doc8",
                          "coll/doc9", "coll/doc6", "coll/doc7", "coll/doc4",
-                         "coll/doc5", "coll/doc2", "coll/doc3", "coll/doc1");
+                         "coll/doc5", "coll/doc15", "coll/doc18", "coll/doc2",
+                         "coll/doc3", "coll/doc16", "coll/doc17", "coll/doc14",
+                         "coll/doc13", "coll/doc1");
 }
 
 TEST_F(LevelDbLocalStoreTest, IndexesAllTypesTogether) {
@@ -936,35 +1133,41 @@ TEST_F(LevelDbLocalStoreTest, IndexesAllTypesTogether) {
   WriteMutation(SetMutation("coll/doc2", Map("key", MinKey())));
   WriteMutation(SetMutation("coll/doc3", Map("key", true)));
   WriteMutation(SetMutation("coll/doc4", Map("key", NAN)));
-  WriteMutation(SetMutation("coll/doc5", Map("key", Int32(1))));
-  WriteMutation(SetMutation("coll/doc6", Map("key", 2.0)));
-  WriteMutation(SetMutation("coll/doc7", Map("key", 3L)));
+  WriteMutation(SetMutation("coll/doc5", Map("key", Decimal128("NaN"))));
+  WriteMutation(SetMutation("coll/doc6", Map("key", Decimal128("-Infinity"))));
+  WriteMutation(SetMutation("coll/doc7", Map("key", Decimal128("-1.2e-3"))));
+  WriteMutation(SetMutation("coll/doc8", Map("key", Decimal128("0"))));
+  WriteMutation(SetMutation("coll/doc9", Map("key", Int32(1))));
+  WriteMutation(SetMutation("coll/doc10", Map("key", 2.0)));
+  WriteMutation(SetMutation("coll/doc11", Map("key", 3L)));
+  WriteMutation(SetMutation("coll/doc12", Map("key", Decimal128("1.2e3"))));
+  WriteMutation(SetMutation("coll/doc13", Map("key", Decimal128("Infinity"))));
   WriteMutation(
-      SetMutation("coll/doc8", Map("key", Timestamp(100, 123456000))));
-  WriteMutation(SetMutation("coll/doc9", Map("key", BsonTimestamp(1, 2))));
-  WriteMutation(SetMutation("coll/doc10", Map("key", "string")));
-  WriteMutation(SetMutation("coll/doc11", Map("key", BlobValue(1, 2, 3))));
+      SetMutation("coll/doc14", Map("key", Timestamp(100, 123456000))));
+  WriteMutation(SetMutation("coll/doc15", Map("key", BsonTimestamp(1, 2))));
+  WriteMutation(SetMutation("coll/doc16", Map("key", "string")));
+  WriteMutation(SetMutation("coll/doc17", Map("key", BlobValue(1, 2, 3))));
   WriteMutation(
-      SetMutation("coll/doc12", Map("key", BsonBinaryData(1, {1, 2, 3}))));
+      SetMutation("coll/doc18", Map("key", BsonBinaryData(1, {1, 2, 3}))));
   WriteMutation(
-      SetMutation("coll/doc13", Map("key", Ref("project/db", "col/doc"))));
+      SetMutation("coll/doc19", Map("key", Ref("project/db", "col/doc"))));
   WriteMutation(SetMutation(
-      "coll/doc14", Map("key", BsonObjectId("507f191e810c19729de860ea"))));
-  WriteMutation(SetMutation("coll/doc15", Map("key", GeoPoint(1, 2))));
-  WriteMutation(SetMutation("coll/doc16", Map("key", Regex("^bar", "m"))));
-  WriteMutation(SetMutation("coll/doc17", Map("key", Array(2L, "foo"))));
+      "coll/doc20", Map("key", BsonObjectId("507f191e810c19729de860ea"))));
+  WriteMutation(SetMutation("coll/doc21", Map("key", GeoPoint(1, 2))));
+  WriteMutation(SetMutation("coll/doc22", Map("key", Regex("^bar", "m"))));
+  WriteMutation(SetMutation("coll/doc23", Map("key", Array(2L, "foo"))));
   WriteMutation(
-      SetMutation("coll/doc18", Map("key", VectorType(1.0, 2.0, 3.0))));
+      SetMutation("coll/doc24", Map("key", VectorType(1.0, 2.0, 3.0))));
   WriteMutation(
-      SetMutation("coll/doc19", Map("key", Map("bar", 1L, "foo", 2L))));
-  WriteMutation(SetMutation("coll/doc20", Map("key", MaxKey())));
+      SetMutation("coll/doc25", Map("key", Map("bar", 1L, "foo", 2L))));
+  WriteMutation(SetMutation("coll/doc26", Map("key", MaxKey())));
 
   BackfillIndexes();
 
   core::Query query =
       testutil::Query("coll").AddingOrderBy(OrderBy("key", "asc"));
   ExecuteQuery(query);
-  FSTAssertOverlaysRead(/* byKey= */ 20, /* byCollection= */ 0);
+  FSTAssertOverlaysRead(/* byKey= */ 26, /* byCollection= */ 0);
   FSTAssertOverlayTypes(
       OverlayTypeMap({{Key("coll/doc1"), model::Mutation::Type::Set},
                       {Key("coll/doc2"), model::Mutation::Type::Set},
@@ -985,13 +1188,21 @@ TEST_F(LevelDbLocalStoreTest, IndexesAllTypesTogether) {
                       {Key("coll/doc17"), model::Mutation::Type::Set},
                       {Key("coll/doc18"), model::Mutation::Type::Set},
                       {Key("coll/doc19"), model::Mutation::Type::Set},
-                      {Key("coll/doc20"), model::Mutation::Type::Set}}));
+                      {Key("coll/doc20"), model::Mutation::Type::Set},
+                      {Key("coll/doc21"), model::Mutation::Type::Set},
+                      {Key("coll/doc22"), model::Mutation::Type::Set},
+                      {Key("coll/doc23"), model::Mutation::Type::Set},
+                      {Key("coll/doc24"), model::Mutation::Type::Set},
+                      {Key("coll/doc25"), model::Mutation::Type::Set},
+                      {Key("coll/doc26"), model::Mutation::Type::Set}}));
 
-  FSTAssertQueryReturned(
-      "coll/doc1", "coll/doc2", "coll/doc3", "coll/doc4", "coll/doc5",
-      "coll/doc6", "coll/doc7", "coll/doc8", "coll/doc9", "coll/doc10",
-      "coll/doc11", "coll/doc12", "coll/doc13", "coll/doc14", "coll/doc15",
-      "coll/doc16", "coll/doc17", "coll/doc18", "coll/doc19", "coll/doc20");
+  FSTAssertQueryReturned("coll/doc1", "coll/doc2", "coll/doc3", "coll/doc4",
+                         "coll/doc5", "coll/doc6", "coll/doc7", "coll/doc8",
+                         "coll/doc9", "coll/doc10", "coll/doc11", "coll/doc12",
+                         "coll/doc13", "coll/doc14", "coll/doc15", "coll/doc16",
+                         "coll/doc17", "coll/doc18", "coll/doc19", "coll/doc20",
+                         "coll/doc21", "coll/doc22", "coll/doc23", "coll/doc24",
+                         "coll/doc25", "coll/doc26");
 }
 
 TEST_F(LevelDbLocalStoreTest, IndexesServerTimestamps) {

+ 5 - 2
Firestore/core/test/unit/model/document_test.cc

@@ -28,6 +28,7 @@ namespace model {
 using testutil::BsonBinaryData;
 using testutil::BsonObjectId;
 using testutil::BsonTimestamp;
+using testutil::Decimal128;
 using testutil::DeletedDoc;
 using testutil::Doc;
 using testutil::Field;
@@ -81,8 +82,9 @@ TEST(DocumentTest, ExtractsFields) {
 TEST(DocumentTest, CanContainBsonTypes) {
   auto data = WrapObject(
       Map("minKey", MinKey(), "maxKey", MaxKey(), "regex", Regex("^foo", "i"),
-          "int32", Int32(1234), "objectId", BsonObjectId("foo"), "timestamp",
-          BsonTimestamp(123, 456), "binary", BsonBinaryData(128, {7, 8, 9})));
+          "int32", Int32(1234), "decimal128", Decimal128("1.234e2"), "objectId",
+          BsonObjectId("foo"), "timestamp", BsonTimestamp(123, 456), "binary",
+          BsonBinaryData(128, {7, 8, 9})));
 
   auto doc = MutableDocument::FoundDocument(Key("col/doc"), Version(1), data);
 
@@ -92,6 +94,7 @@ TEST(DocumentTest, CanContainBsonTypes) {
   EXPECT_EQ(doc.field(Field("maxKey")), *MaxKey());
   EXPECT_EQ(doc.field(Field("regex")), *Regex("^foo", "i"));
   EXPECT_EQ(doc.field(Field("int32")), *Int32(1234));
+  EXPECT_EQ(doc.field(Field("decimal128")), *Decimal128("1.234e2"));
   EXPECT_EQ(doc.field(Field("objectId")), *BsonObjectId("foo"));
   EXPECT_EQ(doc.field(Field("timestamp")), *BsonTimestamp(123, 456));
   EXPECT_EQ(doc.field(Field("binary")), *BsonBinaryData(128, {7, 8, 9}));

+ 29 - 23
Firestore/core/test/unit/model/object_value_test.cc

@@ -35,6 +35,7 @@ using testutil::BsonBinaryData;
 using testutil::BsonObjectId;
 using testutil::BsonTimestamp;
 using testutil::DbId;
+using testutil::Decimal128;
 using testutil::Field;
 using testutil::Int32;
 using testutil::Map;
@@ -53,8 +54,9 @@ TEST_F(ObjectValueTest, ExtractsFields) {
   ObjectValue value = WrapObject(
       "foo", Map("a", 1, "b", true, "c", "string"), "bson",
       Map("minKey", MinKey(), "maxKey", MaxKey(), "regex", Regex("^foo", "i"),
-          "int32", Int32(1234), "objectId", BsonObjectId("foo"), "timestamp",
-          BsonTimestamp(123, 456), "binary", BsonBinaryData(128, {7, 8, 9})));
+          "int32", Int32(1234), "decimal128", Decimal128("1.234e5"), "objectId",
+          BsonObjectId("foo"), "timestamp", BsonTimestamp(123, 456), "binary",
+          BsonBinaryData(128, {7, 8, 9})));
 
   ASSERT_EQ(google_firestore_v1_Value_map_value_tag,
             value.Get(Field("foo"))->which_value_type);
@@ -62,12 +64,12 @@ TEST_F(ObjectValueTest, ExtractsFields) {
   EXPECT_EQ(*Value(1), *value.Get(Field("foo.a")));
   EXPECT_EQ(*Value(true), *value.Get(Field("foo.b")));
   EXPECT_EQ(*Value("string"), *value.Get(Field("foo.c")));
-  EXPECT_EQ(
-      *Value(Map("minKey", MinKey(), "maxKey", MaxKey(), "regex",
-                 Regex("^foo", "i"), "int32", Int32(1234), "objectId",
-                 BsonObjectId("foo"), "timestamp", BsonTimestamp(123, 456),
-                 "binary", BsonBinaryData(128, {7, 8, 9}))),
-      *value.Get(Field("bson")));
+  EXPECT_EQ(*Value(Map("minKey", MinKey(), "maxKey", MaxKey(), "regex",
+                       Regex("^foo", "i"), "int32", Int32(1234), "decimal128",
+                       Decimal128("1.234e5"), "objectId", BsonObjectId("foo"),
+                       "timestamp", BsonTimestamp(123, 456), "binary",
+                       BsonBinaryData(128, {7, 8, 9}))),
+            *value.Get(Field("bson")));
   EXPECT_EQ(nullopt, value.Get(Field("foo.a.b")));
   EXPECT_EQ(nullopt, value.Get(Field("bar")));
   EXPECT_EQ(nullopt, value.Get(Field("bar.a")));
@@ -79,15 +81,17 @@ TEST_F(ObjectValueTest, ExtractsFieldMask) {
       Map("a", 1, "b", true, "c", "string", "nested", Map("d", "e")),
       "emptymap", Map(), "bson",
       Value(Map("minKey", MinKey(), "maxKey", MaxKey(), "regex",
-                Regex("^foo", "i"), "int32", Int32(1234), "objectId",
-                BsonObjectId("foo"), "timestamp", BsonTimestamp(123, 456),
-                "binary", BsonBinaryData(128, {7, 8, 9}))));
-
-  FieldMask expected_mask = FieldMask(
-      {Field("a"), Field("Map.a"), Field("Map.b"), Field("Map.c"),
-       Field("Map.nested.d"), Field("emptymap"), Field("bson.minKey"),
-       Field("bson.maxKey"), Field("bson.regex"), Field("bson.int32"),
-       Field("bson.objectId"), Field("bson.timestamp"), Field("bson.binary")});
+                Regex("^foo", "i"), "int32", Int32(1234), "decimal128",
+                Decimal128("1.234e5"), "objectId", BsonObjectId("foo"),
+                "timestamp", BsonTimestamp(123, 456), "binary",
+                BsonBinaryData(128, {7, 8, 9}))));
+
+  FieldMask expected_mask =
+      FieldMask({Field("a"), Field("Map.a"), Field("Map.b"), Field("Map.c"),
+                 Field("Map.nested.d"), Field("emptymap"), Field("bson.minKey"),
+                 Field("bson.maxKey"), Field("bson.regex"), Field("bson.int32"),
+                 Field("bson.decimal128"), Field("bson.objectId"),
+                 Field("bson.timestamp"), Field("bson.binary")});
   FieldMask actual_mask = value.ToFieldMask();
 
   EXPECT_EQ(expected_mask, actual_mask);
@@ -363,15 +367,17 @@ TEST_F(ObjectValueTest, CanHandleBsonTypesInObjectValue) {
   object_value.Set(Field("maxKey"), MaxKey());
   object_value.Set(Field("regex"), Regex("^foo", "i"));
   object_value.Set(Field("int32"), Int32(1234));
+  object_value.Set(Field("decimal128"), Decimal128("1.2e3"));
   object_value.Set(Field("objectId"), BsonObjectId("foo"));
   object_value.Set(Field("timestamp"), BsonTimestamp(123, 456));
   object_value.Set(Field("binary"), BsonBinaryData(128, {7, 8, 9}));
 
   EXPECT_EQ(
       WrapObject(Map("minKey", MinKey(), "maxKey", MaxKey(), "regex",
-                     Regex("^foo", "i"), "int32", Int32(1234), "objectId",
-                     BsonObjectId("foo"), "timestamp", BsonTimestamp(123, 456),
-                     "binary", BsonBinaryData(128, {7, 8, 9}))),
+                     Regex("^foo", "i"), "int32", Int32(1234), "decimal128",
+                     Decimal128("1.2e3"), "objectId", BsonObjectId("foo"),
+                     "timestamp", BsonTimestamp(123, 456), "binary",
+                     BsonBinaryData(128, {7, 8, 9}))),
       object_value);
 
   // Overwrite existing fields
@@ -392,9 +398,9 @@ TEST_F(ObjectValueTest, CanHandleBsonTypesInObjectValue) {
   EXPECT_EQ(
       WrapObject(Map(
           "minKey", MinKey(), "maxKey", MaxKey(), "regex", Regex("^baz", "g"),
-          "int32", Int32(1234), "objectId", BsonObjectId("new-foo-value"),
-          "timestamp", BsonTimestamp(123, 456), "binary",
-          BsonBinaryData(128, {7, 8, 9}), "foo",
+          "int32", Int32(1234), "decimal128", Decimal128("1.2e3"), "objectId",
+          BsonObjectId("new-foo-value"), "timestamp", BsonTimestamp(123, 456),
+          "binary", BsonBinaryData(128, {7, 8, 9}), "foo",
           Map("regex2", Regex("^bar", "x"), "timestamp", BsonTimestamp(2, 1)))),
       object_value);
 }

+ 63 - 11
Firestore/core/test/unit/model/value_util_test.cc

@@ -44,6 +44,7 @@ using testutil::BsonBinaryData;
 using testutil::BsonObjectId;
 using testutil::BsonTimestamp;
 using testutil::DbId;
+using testutil::Decimal128;
 using testutil::Int32;
 using testutil::kCanonicalNanBits;
 using testutil::Key;
@@ -222,6 +223,10 @@ TEST(FieldValueTest, ValueHelpers) {
   ASSERT_EQ(GetTypeOrder(*int32_value), TypeOrder::kNumber);
   ASSERT_EQ(DetectMapType(*int32_value), MapType::kInt32);
 
+  auto decimal128_value = Decimal128("1.2e3");
+  ASSERT_EQ(GetTypeOrder(*decimal128_value), TypeOrder::kNumber);
+  ASSERT_EQ(DetectMapType(*decimal128_value), MapType::kDecimal128);
+
   auto bson_object_id_value = BsonObjectId("foo");
   ASSERT_EQ(GetTypeOrder(*bson_object_id_value), TypeOrder::kBsonObjectId);
   ASSERT_EQ(DetectMapType(*bson_object_id_value), MapType::kBsonObjectId);
@@ -251,6 +256,35 @@ TEST(FieldValueTest, CanonicalBitsAreCanonical) {
 }
 #endif  // __APPLE__
 
+TEST_F(ValueUtilTest, Decimal128Comparison) {
+  EXPECT_EQ(Compare(*Decimal128("NaN"), *Value(std::nan("1"))),
+            ComparisonResult::Same);
+  EXPECT_EQ(Compare(*Decimal128("NaN"), *Decimal128("NaN")),
+            ComparisonResult::Same);
+  EXPECT_EQ(Compare(*Decimal128("NaN"), *Decimal128("-Infinity")),
+            ComparisonResult::Ascending);
+  EXPECT_EQ(Compare(*Decimal128("NaN"), *Decimal128("+Infinity")),
+            ComparisonResult::Ascending);
+  EXPECT_EQ(Compare(*Decimal128("NaN"), *Decimal128("-1.2e-3")),
+            ComparisonResult::Ascending);
+  EXPECT_EQ(Compare(*Decimal128("1"), *Decimal128("2e-4")),
+            ComparisonResult::Descending);
+  EXPECT_EQ(Compare(*Decimal128("-1"), *Decimal128("-2e-4")),
+            ComparisonResult::Ascending);
+  EXPECT_EQ(Compare(*Decimal128("-0.0"), *Decimal128("0.0")),
+            ComparisonResult::Same);
+  EXPECT_EQ(Compare(*Decimal128("-0.0"), *Decimal128("+0.0")),
+            ComparisonResult::Same);
+  EXPECT_EQ(Compare(*Decimal128("-0"), *Decimal128("0")),
+            ComparisonResult::Same);
+  EXPECT_EQ(Compare(*Decimal128("1500e-3"), *Value(1.5F)),
+            ComparisonResult::Same);
+  EXPECT_EQ(Compare(*Decimal128("-1000e-3"), *Value(-1L)),
+            ComparisonResult::Same);
+  EXPECT_EQ(Compare(*Decimal128("3.4e-5"), *Value(2.0F)),
+            ComparisonResult::Ascending);
+}
+
 TEST_F(ValueUtilTest, Equality) {
   // Create a matrix that defines an equality group. The outer vector has
   // multiple rows and each row can have an arbitrary number of entries.
@@ -265,6 +299,8 @@ TEST_F(ValueUtilTest, Equality) {
   Add(equals_group, std::numeric_limits<double>::quiet_NaN(),
       ToDouble(kCanonicalNanBits), ToDouble(kAlternateNanBits), std::nan("1"),
       std::nan("2"));
+
+  Add(equals_group, Decimal128("-Infinity"));
   // -0.0 and 0.0 compare the same but are not equal.
   Add(equals_group, -0.0);
   Add(equals_group, 0.0);
@@ -272,8 +308,11 @@ TEST_F(ValueUtilTest, Equality) {
   // Doubles and Longs aren't equal (even though they compare same).
   Add(equals_group, 1.0, 1.0);
   Add(equals_group, 1.1, 1.1);
+  Add(equals_group, Decimal128("-1.2"), Decimal128("-12e-1"));
   Add(equals_group, Int32(-1), Int32(-1));
   Add(equals_group, Int32(1), Int32(1));
+  Add(equals_group, Decimal128("1.2"), Decimal128("12e-1"));
+  Add(equals_group, Decimal128("Infinity"));
   Add(equals_group, BlobValue(0, 1, 1));
   Add(equals_group, BlobValue(0, 1));
   Add(equals_group, "string", "string");
@@ -343,18 +382,19 @@ TEST_F(ValueUtilTest, StrictOrdering) {
 
   // numbers
   Add(comparison_groups, DeepClone(MinNumber()));
-  Add(comparison_groups, -1e20);
+  Add(comparison_groups, -1e20, Decimal128("-1e20"));
   Add(comparison_groups, std::numeric_limits<int64_t>::min());
   Add(comparison_groups, -0.1);
   // Zeros all compare the same.
-  Add(comparison_groups, -0.0, 0.0, 0L, Int32(0));
+  Add(comparison_groups, -0.0, 0.0, 0L, Int32(0), Decimal128("0"),
+      Decimal128("-0.0"));
   Add(comparison_groups, 0.1);
   // Doubles, longs, and Int32 Compare() the same.
-  Add(comparison_groups, 1.0, 1L, Int32(1));
-  Add(comparison_groups, Int32(2));
+  Add(comparison_groups, 1.0, 1L, Int32(1), Decimal128("1.0e0"));
+  Add(comparison_groups, Int32(2), Decimal128("0.2e1"));
   Add(comparison_groups, Int32(2147483647));
   Add(comparison_groups, std::numeric_limits<int64_t>::max());
-  Add(comparison_groups, 1e20);
+  Add(comparison_groups, 1e20, Decimal128("1e20"));
 
   // dates
   Add(comparison_groups, DeepClone(MinTimestamp()));
@@ -500,18 +540,20 @@ TEST_F(ValueUtilTest, RelaxedOrdering) {
   // numbers
   Add(comparison_groups, DeepClone(MinNumber()));
   Add(comparison_groups, DeepClone(MinNumber()));
-  Add(comparison_groups, -1e20);
+  Add(comparison_groups, -1e20, Decimal128("-1e20"));
   Add(comparison_groups, std::numeric_limits<int64_t>::min());
   Add(comparison_groups, -0.1);
   // Zeros all compare the same.
-  Add(comparison_groups, -0.0, 0.0, 0L, Int32(0));
+  Add(comparison_groups, -0.0, 0.0, 0L, Int32(0), Decimal128("-0.0"),
+      Decimal128("0.0"));
   Add(comparison_groups, 0.1);
   // Doubles and longs Compare() the same.
-  Add(comparison_groups, 1.0, 1L, Int32(1));
-  Add(comparison_groups, Int32(2));
+  Add(comparison_groups, 1.0, 1L, Int32(1), Decimal128("100.0e-2"),
+      Decimal128("1"));
+  Add(comparison_groups, Int32(2), Decimal128("2"));
   Add(comparison_groups, Int32(2147483647));
   Add(comparison_groups, std::numeric_limits<int64_t>::max());
-  Add(comparison_groups, 1e20);
+  Add(comparison_groups, 1e20, Decimal128("1e20"));
   Add(comparison_groups, DeepClone(MinTimestamp()));
   Add(comparison_groups, DeepClone(MinTimestamp()));
 
@@ -657,7 +699,11 @@ TEST_F(ValueUtilTest, ComputesLowerBound) {
 
   // Numbers
   Add(groups, GetLowerBoundMessage(Value(0.0)), GetLowerBoundMessage(Value(0L)),
-      GetLowerBoundMessage(Int32(0)), std::nan(""), DeepClone(MinNumber()));
+      GetLowerBoundMessage(Decimal128("0.0")),
+      GetLowerBoundMessage(Decimal128("-Infinity")),
+      GetLowerBoundMessage(Decimal128("Infinity")),
+      GetLowerBoundMessage(Decimal128("NaN")), GetLowerBoundMessage(Int32(0)),
+      std::nan(""), DeepClone(MinNumber()));
   Add(groups, INT_MIN);
 
   // Timestamps
@@ -753,6 +799,11 @@ TEST_F(ValueUtilTest, ComputesUpperBound) {
   Add(groups, INT_MAX);
   Add(groups, GetUpperBoundMessage(Value(INT_MAX)),
       GetUpperBoundMessage(Value(0L)), GetUpperBoundMessage(Int32(0)),
+      GetUpperBoundMessage(Decimal128("0")),
+      GetUpperBoundMessage(Decimal128("NaN")),
+      GetUpperBoundMessage(Decimal128("Infinity")),
+      GetUpperBoundMessage(Decimal128("-Infinity")),
+      GetUpperBoundMessage(Decimal128("2.0e5")),
       GetUpperBoundMessage(Value(std::nan(""))));
 
   // Timestamps
@@ -839,6 +890,7 @@ TEST_F(ValueUtilTest, CanonicalId) {
   VerifyCanonicalId(MaxKey(), "{__max__:null}");
   VerifyCanonicalId(Regex("^foo", "x"), "{__regex__:{pattern:^foo,options:x}}");
   VerifyCanonicalId(Int32(123), "{__int__:123}");
+  VerifyCanonicalId(Decimal128("1.2e3"), "{__decimal128__:1.2e3}");
   VerifyCanonicalId(BsonObjectId("foo"), "{__oid__:foo}");
   VerifyCanonicalId(BsonTimestamp(1, 2),
                     "{__request_timestamp__:{seconds:1,increment:2}}");

+ 12 - 0
Firestore/core/test/unit/remote/serializer_test.cc

@@ -115,6 +115,7 @@ using testutil::BsonBinaryData;
 using testutil::BsonObjectId;
 using testutil::BsonTimestamp;
 using testutil::Bytes;
+using testutil::Decimal128;
 using testutil::DeletedDoc;
 using testutil::Doc;
 using testutil::Filter;
@@ -878,6 +879,17 @@ TEST_F(SerializerTest, EncodesInt32Value) {
   ExpectRoundTrip(model, proto, TypeOrder::kNumber);
 }
 
+TEST_F(SerializerTest, EncodesDecimal128Value) {
+  Message<google_firestore_v1_Value> model = Decimal128("1.2e3");
+
+  v1::Value proto;
+  google::protobuf::Map<std::string, v1::Value>* fields =
+      proto.mutable_map_value()->mutable_fields();
+  (*fields)["__decimal128__"] = ValueProto("1.2e3");
+
+  ExpectRoundTrip(model, proto, TypeOrder::kNumber);
+}
+
 TEST_F(SerializerTest, EncodesBsonObjectId) {
   Message<google_firestore_v1_Value> model = BsonObjectId("foo");
 

+ 4 - 0
Firestore/core/test/unit/testutil/testutil.cc

@@ -209,6 +209,10 @@ nanopb::Message<google_firestore_v1_Value> Int32(int32_t value) {
   return Map("__int__", Value(value));
 }
 
+nanopb::Message<google_firestore_v1_Value> Decimal128(std::string value) {
+  return Map("__decimal128__", Value(value));
+}
+
 nanopb::Message<google_firestore_v1_Value> BsonObjectId(std::string oid) {
   return Map("__oid__", Value(oid));
 }

+ 1 - 0
Firestore/core/test/unit/testutil/testutil.h

@@ -299,6 +299,7 @@ nanopb::Message<google_firestore_v1_Value> MaxKey();
 nanopb::Message<google_firestore_v1_Value> Regex(std::string pattern,
                                                  std::string options);
 nanopb::Message<google_firestore_v1_Value> Int32(int32_t value);
+nanopb::Message<google_firestore_v1_Value> Decimal128(std::string value);
 nanopb::Message<google_firestore_v1_Value> BsonObjectId(std::string oid);
 nanopb::Message<google_firestore_v1_Value> BsonTimestamp(uint32_t seconds,
                                                          uint32_t increment);