-
-
Notifications
You must be signed in to change notification settings - Fork 342
Expand file tree
/
Copy pathFloatX.cpp
More file actions
516 lines (423 loc) · 17.4 KB
/
Copy pathFloatX.cpp
File metadata and controls
516 lines (423 loc) · 17.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
/*
* Copyright (C) 2017 - 2025 Evan Teran <evan.teran@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "FloatX.h"
#include "util/Float.h"
#include <array>
#include <iomanip>
#include <sstream>
#if defined(HAVE_DOUBLE_CONVERSION)
#include <double-conversion/double-conversion.h>
#endif
#if defined(HAVE_GDTOA)
#if __has_include(<gdtoa-functions-renamed.h>)
#include <gdtoa-functions-renamed.h>
#elif __has_include(<gdtoa-desktop.h>)
#include <gdtoa-desktop.h>
#else
#error "gdtoa-desktop not found! Please make sure your submodules are up to date by running: git submodule update --init --recursive"
// undef this macro to suppress an further related errors. This initial error message should be what the user focuses on
#undef HAVE_GDTOA
#endif
#endif
#ifdef _MSC_VER
extern "C" EDB_EXPORT void __fastcall float64_to_float80(const void *src, void *dest);
// NOTE(eteran): this thin wrapper function make look pointless... and it REALLY does.
// However, I could not get plugins to be able to see the functions defined in .asm files
// unless I wrapped them in a concrete function like this. It's dumb, but it works
void convert_real64_to_real80(const void *src, void *dst) {
float64_to_float80(src, dst);
}
#endif
namespace {
template <class T>
struct SpecialValues;
template <>
struct SpecialValues<double> {
static constexpr std::array<std::uint8_t, 8> positiveInf{{0, 0, 0, 0, 0, 0, 0xf0, 0x7f}};
static constexpr std::array<std::uint8_t, 8> negativeInf{{0, 0, 0, 0, 0, 0, 0xf0, 0xff}};
static constexpr std::array<std::uint8_t, 8> positiveSNaN{{0, 0, 0, 0, 0, 0, 0xfc, 0x7f}};
static constexpr std::array<std::uint8_t, 8> negativeSNaN{{0, 0, 0, 0, 0, 0, 0xfc, 0xff}};
static constexpr std::array<std::uint8_t, 8> positiveQNaN{{0, 0, 0, 0, 0, 0, 0xf8, 0x7f}};
static constexpr std::array<std::uint8_t, 8> negativeQNaN{{0, 0, 0, 0, 0, 0, 0xf8, 0xff}};
};
template <>
struct SpecialValues<float> {
static constexpr std::array<std::uint8_t, 4> positiveInf{{0, 0, 0x80, 0x7f}};
static constexpr std::array<std::uint8_t, 4> negativeInf{{0, 0, 0x80, 0xff}};
static constexpr std::array<std::uint8_t, 4> positiveSNaN{{0, 0, 0xe0, 0x7f}};
static constexpr std::array<std::uint8_t, 4> negativeSNaN{{0, 0, 0xe0, 0xff}};
static constexpr std::array<std::uint8_t, 4> positiveQNaN{{0, 0, 0xc0, 0x7f}};
static constexpr std::array<std::uint8_t, 4> negativeQNaN{{0, 0, 0xc0, 0xff}};
};
#ifndef _MSC_VER
#if defined(EDB_X86) || defined(EDB_X86_64)
template <>
struct SpecialValues<long double> {
static_assert(std::numeric_limits<long double>::digits == 64 && std::numeric_limits<long double>::max_exponent == 16384,
"Expected to have x87 80-bit long double");
static constexpr std::array<std::uint8_t, 16> positiveInf{{0, 0, 0, 0, 0, 0, 0, 0x80, 0xff, 0x7f, 0, 0, 0, 0, 0, 0}};
static constexpr std::array<std::uint8_t, 16> negativeInf{{0, 0, 0, 0, 0, 0, 0, 0x80, 0xff, 0xff, 0, 0, 0, 0, 0, 0}};
static constexpr std::array<std::uint8_t, 16> positiveSNaN{{0, 0, 0, 0, 0, 0, 0, 0x90, 0xff, 0x7f, 0, 0, 0, 0, 0, 0}};
static constexpr std::array<std::uint8_t, 16> negativeSNaN{{0, 0, 0, 0, 0, 0, 0, 0x90, 0xff, 0xff, 0, 0, 0, 0, 0, 0}};
static constexpr std::array<std::uint8_t, 16> positiveQNaN{{0, 0, 0, 0, 0, 0, 0, 0xc0, 0xff, 0x7f, 0, 0, 0, 0, 0, 0}};
static constexpr std::array<std::uint8_t, 16> negativeQNaN{{0, 0, 0, 0, 0, 0, 0, 0xc0, 0xff, 0xff, 0, 0, 0, 0, 0, 0}};
};
#endif
#endif
float to_real(edb::value32 value) {
float result;
std::memcpy(&result, &value, sizeof(result));
return result;
}
double to_real(edb::value64 value) {
double result;
std::memcpy(&result, &value, sizeof(result));
return result;
}
long double to_real(edb::value80 value) {
return value.toFloatValue();
}
template <unsigned MantissaLength, class FloatHolder>
FloatValueClass ieee_classify(FloatHolder value) {
constexpr auto ExpLength = 8 * sizeof(value) - MantissaLength - 1;
constexpr auto ExpMax = (1u << ExpLength) - 1;
constexpr auto QNaN_mask = 1ull << (MantissaLength - 1);
const auto mantissa = value & ((1ull << MantissaLength) - 1);
const auto exponent = (value >> MantissaLength) & ExpMax;
if (exponent == ExpMax) {
if (mantissa == 0u) {
return FloatValueClass::Infinity; // |S|11..11|00..00|
}
if (mantissa & QNaN_mask) {
return FloatValueClass::QNaN; // |S|11..11|1XX..XX|
}
return FloatValueClass::SNaN; // |S|11..11|0XX..XX|
}
if (exponent == 0u) {
if (mantissa == 0u) {
return FloatValueClass::Zero; // |S|00..00|00..00|
}
return FloatValueClass::Denormal; // |S|00..00|XX..XX|
}
return FloatValueClass::Normal;
}
#if defined(HAVE_GDTOA)
/*
* gdtoa_g_?fmt functions do generally a good job at formatting the numbers in
* a form close to that specified in ECMAScript specification (actually the
* spec even references this implementation in 7.1.12.1/note3). There are two
* issues though with the function, one small and one bigger.
*
* The small issue is that this function prints numbers x such that 1e-5<|x|<1
* without leading zeros (contrary to the spec). It's easy to fix up, and it's
* the first thing the following function does.
*
* The bigger issue is that the specification prescribes to print large numbers
* smaller than 1e21 in fixed-point format, and append zeros(!) instead of
* actual digits present in the closest representable integer to the base part.
*
* This can be quite a problem for our users, because it can give a false sense
* of precision. E.g., consider the number 1.2345678912345678e20. Closest
* representable IEEE 754 binary64 number is 123456789123456778240. Next
* representable is 123456789123456794624. Yet gdtoa_g_dfmt (following
* ECMAScript) formats it as 123456789123456780000, which, having so many
* trailing zeros, misleads the user into thinking that all the digits are
* significant (the user may think that e.g. 123456789123456781000 or even
* 123456789123456780001 are representable too).
*
* The following function tries to ensure that such situations never occur: it
* takes numeric_limits::digits10 digits as the maximum length of integers (or
* maximum value of exponent+1 for fractions) for fixed-point format, and if the
* number formatted into 'buffer' is larger than that, it converts the result
* into exponential format, removing any trailing zeros.
*
* Note that in principle, we could use gdtoa_gdtoa directly and format the
* number ourselves. But this would result in even more logic to 1) prepare
* arguments, 2) actually do the formatting; total of both might be just as
* convoluted as the current post-processing logic.
*/
const char *fixup_g_Yfmt(char *buffer, int digits10) {
const size_t len = std::strlen(buffer);
const char x0 = buffer[0];
const char x1 = buffer[1];
if (x0 == '.' || (x0 == '-' && x1 == '.')) {
// ".235" or "-.235" forms are unreadable, so insert leading zero
const size_t posToInsert = x0 == '.' ? 0 : 1;
// Give space for the zero: move the remaining line with terminating zero to the right
std::memmove(buffer + posToInsert + 1, buffer + posToInsert, len + 1 - posToInsert);
buffer[posToInsert] = '0';
return buffer;
}
// We want exponential format for numbers which are too imprecise for fixed-point format.
// If we find a number with more than digits10 digits before point, we must fix it.
int digitCount = 0;
int pointPos = -1;
for (int i = 0; i < static_cast<int>(len); ++i) {
const char c = buffer[i];
// If it's already in exponential format, it's fine, just return it
if (c == 'e') {
return buffer;
}
if (c == '.') {
pointPos = i;
continue;
}
if ('0' <= c && c <= '9') {
++digitCount;
}
}
// If point wasn't found, assume it's at the end of the number
if (pointPos < 0) {
pointPos = static_cast<int>(len);
}
const int signChars = buffer[0] == '-';
const int exp = pointPos - signChars - 1;
if (exp + 1 > digits10) {
// Yes, the format is too precise for actual value, need to shift the
// point to the second position and append e+XX to the resulting string.
char *const buf = buffer + signChars;
// NOTE: don't attempt to replace this loop with memmove: you'll get
// confused trying to work out size of data to move.
// The original string may contain a point, may not contain any. In the
// former case we must move everything including the null terminator. In
// the latter case only the chunk up to the original point needs moving.
const auto lenWithNull = static_cast<int>(len + 1);
char next = buf[1];
for (int i = 0; i < lenWithNull - signChars; ++i) {
// Avoid writing the point; after this, there's no more need to shift
if (next == '.') {
break;
}
std::swap(next, buf[i + 1]);
}
buf[1] = '.';
// Now after all the mess with present/absent point, it's better to
// re-calculate length of the current buffer content
auto len = std::strlen(buf);
// Remove trailing zeros
while (buf[len - 1] == '0') {
--len;
}
// Append the exponent
buf[len] = 'e';
buf[len + 1] = '+';
buf[len + 2] = static_cast<char>(exp / 10 + '0');
buf[len + 3] = static_cast<char>(exp % 10 + '0');
buf[len + 4] = 0;
}
return buffer;
}
#endif
}
template <class Float>
Float read_float(const QString &input, bool &ok) {
ok = false;
const QString str(input.toLower().trimmed());
if (const auto value = util::full_string_to_float<Float>(str.toStdString())) {
ok = true;
return *value;
}
// OK, so either it is invalid/unfinished, or it's some special value
// We still do want the user to be able to enter common special values
Float value;
if (str == "+snan" || str == "snan") {
std::memcpy(&value, &SpecialValues<Float>::positiveSNaN, sizeof(value));
} else if (str == "-snan") {
std::memcpy(&value, &SpecialValues<Float>::negativeSNaN, sizeof(value));
} else if (str == "+qnan" || str == "qnan" || str == "nan") {
std::memcpy(&value, &SpecialValues<Float>::positiveQNaN, sizeof(value));
} else if (str == "-qnan") {
std::memcpy(&value, &SpecialValues<Float>::negativeQNaN, sizeof(value));
} else if (str == "+inf" || str == "inf") {
std::memcpy(&value, &SpecialValues<Float>::positiveInf, sizeof(value));
} else if (str == "-inf") {
std::memcpy(&value, &SpecialValues<Float>::negativeInf, sizeof(value));
} else {
return 0;
}
ok = true;
return value;
}
template EDB_EXPORT float read_float<float>(const QString &input, bool &ok);
template EDB_EXPORT double read_float<double>(const QString &input, bool &ok);
#ifndef _MSC_VER
#if defined(EDB_X86) || defined(EDB_X86_64)
template long double read_float<long double>(const QString &input, bool &ok);
#endif
#endif
FloatValueClass float_type(edb::value32 value) {
return ieee_classify<23>(value);
}
FloatValueClass float_type(edb::value64 value) {
return ieee_classify<52>(value);
}
FloatValueClass float_type(edb::value80 value) {
constexpr auto MantissaLength = 64;
constexpr auto ExpLength = 8 * sizeof(value) - MantissaLength - 1;
constexpr auto IntegerBitOnly = 1ull << (MantissaLength - 1);
constexpr auto QNaN_mask = 3ull << (MantissaLength - 2);
constexpr auto ExpMax = (1u << ExpLength) - 1;
const auto exponent = value.exponent();
const auto mantissa = value.mantissa();
const bool integerBitSet = mantissa & IntegerBitOnly;
// This is almost as ieee_classify, but also takes integer bit (not present in
// IEEE754 format) into account to detect unsupported values
if (exponent == ExpMax) {
if (mantissa == IntegerBitOnly) {
return FloatValueClass::Infinity; // |S|11..11|1.000..0|
}
if ((mantissa & QNaN_mask) == QNaN_mask) {
return FloatValueClass::QNaN; // |S|11..11|1.1XX..X|
}
if ((mantissa & QNaN_mask) == IntegerBitOnly) {
return FloatValueClass::SNaN; // |S|11..11|1.0XX..X|
}
return FloatValueClass::Unsupported; // all exp bits set, but integer bit reset - pseudo-NaN/Inf
}
if (exponent == 0u) {
if (mantissa == 0u) {
return FloatValueClass::Zero; // |S|00..00|00..00|
}
if (!integerBitSet) {
return FloatValueClass::Denormal; // |S|00..00|0.XXXX..X|
}
return FloatValueClass::PseudoDenormal; // |S|00..00|1.XXXX..X|
}
if (integerBitSet) {
return FloatValueClass::Normal;
}
return FloatValueClass::Unsupported; // integer bit reset but exp is as if normal - unnormal
}
template <class Float>
QValidator::State FloatXValidator<Float>::validate(QString &input, int &) const {
if (input.isEmpty()) {
return QValidator::Intermediate;
}
// The input may be in hex format. std::istream doesn't support extraction
// of hexfloat, but std::strto[f,d,ld] do. (see wg21.link/lwg2381)
if (const auto v = util::full_string_to_float<Float>(input.toStdString())) {
return QValidator::Acceptable;
}
// OK, so we failed to read it or it is unfinished. Let's check whether it's intermediate or invalid.
static const QRegularExpression basicFormat("^[+-]?[0-9]*\\.?[0-9]*(e([+-]?[0-9]*)?)?$");
static const QRegularExpression specialFormat("^[+-]?[sq]?nan|[+-]?inf$", QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression hexfloatFormat("^[+-]?0x[0-9a-f]*\\.?[0-9a-f]*(p([+-]?[0-9]*)?)?$", QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression specialFormatUnfinished("^[+-]?[sq]?(n(an?)?)?|[+-]?(i(nf?)?)?$", QRegularExpression::CaseInsensitiveOption);
const QRegularExpressionMatch match = basicFormat.match(input);
const QRegularExpressionMatch matchSpecial = specialFormat.match(input);
const QRegularExpressionMatch matchHex = hexfloatFormat.match(input);
const QRegularExpressionMatch matchSpecialUnfinished = specialFormatUnfinished.match(input);
if (matchHex.hasMatch()) {
return QValidator::Intermediate;
}
if (match.hasMatch()) {
return QValidator::Intermediate;
}
if (matchSpecial.hasMatch()) {
return QValidator::Acceptable;
}
if (matchSpecialUnfinished.hasMatch()) {
return QValidator::Intermediate;
}
// All possible options are exhausted, so consider the input invalid
return QValidator::Invalid;
}
template QValidator::State FloatXValidator<float>::validate(QString &input, int &) const;
template QValidator::State FloatXValidator<double>::validate(QString &input, int &) const;
template QValidator::State FloatXValidator<long double>::validate(QString &input, int &) const;
template <class Float>
EDB_EXPORT QString format_float(Float value) {
const auto type = float_type(value);
QString specialStr = "???? ";
switch (type) {
case FloatValueClass::Zero:
return value.negative() ? "-0.0" : "0.0";
case FloatValueClass::PseudoDenormal:
if constexpr (sizeof(value) >= 10) {
Q_ASSERT(sizeof(value) == 10);
// Convert to supported value as the CPU would. Otherwise glibc takes it wrong.
const uint16_t exponent = value.negative() ? 0x8001 : 0x0001;
// NOTE(eteran): hushes a warning from GCC >= 8.2
auto ptr = reinterpret_cast<char *>(&value) + 8;
std::memcpy(ptr, &exponent, sizeof(exponent));
}
[[fallthrough]];
case FloatValueClass::Normal:
case FloatValueClass::Denormal: {
#ifdef HAVE_DOUBLE_CONVERSION
constexpr bool isDouble = std::is_same_v<Float, edb::value64>;
constexpr bool isFloat = std::is_same_v<Float, edb::value32>;
if constexpr (isDouble || isFloat) {
using namespace double_conversion;
char buffer[64];
DoubleToStringConverter conv(DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN, "inf", "nan", 'e', -4, isDouble ? 15 : 6, 0, 0);
StringBuilder builder(buffer, sizeof(buffer));
bool ret = false;
if constexpr (isDouble) {
double d;
std::memcpy(&d, &value, sizeof(d));
ret = conv.ToShortest(d, &builder);
} else {
// isFloat
float f;
std::memcpy(&f, &value, sizeof(f));
ret = conv.ToShortestSingle(f, &builder);
}
if (ret && builder.Finalize()) {
const QString result = buffer;
if (result.size() == 1 && result[0].isDigit()) {
return result + ".0"; // avoid printing small whole numbers as integers
}
return result;
}
}
#endif
#if defined(HAVE_GDTOA)
if constexpr (std::is_same_v<Float, edb::value80>) {
char buffer[64] = {};
gdtoa_g_xfmt(buffer, &value, -1, sizeof buffer);
fixup_g_Yfmt(buffer, std::numeric_limits<long double>::digits10);
const QString result = buffer;
if (result.size() == 1 && result[0].isDigit()) {
return result + ".0"; // avoid printing small whole numbers as integers
}
return result;
}
#endif
std::ostringstream ss;
ss << std::setprecision(std::numeric_limits<decltype(to_real(value))>::max_digits10) << to_real(value);
const auto result = QString::fromStdString(ss.str());
if (result.size() == 1 && result[0].isDigit()) {
return result + ".0"; // avoid printing small whole numbers as integers
}
return result;
}
case FloatValueClass::Infinity:
return QString(value.negative() ? "-" : "+") + "INF";
case FloatValueClass::QNaN:
specialStr = QString(value.negative() ? "-" : "+") + "QNAN ";
break;
case FloatValueClass::SNaN:
specialStr = QString(value.negative() ? "-" : "+") + "SNAN ";
break;
case FloatValueClass::Unsupported:
specialStr = QString(value.negative() ? "-" : "+") + "BAD ";
break;
}
// If we are here, then the value is special
auto hexStr = value.toHexString();
const QChar groupSeparator(' ');
if (hexStr.size() > 8) {
hexStr.insert(hexStr.size() - 8, groupSeparator);
}
if (hexStr.size() > 16 + 1) { // +1 to take into account already inserted first separator
hexStr.insert(hexStr.size() - 16 - 1, groupSeparator);
}
return specialStr + hexStr;
}
template EDB_EXPORT QString format_float<edb::value32>(edb::value32);
template EDB_EXPORT QString format_float<edb::value64>(edb::value64);
template EDB_EXPORT QString format_float<edb::value80>(edb::value80);