Skip to content

Commit 061380e

Browse files
committed
Add quadruple and quadruple_builder.
1 parent 7da95f8 commit 061380e

File tree

4 files changed

+988
-0
lines changed

4 files changed

+988
-0
lines changed

Firestore/core/src/util/quadruple.cc

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "quadruple.h"
16+
#include "quadruple_builder.h"
17+
#include <ctype.h>
18+
#include <stdint.h>
19+
#include <cmath>
20+
#include <limits>
21+
22+
namespace firebase {
23+
namespace firestore {
24+
namespace util {
25+
26+
namespace {
27+
constexpr int64_t kHashCodeOfNan = 7652541255;
28+
}
29+
30+
Quadruple::Quadruple(double x) {
31+
negative_ = signbit(x);
32+
switch (fpclassify(x)) {
33+
case FP_NAN:
34+
negative_ = false;
35+
exponent_ = kInfiniteExponent;
36+
mantissa_hi_ = 1ULL << 63;
37+
mantissa_lo_ = 0;
38+
break;
39+
case FP_INFINITE:
40+
exponent_ = kInfiniteExponent;
41+
mantissa_hi_ = 0;
42+
mantissa_lo_ = 0;
43+
break;
44+
case FP_ZERO:
45+
exponent_ = 0;
46+
mantissa_hi_ = 0;
47+
mantissa_lo_ = 0;
48+
break;
49+
case FP_SUBNORMAL:
50+
case FP_NORMAL:
51+
negative_ = x < 0;
52+
int x_exponent;
53+
double small = frexp(std::abs(x), &x_exponent);
54+
exponent_ = static_cast<uint32_t>(x_exponent - 1) + kExponentBias;
55+
// Scale 'small' to its 53-bit mantissa value as a long, then left-justify
56+
// it with the leading 1 bit dropped in mantissa_hi (65-53=12).
57+
mantissa_hi_ = static_cast<uint64_t>(ldexp(small, 53)) << 12;
58+
mantissa_lo_ = 0;
59+
break;
60+
}
61+
}
62+
Quadruple::Quadruple(int64_t x) {
63+
if (x == 0) {
64+
negative_ = false;
65+
exponent_ = 0;
66+
mantissa_hi_ = 0;
67+
mantissa_lo_ = 0;
68+
} else if (x == std::numeric_limits<int64_t>::min()) {
69+
// -2^63 cannot be negated, so special-case it.
70+
negative_ = true;
71+
exponent_ = 63 + kExponentBias;
72+
mantissa_hi_ = 0;
73+
mantissa_lo_ = 0;
74+
} else {
75+
negative_ = x < 0;
76+
if (negative_) {
77+
x = -x;
78+
}
79+
if (x == 1) {
80+
// The shift below wraps around for x=1, so special-case it.
81+
exponent_ = kExponentBias;
82+
mantissa_hi_ = 0;
83+
mantissa_lo_ = 0;
84+
} else {
85+
uint64_t ux = static_cast<uint64_t>(x);
86+
int leading_zeros = __builtin_clzll(ux);
87+
// Left-justify with the leading 1 dropped.
88+
mantissa_hi_ = ux << (leading_zeros + 1);
89+
mantissa_lo_ = 0;
90+
exponent_ = static_cast<uint32_t>(63 - leading_zeros) + kExponentBias;
91+
}
92+
}
93+
}
94+
bool Quadruple::Parse(std::string s) {
95+
if (s == "NaN") {
96+
negative_ = false;
97+
exponent_ = kInfiniteExponent;
98+
mantissa_hi_ = 1LL << 63;
99+
mantissa_lo_ = 0;
100+
return true;
101+
}
102+
if (s == "-Infinity") {
103+
negative_ = true;
104+
exponent_ = kInfiniteExponent;
105+
mantissa_hi_ = 0;
106+
mantissa_lo_ = 0;
107+
return true;
108+
}
109+
if (s == "Infinity" || s == "+Infinity") {
110+
negative_ = false;
111+
exponent_ = kInfiniteExponent;
112+
mantissa_hi_ = 0;
113+
mantissa_lo_ = 0;
114+
return true;
115+
}
116+
bool negative = false;
117+
int len = s.size();
118+
uint8_t* digits = new uint8_t[len];
119+
int i = 0;
120+
int j = 0;
121+
int64_t exponent = 0;
122+
if (i < len) {
123+
if (s[i] == '-') {
124+
negative = true;
125+
i++;
126+
} else if (s[i] == '+') {
127+
i++;
128+
}
129+
}
130+
//int firstDigit = i;
131+
while (i < len && isdigit(s[i])) {
132+
digits[j++] = static_cast<uint8_t>(s[i++] - '0');
133+
}
134+
if (i < len && s[i] == '.') {
135+
int decimal = ++i;
136+
while (i < len && isdigit(s[i])) {
137+
digits[j++] = static_cast<uint8_t>(s[i++] - '0');
138+
}
139+
exponent = decimal - i;
140+
}
141+
if (i < len && (s[i] == 'e' || s[i] == 'E')) {
142+
int64_t exponentValue = 0;
143+
i++;
144+
int exponentSign = 1;
145+
if (i < len) {
146+
if (s[i] == '-') {
147+
exponentSign = -1;
148+
i++;
149+
} else if (s[i] == '+') {
150+
i++;
151+
}
152+
}
153+
int firstExponent = i;
154+
while (i < len && isdigit(s[i])) {
155+
exponentValue = exponentValue * 10 + s[i++] - '0';
156+
if (i - firstExponent > 9) {
157+
return false;
158+
}
159+
}
160+
if (i == firstExponent) {
161+
return false;
162+
}
163+
exponent += exponentValue * exponentSign;
164+
}
165+
if (j == 0 || i != len) {
166+
return false;
167+
}
168+
std::vector<uint8_t> digits_copy(j);
169+
for (int k = 0; k < j; k++) {
170+
digits_copy[k] = digits[k];
171+
}
172+
QuadrupleBuilder parsed;
173+
parsed.parseDecimal(digits_copy, exponent);
174+
negative_ = negative;
175+
exponent_ = parsed.exponent;
176+
mantissa_hi_ = parsed.mantHi;
177+
mantissa_lo_ = parsed.mantLo;
178+
return true;
179+
}
180+
// Compare two quadruples, with -0 < 0, and NaNs larger than all numbers.
181+
int Quadruple::Compare(const Quadruple& other) const {
182+
int lessThan;
183+
int greaterThan;
184+
if (negative_) {
185+
if (!other.negative_) {
186+
return -1;
187+
}
188+
lessThan = 1;
189+
greaterThan = -1;
190+
} else {
191+
if (other.negative_) {
192+
return 1;
193+
}
194+
lessThan = -1;
195+
greaterThan = 1;
196+
}
197+
if (exponent_ < other.exponent_) {
198+
return lessThan;
199+
} else if (exponent_ > other.exponent_) {
200+
return greaterThan;
201+
} else if (mantissa_hi_ < other.mantissa_hi_) {
202+
return lessThan;
203+
} else if (mantissa_hi_ > other.mantissa_hi_) {
204+
return greaterThan;
205+
} else if (mantissa_lo_ < other.mantissa_lo_) {
206+
return lessThan;
207+
} else if (mantissa_lo_ > other.mantissa_lo_) {
208+
return greaterThan;
209+
} else {
210+
return 0;
211+
}
212+
}
213+
Quadruple::operator double() const {
214+
switch (exponent_) {
215+
case 0:
216+
// zero or Quadruple subnormal
217+
return negative_ ? -0.0 : 0.0;
218+
case kInfiniteExponent: {
219+
if (is_nan()) {
220+
return NAN;
221+
}
222+
return negative_ ? -INFINITY : INFINITY;
223+
}
224+
default:
225+
int32_t unbiased_exp = static_cast<int32_t>(exponent_ - kExponentBias);
226+
return scalb((1LL << 52) | (mantissa_hi_ >> 12), -52 + unbiased_exp) *
227+
(negative_ ? -1 : 1);
228+
}
229+
}
230+
int64_t Quadruple::HashValue() const {
231+
if (is_nan()) {
232+
return kHashCodeOfNan;
233+
}
234+
const int64_t prime = 31;
235+
int64_t result = 1;
236+
result = prime * result + static_cast<uint64_t>(exponent_);
237+
result = prime * result + static_cast<uint64_t>(mantissa_hi_);
238+
result = prime * result + static_cast<uint64_t>(mantissa_lo_);
239+
result = prime * result + (negative_ ? 1231 : 1237);
240+
return result;
241+
}
242+
243+
} // namespace util
244+
} // namespace firestore
245+
} // namespace firebase

Firestore/core/src/util/quadruple.h

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <stdint.h>
16+
#include <string>
17+
#include <iomanip>
18+
#include <ios>
19+
#include <sstream>
20+
21+
#ifndef FIRESTORE_CORE_UTIL_QUADRUPLE_H_
22+
#define FIRESTORE_CORE_UTIL_QUADRUPLE_H_
23+
24+
namespace firebase {
25+
namespace firestore {
26+
namespace util {
27+
28+
// A minimal C++ implementation of a 128-bit mantissa / 32-bit exponent binary
29+
// floating point number equivalent to https://github.com/m-vokhm/Quadruple
30+
//
31+
// Supports:
32+
// - creation from string
33+
// - creation from serialised format (3 longs), long and double
34+
// - comparisons
35+
class Quadruple {
36+
public:
37+
// Initialises a Quadruple to +0.0
38+
Quadruple() : Quadruple(0, 0, 0) {}
39+
40+
Quadruple(uint64_t exponent_and_sign, uint64_t mantissa_hi,
41+
uint64_t mantissa_lo)
42+
: negative_(exponent_and_sign >> 63),
43+
exponent_(static_cast<uint32_t>(exponent_and_sign)),
44+
mantissa_hi_(mantissa_hi),
45+
mantissa_lo_(mantissa_lo) {}
46+
explicit Quadruple(double x);
47+
explicit Quadruple(int64_t x);
48+
// Updates this Quadruple with the decimal number specified in s.
49+
// Returns true for valid numbers, false for invalid numbers.
50+
// The Quadruple is unchanged if the result is false.
51+
//
52+
// The supported format (no whitespace allowed) is:
53+
// - NaN, Infinity, +Infinity, -Infinity for the corresponding constants
54+
// - a string matching [+-]?[0-9]*(.[0-9]*)?([eE][+-]?[0-9]+)?
55+
// with the exponent at most 9 characters, and the whole string not empty
56+
bool Parse(std::string s);
57+
// Rounds out-of-range numbers to +/- 0/HUGE_VAL. Rounds towards 0.
58+
explicit operator double() const;
59+
// Compare two quadruples, with -0 < 0, and NaNs larger than all numbers.
60+
int Compare(const Quadruple& other) const;
61+
bool operator==(const Quadruple& other) const { return Compare(other) == 0; }
62+
bool is_nan() const {
63+
return (exponent_ == kInfiniteExponent) &&
64+
!(mantissa_hi_ == 0 && mantissa_lo_ == 0);
65+
}
66+
// The actual exponent is exponent_-kExponentBias.
67+
static const uint32_t kExponentBias = 0x7FFFFFFF;
68+
int64_t HashValue() const;
69+
std::string DebugString() {
70+
std::stringstream out;
71+
if (negative_) {
72+
out << "-";
73+
}
74+
out << "1x" << std::hex << std::setfill('0');
75+
out << std::setw(16) << mantissa_hi_;
76+
out << std::setw(16) << mantissa_lo_;
77+
out << "*2^" << std::dec << exponent_ - static_cast<int64_t>(kExponentBias);
78+
out << " =~ " << static_cast<double>(*this);
79+
80+
return out.str();
81+
}
82+
private:
83+
static const uint32_t kInfiniteExponent = 0xFFFFFFFF; // including its bias
84+
bool negative_;
85+
uint32_t exponent_;
86+
uint64_t mantissa_hi_;
87+
uint64_t mantissa_lo_;
88+
};
89+
90+
} // namespace util
91+
} // namespace firestore
92+
} // namespace firebase
93+
94+
#endif // FIRESTORE_CORE_UTIL_QUADRUPLE_H_

0 commit comments

Comments
 (0)