fb-cpp 0.0.2
A modern C++ wrapper for the Firebird database API
Loading...
Searching...
No Matches
NumericConverter.h
1/*
2 * MIT License
3 *
4 * Copyright (c) 2025 Adriano dos Santos Fernandes
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 * SOFTWARE.
20 */
21
22#ifndef FBCPP_NUMERIC_CONVERTER_H
23#define FBCPP_NUMERIC_CONVERTER_H
24
25#include "config.h"
26#include "fb-api.h"
27#include "Client.h"
28#include "Exception.h"
29#include "types.h"
30#include <algorithm>
31#include <cassert>
32#include <cctype>
33#include <charconv>
34#include <cmath>
35#include <concepts>
36#include <cstddef>
37#include <cstdlib>
38#include <format>
39#include <limits>
40#include <string>
41#include <string_view>
42#include <type_traits>
43#include <utility>
44
45
46namespace fbcpp::impl
47{
48 template <typename T>
49 struct NumberTypePriority
50 {
51 static constexpr int value = 0;
52 };
53
54#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
55 template <>
56 struct NumberTypePriority<BoostDecFloat34>
57 {
58 static constexpr int value = 8;
59 };
60
61 template <>
62 struct NumberTypePriority<BoostDecFloat16>
63 {
64 static constexpr int value = 7;
65 };
66#endif
67
68 template <>
69 struct NumberTypePriority<double>
70 {
71 static constexpr int value = 6;
72 };
73
74 template <>
75 struct NumberTypePriority<float>
76 {
77 static constexpr int value = 5;
78 };
79
80#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
81 template <>
82 struct NumberTypePriority<BoostInt128>
83 {
84 static constexpr int value = 4;
85 };
86#endif
87
88 template <>
89 struct NumberTypePriority<std::int64_t>
90 {
91 static constexpr int value = 3;
92 };
93
94 template <>
95 struct NumberTypePriority<std::int32_t>
96 {
97 static constexpr int value = 2;
98 };
99
100 template <>
101 struct NumberTypePriority<std::int16_t>
102 {
103 static constexpr int value = 1;
104 };
105
106 template <typename T>
107 inline constexpr int NumberTypePriorityValue =
108 NumberTypePriority<std::remove_cv_t<std::remove_reference_t<T>>>::value;
109
110 template <typename T1, typename T2>
111 using GreaterNumberType = std::conditional_t<(NumberTypePriorityValue<T1> >= NumberTypePriorityValue<T2>), T1, T2>;
112
113 template <typename T>
114 inline constexpr bool IsFloatingNumber = std::is_floating_point_v<T>
115#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
116 || std::same_as<T, BoostDecFloat16> || std::same_as<T, BoostDecFloat34>
117#endif
118 ;
119
120 template <typename T>
121 concept FloatingNumber = IsFloatingNumber<T>;
122
123 template <typename T>
124 concept IntegralNumber = std::is_integral_v<T>
125#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
126 || std::same_as<T, BoostInt128>
127#endif
128 ;
129
130 template <typename T>
131 struct MakeUnsigned
132 {
133 using type = std::make_unsigned_t<T>;
134 };
135
136#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
137 template <>
138 struct MakeUnsigned<BoostInt128>
139 {
140 using type = boost::multiprecision::uint128_t;
141 };
142#endif
143
144 template <typename T>
145 using MakeUnsignedType = typename MakeUnsigned<T>::type;
146
147 class NumericConverter final
148 {
149 public:
150 explicit NumericConverter(Client& client)
151 : client{&client}
152 {
153 }
154
155 [[noreturn]] void throwNumericOutOfRange()
156 {
157 static constexpr std::intptr_t STATUS_NUMERIC_OUT_OF_RANGE[] = {
158 isc_arith_except,
159 isc_numeric_out_of_range,
160 isc_arg_end,
161 };
162
163 throw DatabaseException(*client, STATUS_NUMERIC_OUT_OF_RANGE);
164 }
165
166 [[noreturn]] void throwConversionErrorFromString(const std::string& str)
167 {
168 const std::intptr_t STATUS_CONVERSION_ERROR_FROM_STRING[] = {
169 isc_convert_error,
170 reinterpret_cast<std::intptr_t>(str.c_str()),
171 isc_arg_end,
172 };
173
174 throw DatabaseException(*client, STATUS_CONVERSION_ERROR_FROM_STRING);
175 }
176
177 public:
178 template <IntegralNumber To, IntegralNumber From>
179 To numberToNumber(const ScaledNumber<From>& from, int toScale)
180 {
181 const int scaleDiff = toScale - from.scale;
182
183 using ComputeType = GreaterNumberType<decltype(from.value), To>;
184
185 ComputeType result = static_cast<ComputeType>(from.value);
186
187 if (scaleDiff != 0)
188 {
189 adjustScale(result, scaleDiff, static_cast<ComputeType>(std::numeric_limits<To>::min()),
190 static_cast<ComputeType>(std::numeric_limits<To>::max()));
191 }
192
193 if (result < static_cast<ComputeType>(std::numeric_limits<To>::min()) ||
194 result > static_cast<ComputeType>(std::numeric_limits<To>::max()))
195 {
196 throwNumericOutOfRange();
197 }
198
199 return static_cast<To>(result);
200 }
201
202 template <IntegralNumber To, FloatingNumber From>
203 To numberToNumber(const From& from, int toScale)
204 {
205 using ComputeType = GreaterNumberType<double, From>;
206
207 if constexpr (std::is_floating_point_v<From>)
208 {
209 if (std::isnan(from) || std::isinf(from))
210 throwNumericOutOfRange();
211 }
212
213 ComputeType value{from};
214 const ComputeType eps = conversionEpsilon<ComputeType>();
215
216 if (toScale > 0)
217 value /= powerOfTen(toScale);
218 else if (toScale < 0)
219 value *= powerOfTen(-toScale);
220
221 if (value > 0)
222 value += 0.5f + eps;
223 else
224 value -= 0.5f + eps;
225
226 static const auto minLimit = static_cast<ComputeType>(std::numeric_limits<To>::min());
227 static const auto maxLimit = static_cast<ComputeType>(std::numeric_limits<To>::max());
228
229 if (value < minLimit)
230 {
231 if (value > minLimit - 1.0f)
232 return std::numeric_limits<To>::min();
233 throwNumericOutOfRange();
234 }
235
236 if (value > maxLimit)
237 {
238 if (value < maxLimit + 1.0f)
239 return std::numeric_limits<To>::max();
240 throwNumericOutOfRange();
241 }
242
243 return static_cast<To>(value);
244 }
245
246 template <FloatingNumber To, typename From>
247 To numberToNumber(const ScaledNumber<From>& from, int toScale = 0)
248 {
249 assert(toScale == 0);
250
251 using ComputeType = GreaterNumberType<double, To>;
252
253 ComputeType value = static_cast<ComputeType>(from.value); // FIXME: decfloat
254
255 if (from.scale != 0)
256 {
257 if (std::abs(from.scale) > std::numeric_limits<To>::max_exponent10)
258 throwNumericOutOfRange();
259
260 if (from.scale > 0)
261 value *= powerOfTen(from.scale);
262 else if (from.scale < 0)
263 value /= powerOfTen(-from.scale);
264 }
265
266 return static_cast<To>(value);
267 }
268
269 template <FloatingNumber To, FloatingNumber From>
270 To numberToNumber(const From& from, int toScale = 0)
271 {
272 assert(toScale == 0);
273
274 if constexpr (std::is_floating_point_v<From> && !std::is_floating_point_v<To>)
275 return To{std::format("{:.16e}", from)};
276 else
277 return static_cast<To>(from);
278 }
279
280 template <IntegralNumber From>
281 std::string numberToString(const ScaledNumber<From>& from)
282 {
283 char buffer[64];
284
285 const bool isNegative = from.value < 0;
286 const bool isMinLimit = from.value == std::numeric_limits<decltype(from.value)>::min();
287
288 using UnsignedType = MakeUnsignedType<decltype(from.value)>;
289
290 auto unsignedValue = isMinLimit ? static_cast<UnsignedType>(-(from.value + 1)) + 1
291 : static_cast<UnsignedType>(isNegative ? -from.value : from.value);
292
293 int digitCount = 0;
294
295 do
296 {
297 buffer[digitCount++] = static_cast<char>((unsignedValue % 10) + '0');
298 unsignedValue /= 10;
299 } while (unsignedValue > 0);
300
301 std::string result;
302
303 if (isNegative)
304 result += '-';
305
306 if (from.scale >= 0)
307 {
308 for (int i = digitCount - 1; i >= 0; --i)
309 result += buffer[i];
310
311 result.append(static_cast<std::string::size_type>(from.scale), '0');
312 }
313 else
314 {
315 const int decimalPlaces = -from.scale;
316
317 if (decimalPlaces >= static_cast<int>(digitCount))
318 {
319 result += "0.";
320 const int leadingZeros = decimalPlaces - digitCount;
321 result.append(static_cast<std::string::size_type>(leadingZeros), '0');
322
323 for (int i = digitCount - 1; i >= 0; --i)
324 result += buffer[i];
325 }
326 else
327 {
328 for (int i = digitCount - 1; i >= decimalPlaces; --i)
329 result += buffer[i];
330
331 result += '.';
332
333 for (int i = decimalPlaces - 1; i >= 0; --i)
334 result += buffer[i];
335 }
336 }
337
338 return result;
339 }
340
341 template <FloatingNumber From>
342 std::string numberToString(const From& from)
343 {
344 if constexpr (std::is_floating_point_v<From>)
345 {
346 if (std::isnan(from))
347 return "NaN";
348 if (std::isinf(from))
349 return from > 0 ? "Infinity" : "-Infinity";
350 return std::to_string(from);
351 }
352 else
353 return from.str();
354 }
355
356 std::string opaqueInt128ToString(StatusWrapper* statusWrapper, const OpaqueInt128& opaqueInt128, int scale)
357 {
358 const auto int128Util = client->getInt128Util(statusWrapper);
359 char buffer[fb::IInt128::STRING_SIZE + 1];
360 int128Util->toString(statusWrapper, &opaqueInt128, scale, static_cast<unsigned>(sizeof(buffer)), buffer);
361 return buffer;
362 }
363
364 std::string opaqueDecFloat16ToString(StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16)
365 {
366 const auto decFloat16Util = client->getDecFloat16Util(statusWrapper);
367 char buffer[fb::IDecFloat16::STRING_SIZE + 1];
368 decFloat16Util->toString(statusWrapper, &opaqueDecFloat16, static_cast<unsigned>(sizeof(buffer)), buffer);
369 return buffer;
370 }
371
372 std::string opaqueDecFloat34ToString(StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34)
373 {
374 const auto decFloat34Util = client->getDecFloat34Util(statusWrapper);
375 char buffer[fb::IDecFloat34::STRING_SIZE + 1];
376 decFloat34Util->toString(statusWrapper, &opaqueDecFloat34, static_cast<unsigned>(sizeof(buffer)), buffer);
377 return buffer;
378 }
379
380#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
381 OpaqueInt128 boostInt128ToOpaqueInt128(const BoostInt128& boostInt128)
382 {
383 const boost::multiprecision::uint128_t boostUInt128{boostInt128};
384
385 OpaqueInt128 opaqueInt128;
386 opaqueInt128.fb_data[0] = static_cast<std::uint64_t>(boostUInt128 & 0xFFFFFFFFFFFFFFFFULL);
387 opaqueInt128.fb_data[1] = static_cast<std::uint64_t>(boostUInt128 >> 64);
388
389 return opaqueInt128;
390 }
391
392 BoostInt128 opaqueInt128ToBoostInt128(const OpaqueInt128& opaqueInt128)
393 {
394 const auto high = static_cast<std::int64_t>(opaqueInt128.fb_data[1]);
395 BoostInt128 boostInt128 = static_cast<BoostInt128>(high);
396 boostInt128 <<= 64;
397 boostInt128 += static_cast<BoostInt128>(opaqueInt128.fb_data[0]);
398 return boostInt128;
399 }
400
401 OpaqueDecFloat16 boostDecFloat16ToOpaqueDecFloat16(
402 StatusWrapper* statusWrapper, const BoostDecFloat16& boostDecFloat16)
403 {
404 const auto decFloat16Util = client->getDecFloat16Util(statusWrapper);
405 OpaqueDecFloat16 opaqueDecFloat16;
406 decFloat16Util->fromString(statusWrapper, boostDecFloat16.str().c_str(), &opaqueDecFloat16);
407 return opaqueDecFloat16;
408 }
409
410 BoostDecFloat16 opaqueDecFloat16ToBoostDecFloat16(
411 StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16)
412 {
413 return BoostDecFloat16{opaqueDecFloat16ToString(statusWrapper, opaqueDecFloat16)};
414 }
415
416 OpaqueDecFloat34 boostDecFloat34ToOpaqueDecFloat34(
417 StatusWrapper* statusWrapper, const BoostDecFloat34& boostDecFloat34)
418 {
419 const auto decFloat34Util = client->getDecFloat34Util(statusWrapper);
420 OpaqueDecFloat34 opaqueDecFloat34;
421 decFloat34Util->fromString(statusWrapper, boostDecFloat34.str().c_str(), &opaqueDecFloat34);
422 return opaqueDecFloat34;
423 }
424
425 BoostDecFloat34 opaqueDecFloat34ToBoostDecFloat34(
426 StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34)
427 {
428 return BoostDecFloat34{opaqueDecFloat34ToString(statusWrapper, opaqueDecFloat34)};
429 }
430#endif
431
432 // FIXME: move
433 std::byte stringToBoolean(std::string_view value)
434 {
435 auto trimmed = value;
436
437 while (!trimmed.empty() && std::isspace(static_cast<unsigned char>(trimmed.front())))
438 trimmed.remove_prefix(1);
439
440 while (!trimmed.empty() && std::isspace(static_cast<unsigned char>(trimmed.back())))
441 trimmed.remove_suffix(1);
442
443 std::string normalized{trimmed};
444 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
445 [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
446
447 if (normalized == "true")
448 return std::byte{1};
449 else if (normalized == "false")
450 return std::byte{0};
451
452 throwConversionErrorFromString(std::string{value});
453 }
454
455 private:
456 double powerOfTen(int scale) noexcept // FIXME: only for double?
457 {
458 /* FIXME:
459 BoostDecFloat34 powerOfTenDecInternal(unsigned scale)
460 {
461 return boost::multiprecision::pow(BoostDecFloat34{10}, scale);
462 }
463 */
464
465 static constexpr double UPPER_PART[] = {
466 1.e000,
467 1.e032,
468 1.e064,
469 1.e096,
470 1.e128,
471 1.e160,
472 1.e192,
473 1.e224,
474 1.e256,
475 1.e288,
476 };
477
478 static constexpr double LOWER_PART[] = {
479 1.e00,
480 1.e01,
481 1.e02,
482 1.e03,
483 1.e04,
484 1.e05,
485 1.e06,
486 1.e07,
487 1.e08,
488 1.e09,
489 1.e10,
490 1.e11,
491 1.e12,
492 1.e13,
493 1.e14,
494 1.e15,
495 1.e16,
496 1.e17,
497 1.e18,
498 1.e19,
499 1.e20,
500 1.e21,
501 1.e22,
502 1.e23,
503 1.e24,
504 1.e25,
505 1.e26,
506 1.e27,
507 1.e28,
508 1.e29,
509 1.e30,
510 1.e31,
511 };
512
513 assert((scale >= 0) && (scale < 320));
514
515 const auto upper = UPPER_PART[scale >> 5];
516 const auto lower = LOWER_PART[scale & 0x1F];
517
518 return upper * lower;
519 }
520
521 template <typename T>
522 void adjustScale(T& val, int scale, const T minLimit, const T maxLimit)
523 {
524 if (scale > 0)
525 {
526 int fraction = 0;
527
528 do
529 {
530 if (scale == 1)
531 fraction = int(val % 10);
532 val /= 10;
533 } while (--scale);
534
535 if (fraction > 4)
536 ++val;
537 else if (fraction < -4)
538 {
539 static_assert((-85 / 10 == -8) && (-85 % 10 == -5),
540 "If we port to a platform where ((-85 / 10 == -9) && (-85 % 10 == 5)), we'll have to change "
541 "this depending on the platform");
542 --val;
543 }
544 }
545 else if (scale < 0)
546 {
547 do
548 {
549 if ((val > maxLimit / 10) || (val < minLimit / 10))
550 throwNumericOutOfRange();
551
552 val *= 10;
553
554 if (val > maxLimit || val < minLimit)
555 throwNumericOutOfRange();
556 } while (++scale);
557 }
558 }
559
560 template <typename T>
561 T conversionEpsilon()
562 {
563 if constexpr (std::is_same_v<T, float>)
564 return static_cast<T>(1e-5f);
565 else if constexpr (std::is_same_v<T, double>)
566 return static_cast<T>(1e-14);
567#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
568 else if constexpr (std::same_as<T, BoostDecFloat16> || std::same_as<T, BoostDecFloat34>)
569 {
570 const auto epsilon = std::numeric_limits<T>::epsilon();
571 return static_cast<T>(epsilon * static_cast<T>(10));
572 }
573#endif
574 else
575 return std::numeric_limits<T>::epsilon();
576 }
577
578 private:
579 Client* client;
580 };
581} // namespace fbcpp::impl
582
583
584#endif // FBCPP_NUMERIC_CONVERTER_H
boost::multiprecision::number< boost::multiprecision::cpp_dec_float< 34 > > BoostDecFloat34
34-digit decimal floating point using Boost.Multiprecision.
Definition types.h:102
FB_DEC16 OpaqueDecFloat16
Opaque 16-digit decimal floating point exposed by the Firebird API.
Definition types.h:205
boost::multiprecision::number< boost::multiprecision::cpp_dec_float< 16 > > BoostDecFloat16
16-digit decimal floating point using Boost.Multiprecision.
Definition types.h:97
FB_I128 OpaqueInt128
Opaque 128-bit integer exposed by the Firebird API.
Definition types.h:200
FB_DEC34 OpaqueDecFloat34
Opaque 34-digit decimal floating point exposed by the Firebird API.
Definition types.h:210
boost::multiprecision::int128_t BoostInt128
128-bit integer using Boost.Multiprecision.
Definition types.h:85