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 = 7;
59 };
60
61 template <>
62 struct NumberTypePriority<BoostDecFloat16>
63 {
64 static constexpr int value = 6;
65 };
66#endif
67
68#if FB_CPP_USE_BOOST_DECIMAL != 0
69 template <>
70 struct NumberTypePriority<BoostDecimal128>
71 {
72 static constexpr int value = 10;
73 };
74
75 template <>
76 struct NumberTypePriority<BoostDecimal64>
77 {
78 static constexpr int value = 9;
79 };
80
81 template <>
82 struct NumberTypePriority<BoostDecimal32>
83 {
84 static constexpr int value = 8;
85 };
86#endif
87
88 template <>
89 struct NumberTypePriority<double>
90 {
91 static constexpr int value = 6;
92 };
93
94 template <>
95 struct NumberTypePriority<float>
96 {
97 static constexpr int value = 5;
98 };
99
100#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
101 template <>
102 struct NumberTypePriority<BoostInt128>
103 {
104 static constexpr int value = 4;
105 };
106#endif
107
108 template <>
109 struct NumberTypePriority<std::int64_t>
110 {
111 static constexpr int value = 3;
112 };
113
114 template <>
115 struct NumberTypePriority<std::int32_t>
116 {
117 static constexpr int value = 2;
118 };
119
120 template <>
121 struct NumberTypePriority<std::int16_t>
122 {
123 static constexpr int value = 1;
124 };
125
126 template <typename T>
127 inline constexpr int NumberTypePriorityValue =
128 NumberTypePriority<std::remove_cv_t<std::remove_reference_t<T>>>::value;
129
130 template <typename T1, typename T2>
131 using GreaterNumberType = std::conditional_t<(NumberTypePriorityValue<T1> >= NumberTypePriorityValue<T2>), T1, T2>;
132
133 template <typename T>
134 inline constexpr bool IsFloatingNumber = std::is_floating_point_v<T>
135#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
136 || std::same_as<T, BoostDecFloat16> || std::same_as<T, BoostDecFloat34>
137#endif
138#if FB_CPP_USE_BOOST_DECIMAL != 0
139 || std::same_as<T, BoostDecimal32> || std::same_as<T, BoostDecimal64> || std::same_as<T, BoostDecimal128>
140#endif
141 ;
142
143 template <typename T>
144 concept FloatingNumber = IsFloatingNumber<T>;
145
146 template <typename T>
147 concept IntegralNumber = std::is_integral_v<T>
148#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
149 || std::same_as<T, BoostInt128>
150#endif
151 ;
152
153 template <typename T>
154 struct MakeUnsigned
155 {
156 using type = std::make_unsigned_t<T>;
157 };
158
159#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
160 template <>
161 struct MakeUnsigned<BoostInt128>
162 {
163 using type = boost::multiprecision::uint128_t;
164 };
165#endif
166
167 template <typename T>
168 using MakeUnsignedType = typename MakeUnsigned<T>::type;
169
170 class NumericConverter final
171 {
172 public:
173 explicit NumericConverter(Client& client)
174 : client{&client}
175 {
176 }
177
178 [[noreturn]] void throwNumericOutOfRange()
179 {
180 static constexpr std::intptr_t STATUS_NUMERIC_OUT_OF_RANGE[] = {
181 isc_arg_gds,
182 isc_arith_except,
183 isc_arg_gds,
184 isc_numeric_out_of_range,
185 isc_arg_end,
186 };
187
188 throw DatabaseException(*client, STATUS_NUMERIC_OUT_OF_RANGE);
189 }
190
191 [[noreturn]] void throwConversionErrorFromString(const std::string& str)
192 {
193 const std::intptr_t STATUS_CONVERSION_ERROR_FROM_STRING[] = {
194 isc_arg_gds,
195 isc_convert_error,
196 isc_arg_string,
197 reinterpret_cast<std::intptr_t>(str.c_str()),
198 isc_arg_end,
199 };
200
201 throw DatabaseException(*client, STATUS_CONVERSION_ERROR_FROM_STRING);
202 }
203
204 public:
205 template <IntegralNumber To, IntegralNumber From>
206 To numberToNumber(const ScaledNumber<From>& from, int toScale)
207 {
208 const int scaleDiff = toScale - from.scale;
209
210 using ComputeType = GreaterNumberType<decltype(from.value), To>;
211
212 ComputeType result = static_cast<ComputeType>(from.value);
213
214 if (scaleDiff != 0)
215 {
216 adjustScale(result, scaleDiff, static_cast<ComputeType>(std::numeric_limits<To>::min()),
217 static_cast<ComputeType>(std::numeric_limits<To>::max()));
218 }
219
220 if (result < static_cast<ComputeType>(std::numeric_limits<To>::min()) ||
221 result > static_cast<ComputeType>(std::numeric_limits<To>::max()))
222 {
223 throwNumericOutOfRange();
224 }
225
226 return static_cast<To>(result);
227 }
228
229 template <IntegralNumber To, FloatingNumber From>
230 To numberToNumber(const From& from, int toScale)
231 {
232 using ComputeType = GreaterNumberType<double, From>;
233
234 if (isNonFinite(from))
235 throwNumericOutOfRange();
236
237 ComputeType value = convertFloatingValue<ComputeType>(from);
238 const ComputeType eps = conversionEpsilon<ComputeType>();
239 const ComputeType half{0.5};
240
241 if (toScale > 0)
242 value /= powerOfTen<ComputeType>(toScale);
243 else if (toScale < 0)
244 value *= powerOfTen<ComputeType>(-toScale);
245
246 if (value > 0)
247 value += half + eps;
248 else
249 value -= half + eps;
250
251 static const auto minLimit = convertFloatingValue<ComputeType>(std::numeric_limits<To>::min());
252 static const auto maxLimit = convertFloatingValue<ComputeType>(std::numeric_limits<To>::max());
253
254 if (value < minLimit)
255 {
256 if (value > minLimit - ComputeType{1})
257 return std::numeric_limits<To>::min();
258 throwNumericOutOfRange();
259 }
260
261 if (value > maxLimit)
262 {
263 if (value < maxLimit + ComputeType{1})
264 return std::numeric_limits<To>::max();
265 throwNumericOutOfRange();
266 }
267
268 return convertIntegralValue<To>(value);
269 }
270
271 template <FloatingNumber To, typename From>
272 To numberToNumber(const ScaledNumber<From>& from, int toScale = 0)
273 {
274 assert(toScale == 0);
275
276 using ComputeType = GreaterNumberType<double, To>;
277
278 ComputeType value = convertFloatingValue<ComputeType>(from.value);
279
280 if (from.scale != 0)
281 {
282 if (std::abs(from.scale) > std::numeric_limits<To>::max_exponent10)
283 throwNumericOutOfRange();
284
285 if (from.scale > 0)
286 value *= powerOfTen<ComputeType>(from.scale);
287 else if (from.scale < 0)
288 value /= powerOfTen<ComputeType>(-from.scale);
289 }
290
291 return static_cast<To>(value);
292 }
293
294 template <FloatingNumber To, FloatingNumber From>
295 To numberToNumber(const From& from, int toScale = 0)
296 {
297 assert(toScale == 0);
298
299#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
300 if constexpr (std::same_as<To, BoostDecFloat16> && std::same_as<From, BoostDecFloat34>)
301 return boostDecFloat34ToBoostDecFloat16(from);
302#endif
303
304 return convertFloatingValue<To>(from);
305 }
306
307 template <IntegralNumber From>
308 std::string numberToString(const ScaledNumber<From>& from)
309 {
310 char buffer[64];
311
312 const bool isNegative = from.value < 0;
313 const bool isMinLimit = from.value == std::numeric_limits<decltype(from.value)>::min();
314
315 using UnsignedType = MakeUnsignedType<decltype(from.value)>;
316
317 auto unsignedValue = isMinLimit ? static_cast<UnsignedType>(-(from.value + 1)) + 1
318 : static_cast<UnsignedType>(isNegative ? -from.value : from.value);
319
320 int digitCount = 0;
321
322 do
323 {
324 buffer[digitCount++] = static_cast<char>((unsignedValue % 10) + '0');
325 unsignedValue /= 10;
326 } while (unsignedValue > 0);
327
328 std::string result;
329
330 if (isNegative)
331 result += '-';
332
333 if (from.scale >= 0)
334 {
335 for (int i = digitCount - 1; i >= 0; --i)
336 result += buffer[i];
337
338 result.append(static_cast<std::string::size_type>(from.scale), '0');
339 }
340 else
341 {
342 const int decimalPlaces = -from.scale;
343
344 if (decimalPlaces >= static_cast<int>(digitCount))
345 {
346 result += "0.";
347 const int leadingZeros = decimalPlaces - digitCount;
348 result.append(static_cast<std::string::size_type>(leadingZeros), '0');
349
350 for (int i = digitCount - 1; i >= 0; --i)
351 result += buffer[i];
352 }
353 else
354 {
355 for (int i = digitCount - 1; i >= decimalPlaces; --i)
356 result += buffer[i];
357
358 result += '.';
359
360 for (int i = decimalPlaces - 1; i >= 0; --i)
361 result += buffer[i];
362 }
363 }
364
365 return result;
366 }
367
368 template <FloatingNumber From>
369 std::string numberToString(const From& from)
370 {
371 if constexpr (std::is_floating_point_v<From>)
372 {
373 if (std::isnan(from))
374 return "NaN";
375 if (std::isinf(from))
376 return from > 0 ? "Infinity" : "-Infinity";
377 return std::to_string(from);
378 }
379#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
380 else if constexpr (std::same_as<From, BoostDecFloat16> || std::same_as<From, BoostDecFloat34>)
381 {
382 if ((boost::multiprecision::isnan) (from))
383 return "NaN";
384 if ((boost::multiprecision::isinf) (from))
385 return from > 0 ? "Infinity" : "-Infinity";
386 return from.str();
387 }
388#endif
389#if FB_CPP_USE_BOOST_DECIMAL != 0
390 else if constexpr (std::same_as<From, BoostDecimal32> || std::same_as<From, BoostDecimal64> ||
391 std::same_as<From, BoostDecimal128>)
392 {
393 if ((boost::decimal::isnan) (from))
394 return "NaN";
395 if ((boost::decimal::isinf) (from))
396 return from > 0 ? "Infinity" : "-Infinity";
397 return boost::decimal::to_string(from);
398 }
399#endif
400 else
401 return from.str();
402 }
403
404 std::string opaqueInt128ToString(StatusWrapper* statusWrapper, const OpaqueInt128& opaqueInt128, int scale)
405 {
406 const auto int128Util = client->getInt128Util(statusWrapper);
407 char buffer[fb::IInt128::STRING_SIZE + 1];
408 int128Util->toString(statusWrapper, &opaqueInt128, scale, static_cast<unsigned>(sizeof(buffer)), buffer);
409 return buffer;
410 }
411
412 std::string opaqueDecFloat16ToString(StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16)
413 {
414 const auto decFloat16Util = client->getDecFloat16Util(statusWrapper);
415 char buffer[fb::IDecFloat16::STRING_SIZE + 1];
416 decFloat16Util->toString(statusWrapper, &opaqueDecFloat16, static_cast<unsigned>(sizeof(buffer)), buffer);
417 return buffer;
418 }
419
420 std::string opaqueDecFloat34ToString(StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34)
421 {
422 const auto decFloat34Util = client->getDecFloat34Util(statusWrapper);
423 char buffer[fb::IDecFloat34::STRING_SIZE + 1];
424 decFloat34Util->toString(statusWrapper, &opaqueDecFloat34, static_cast<unsigned>(sizeof(buffer)), buffer);
425 return buffer;
426 }
427
428#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
429 OpaqueInt128 boostInt128ToOpaqueInt128(StatusWrapper* statusWrapper, const BoostInt128& boostInt128)
430 {
431 validateFirebirdInt128(boostInt128);
432
433 OpaqueInt128 opaqueInt128;
434 const auto value = boostInt128.str();
435 client->getInt128Util(statusWrapper)->fromString(statusWrapper, 0, value.c_str(), &opaqueInt128);
436
437 return opaqueInt128;
438 }
439
440 OpaqueInt128 boostInt128ToOpaqueInt128(const BoostInt128& boostInt128)
441 {
442 StatusWrapper status{*client};
443 return boostInt128ToOpaqueInt128(&status, boostInt128);
444 }
445
446 BoostInt128 opaqueInt128ToBoostInt128(StatusWrapper* statusWrapper, const OpaqueInt128& opaqueInt128)
447 {
448 return BoostInt128{opaqueInt128ToString(statusWrapper, opaqueInt128, 0)};
449 }
450
451 BoostInt128 opaqueInt128ToBoostInt128(const OpaqueInt128& opaqueInt128)
452 {
453 StatusWrapper status{*client};
454 return opaqueInt128ToBoostInt128(&status, opaqueInt128);
455 }
456
457 OpaqueDecFloat16 boostDecFloat16ToOpaqueDecFloat16(
458 StatusWrapper* statusWrapper, const BoostDecFloat16& boostDecFloat16)
459 {
460 const auto decFloat16Util = client->getDecFloat16Util(statusWrapper);
461 OpaqueDecFloat16 opaqueDecFloat16;
462 const auto value = numberToString(boostDecFloat16);
463 decFloat16Util->fromString(statusWrapper, value.c_str(), &opaqueDecFloat16);
464 return opaqueDecFloat16;
465 }
466
467 BoostDecFloat16 opaqueDecFloat16ToBoostDecFloat16(
468 StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16)
469 {
470 const auto value = opaqueDecFloat16ToString(statusWrapper, opaqueDecFloat16);
471
472 if (isSignalingNaN(value))
473 throw FbCppException("BoostDecFloat16 cannot represent a signaling NaN");
474 else if (value == "Infinity")
475 return std::numeric_limits<BoostDecFloat16>::infinity();
476 else if (value == "-Infinity")
477 return -std::numeric_limits<BoostDecFloat16>::infinity();
478
479 try
480 {
481 return BoostDecFloat16{value};
482 }
483 catch (const std::exception&)
484 {
485 throwConversionErrorFromString(value);
486 }
487 }
488
489 OpaqueDecFloat34 boostDecFloat34ToOpaqueDecFloat34(
490 StatusWrapper* statusWrapper, const BoostDecFloat34& boostDecFloat34)
491 {
492 const auto decFloat34Util = client->getDecFloat34Util(statusWrapper);
493 OpaqueDecFloat34 opaqueDecFloat34;
494 const auto value = numberToString(boostDecFloat34);
495 decFloat34Util->fromString(statusWrapper, value.c_str(), &opaqueDecFloat34);
496 return opaqueDecFloat34;
497 }
498
499 BoostDecFloat34 opaqueDecFloat34ToBoostDecFloat34(
500 StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34)
501 {
502 const auto value = opaqueDecFloat34ToString(statusWrapper, opaqueDecFloat34);
503
504 if (isSignalingNaN(value))
505 throw FbCppException("BoostDecFloat34 cannot represent a signaling NaN; use OpaqueDecFloat34");
506 else if (value == "Infinity")
507 return std::numeric_limits<BoostDecFloat34>::infinity();
508 else if (value == "-Infinity")
509 return -std::numeric_limits<BoostDecFloat34>::infinity();
510
511 try
512 {
513 return BoostDecFloat34{value};
514 }
515 catch (const std::exception&)
516 {
517 throwConversionErrorFromString(value);
518 }
519 }
520
521 BoostDecFloat16 boostDecFloat34ToBoostDecFloat16(const BoostDecFloat34& boostDecFloat34)
522 {
523 StatusWrapper status{*client};
524 OpaqueDecFloat16 opaqueDecFloat16;
525 const auto value = numberToString(boostDecFloat34);
526 client->getDecFloat16Util(&status)->fromString(&status, value.c_str(), &opaqueDecFloat16);
527 return opaqueDecFloat16ToBoostDecFloat16(&status, opaqueDecFloat16);
528 }
529#endif
530
531#if FB_CPP_USE_BOOST_DECIMAL != 0
532 BoostDecimal32 opaqueDecFloat16ToBoostDecimal32(
533 StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16)
534 {
535 return stringToBoostDecimal<BoostDecimal32>(opaqueDecFloat16ToString(statusWrapper, opaqueDecFloat16));
536 }
537
538 BoostDecimal64 opaqueDecFloat16ToBoostDecimal64(
539 StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16)
540 {
541 return stringToBoostDecimal<BoostDecimal64>(opaqueDecFloat16ToString(statusWrapper, opaqueDecFloat16));
542 }
543
544 BoostDecimal128 opaqueDecFloat16ToBoostDecimal128(
545 StatusWrapper* statusWrapper, const OpaqueDecFloat16& opaqueDecFloat16)
546 {
547 return stringToBoostDecimal<BoostDecimal128>(opaqueDecFloat16ToString(statusWrapper, opaqueDecFloat16));
548 }
549
550 BoostDecimal32 opaqueDecFloat34ToBoostDecimal32(
551 StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34)
552 {
553 return stringToBoostDecimal<BoostDecimal32>(opaqueDecFloat34ToString(statusWrapper, opaqueDecFloat34));
554 }
555
556 BoostDecimal64 opaqueDecFloat34ToBoostDecimal64(
557 StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34)
558 {
559 return stringToBoostDecimal<BoostDecimal64>(opaqueDecFloat34ToString(statusWrapper, opaqueDecFloat34));
560 }
561
562 BoostDecimal128 opaqueDecFloat34ToBoostDecimal128(
563 StatusWrapper* statusWrapper, const OpaqueDecFloat34& opaqueDecFloat34)
564 {
565 return stringToBoostDecimal<BoostDecimal128>(opaqueDecFloat34ToString(statusWrapper, opaqueDecFloat34));
566 }
567
568 OpaqueDecFloat16 boostDecimal32ToOpaqueDecFloat16(StatusWrapper* statusWrapper, const BoostDecimal32& value)
569 {
570 return boostDecimalToOpaqueDecFloat16(statusWrapper, value);
571 }
572
573 OpaqueDecFloat16 boostDecimal64ToOpaqueDecFloat16(StatusWrapper* statusWrapper, const BoostDecimal64& value)
574 {
575 return boostDecimalToOpaqueDecFloat16(statusWrapper, value);
576 }
577
578 OpaqueDecFloat16 boostDecimal128ToOpaqueDecFloat16(StatusWrapper* statusWrapper, const BoostDecimal128& value)
579 {
580 return boostDecimalToOpaqueDecFloat16(statusWrapper, value);
581 }
582
583 OpaqueDecFloat34 boostDecimal32ToOpaqueDecFloat34(StatusWrapper* statusWrapper, const BoostDecimal32& value)
584 {
585 return boostDecimalToOpaqueDecFloat34(statusWrapper, value);
586 }
587
588 OpaqueDecFloat34 boostDecimal64ToOpaqueDecFloat34(StatusWrapper* statusWrapper, const BoostDecimal64& value)
589 {
590 return boostDecimalToOpaqueDecFloat34(statusWrapper, value);
591 }
592
593 OpaqueDecFloat34 boostDecimal128ToOpaqueDecFloat34(StatusWrapper* statusWrapper, const BoostDecimal128& value)
594 {
595 return boostDecimalToOpaqueDecFloat34(statusWrapper, value);
596 }
597#endif
598
599 // FIXME: move
600 std::byte stringToBoolean(std::string_view value)
601 {
602 auto trimmed = value;
603
604 while (!trimmed.empty() && std::isspace(static_cast<unsigned char>(trimmed.front())))
605 trimmed.remove_prefix(1);
606
607 while (!trimmed.empty() && std::isspace(static_cast<unsigned char>(trimmed.back())))
608 trimmed.remove_suffix(1);
609
610 std::string normalized{trimmed};
611 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
612 [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
613
614 if (normalized == "true")
615 return std::byte{1};
616 else if (normalized == "false")
617 return std::byte{0};
618
619 throwConversionErrorFromString(std::string{value});
620 }
621
622 private:
623 template <typename T>
624 static bool isNonFinite(const T& value)
625 {
626 using ValueType = std::remove_cvref_t<T>;
627
628 if constexpr (std::is_floating_point_v<ValueType>)
629 return std::isnan(value) || std::isinf(value);
630#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
631 else if constexpr (std::same_as<ValueType, BoostDecFloat16> || std::same_as<ValueType, BoostDecFloat34>)
632 return (boost::multiprecision::isnan) (value) || (boost::multiprecision::isinf) (value);
633#endif
634#if FB_CPP_USE_BOOST_DECIMAL != 0
635 else if constexpr (std::same_as<ValueType, BoostDecimal32> || std::same_as<ValueType, BoostDecimal64> ||
636 std::same_as<ValueType, BoostDecimal128>)
637 return (boost::decimal::isnan) (value) || (boost::decimal::isinf) (value);
638#endif
639 else
640 return false;
641 }
642
643 template <typename T>
644 static bool isSignalingNaNValue(const T& value)
645 {
646#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 || FB_CPP_USE_BOOST_DECIMAL != 0
647 using ValueType = std::remove_cvref_t<T>;
648#endif
649
650#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
651 if constexpr (std::same_as<ValueType, BoostDecFloat16> || std::same_as<ValueType, BoostDecFloat34>)
652 return (boost::multiprecision::isnan) (value) && isSignalingNaN(value.str());
653#endif
654#if FB_CPP_USE_BOOST_DECIMAL != 0
655 if constexpr (std::same_as<ValueType, BoostDecimal32> || std::same_as<ValueType, BoostDecimal64> ||
656 std::same_as<ValueType, BoostDecimal128>)
657 return (boost::decimal::issignaling) (value);
658#endif
659 return false;
660 }
661
662 template <typename To, typename From>
663 To convertFloatingValue(const From& from)
664 {
665 using ToType = std::remove_cvref_t<To>;
666 using FromType = std::remove_cvref_t<From>;
667
668 if constexpr (std::same_as<ToType, FromType>)
669 return from;
670#if FB_CPP_USE_BOOST_DECIMAL != 0
671 else if constexpr (std::same_as<ToType, BoostDecimal32> || std::same_as<ToType, BoostDecimal64> ||
672 std::same_as<ToType, BoostDecimal128>)
673 {
674 if (isSignalingNaNValue(from))
675 return std::numeric_limits<ToType>::signaling_NaN();
676 if constexpr (std::is_floating_point_v<FromType>)
677 return ToType{std::format("{:.16e}", from)};
678#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
679 else if constexpr (std::same_as<FromType, BoostDecFloat16> || std::same_as<FromType, BoostDecFloat34>)
680 {
681 if ((boost::multiprecision::isnan) (from))
682 return std::numeric_limits<ToType>::quiet_NaN();
683 if ((boost::multiprecision::isinf) (from))
684 {
685 return from > 0 ? std::numeric_limits<ToType>::infinity()
686 : -std::numeric_limits<ToType>::infinity();
687 }
688 return ToType{from.str()};
689 }
690 else if constexpr (std::same_as<FromType, BoostInt128>)
691 return ToType{from.str()};
692#endif
693 else
694 return static_cast<ToType>(from);
695 }
696#endif
697#if FB_CPP_USE_BOOST_MULTIPRECISION != 0 && FB_CPP_USE_BOOST_DECIMAL != 0
698 else if constexpr ((std::same_as<ToType, BoostDecFloat16> || std::same_as<ToType, BoostDecFloat34>) &&
699 (std::same_as<FromType, BoostDecimal32> || std::same_as<FromType, BoostDecimal64> ||
700 std::same_as<FromType, BoostDecimal128>) )
701 {
702 if (isSignalingNaNValue(from))
703 throw FbCppException("Boost.Multiprecision cannot represent a signaling NaN");
704 if ((boost::decimal::isnan) (from))
705 return ToType{"NaN"};
706 if ((boost::decimal::isinf) (from))
707 {
708 return from > 0 ? std::numeric_limits<ToType>::infinity()
709 : -std::numeric_limits<ToType>::infinity();
710 }
711 return ToType{boost::decimal::to_string(from)};
712 }
713#endif
714 else
715 return static_cast<ToType>(from);
716 }
717
718 template <typename To, typename From>
719 To convertIntegralValue(const From& from)
720 {
721 using ToType = std::remove_cvref_t<To>;
722
723#if FB_CPP_USE_BOOST_DECIMAL != 0
724 using FromType = std::remove_cvref_t<From>;
725
726 if constexpr (std::same_as<FromType, BoostDecimal32> || std::same_as<FromType, BoostDecimal64> ||
727 std::same_as<FromType, BoostDecimal128>)
728 {
729#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
730 if constexpr (std::same_as<ToType, BoostInt128>)
731 {
732 char buffer[64];
733 const auto result = boost::decimal::to_chars(
734 buffer, buffer + sizeof(buffer), from, boost::decimal::chars_format::fixed);
735
736 if (result.ec != std::errc{})
737 throwNumericOutOfRange();
738
739 std::string value{buffer, result.ptr};
740 if (const auto decimalPoint = value.find('.'); decimalPoint != std::string::npos)
741 value.erase(decimalPoint);
742
743 return ToType{value};
744 }
745#endif
746
747 if constexpr (std::same_as<ToType, bool>)
748 return static_cast<ToType>(static_cast<bool>(from));
749 else if constexpr (std::is_signed_v<ToType>)
750 return static_cast<ToType>(static_cast<long long>(from));
751 else
752 return static_cast<ToType>(static_cast<unsigned long long>(from));
753 }
754 else
755#endif
756 return static_cast<ToType>(from);
757 }
758
759 static bool isSignalingNaN(std::string_view value)
760 {
761 std::string normalized{value};
762 if (!normalized.empty() && (normalized.front() == '+' || normalized.front() == '-'))
763 normalized.erase(0, 1);
764
765 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
766 [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
767
768 return normalized.starts_with("snan");
769 }
770
771#if FB_CPP_USE_BOOST_DECIMAL != 0
772 template <typename T>
773 T stringToBoostDecimal(const std::string& value)
774 {
775 if (isSignalingNaN(value))
776 return std::numeric_limits<T>::signaling_NaN();
777 else if (value == "Infinity")
778 return std::numeric_limits<T>::infinity();
779 else if (value == "-Infinity")
780 return -std::numeric_limits<T>::infinity();
781
782 try
783 {
784 return T{value};
785 }
786 catch (const std::exception&)
787 {
788 throwConversionErrorFromString(value);
789 }
790 }
791
792 template <typename T>
793 OpaqueDecFloat16 boostDecimalToOpaqueDecFloat16(StatusWrapper* statusWrapper, const T& value)
794 {
795 if (isSignalingNaNValue(value))
796 throw FbCppException("Boost.Decimal cannot represent a signaling NaN; use OpaqueDecFloat16");
797
798 OpaqueDecFloat16 result;
799 const auto stringValue = numberToString(value);
800 client->getDecFloat16Util(statusWrapper)->fromString(statusWrapper, stringValue.c_str(), &result);
801 return result;
802 }
803
804 template <typename T>
805 OpaqueDecFloat34 boostDecimalToOpaqueDecFloat34(StatusWrapper* statusWrapper, const T& value)
806 {
807 if (isSignalingNaNValue(value))
808 throw FbCppException("Boost.Decimal cannot represent a signaling NaN; use OpaqueDecFloat34");
809
810 OpaqueDecFloat34 result;
811 const auto stringValue = numberToString(value);
812 client->getDecFloat34Util(statusWrapper)->fromString(statusWrapper, stringValue.c_str(), &result);
813 return result;
814 }
815#endif
816
817 double powerOfTenDouble(int scale) noexcept
818 {
819 static constexpr double UPPER_PART[] = {
820 1.e000,
821 1.e032,
822 1.e064,
823 1.e096,
824 1.e128,
825 1.e160,
826 1.e192,
827 1.e224,
828 1.e256,
829 1.e288,
830 };
831
832 static constexpr double LOWER_PART[] = {
833 1.e00,
834 1.e01,
835 1.e02,
836 1.e03,
837 1.e04,
838 1.e05,
839 1.e06,
840 1.e07,
841 1.e08,
842 1.e09,
843 1.e10,
844 1.e11,
845 1.e12,
846 1.e13,
847 1.e14,
848 1.e15,
849 1.e16,
850 1.e17,
851 1.e18,
852 1.e19,
853 1.e20,
854 1.e21,
855 1.e22,
856 1.e23,
857 1.e24,
858 1.e25,
859 1.e26,
860 1.e27,
861 1.e28,
862 1.e29,
863 1.e30,
864 1.e31,
865 };
866
867 assert((scale >= 0) && (scale < 320));
868
869 const auto upper = UPPER_PART[scale >> 5];
870 const auto lower = LOWER_PART[scale & 0x1F];
871
872 return upper * lower;
873 }
874
875 template <typename T>
876 T powerOfTen(int scale)
877 {
878 assert((scale >= 0) && (scale < 320));
879
880 if constexpr (std::same_as<T, float> || std::same_as<T, double>)
881 return static_cast<T>(powerOfTenDouble(scale));
882 else
883 {
884 T result{1};
885
886 for (int i = 0; i < scale; ++i)
887 result *= 10;
888
889 return result;
890 }
891 }
892
893#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
894 void validateFirebirdInt128(const BoostInt128& value)
895 {
896 constexpr BoostInt128 FB_MAX_INT128 = (BoostInt128{1} << 127) - 1;
897 constexpr BoostInt128 FB_MIN_INT128 = -(BoostInt128{1} << 127);
898
899 if (value < FB_MIN_INT128 || value > FB_MAX_INT128)
900 throwNumericOutOfRange();
901 }
902
903#endif
904
905 template <typename T>
906 void adjustScale(T& val, int scale, const T minLimit, const T maxLimit)
907 {
908 if (scale > 0)
909 {
910 int fraction = 0;
911
912 do
913 {
914 if (scale == 1)
915 fraction = int(val % 10);
916 val /= 10;
917 } while (--scale);
918
919 if (fraction > 4)
920 ++val;
921 else if (fraction < -4)
922 {
923 static_assert((-85 / 10 == -8) && (-85 % 10 == -5),
924 "If we port to a platform where ((-85 / 10 == -9) && (-85 % 10 == 5)), we'll have to change "
925 "this depending on the platform");
926 --val;
927 }
928 }
929 else if (scale < 0)
930 {
931 do
932 {
933 if ((val > maxLimit / 10) || (val < minLimit / 10))
934 throwNumericOutOfRange();
935
936 val *= 10;
937
938 if (val > maxLimit || val < minLimit)
939 throwNumericOutOfRange();
940 } while (++scale);
941 }
942 }
943
944 template <typename T>
945 T conversionEpsilon()
946 {
947 if constexpr (std::is_same_v<T, float>)
948 return static_cast<T>(1e-5f);
949 else if constexpr (std::is_same_v<T, double>)
950 return static_cast<T>(1e-14);
951#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
952 else if constexpr (std::same_as<T, BoostDecFloat16> || std::same_as<T, BoostDecFloat34>)
953 {
954 const auto epsilon = std::numeric_limits<T>::epsilon();
955 return static_cast<T>(epsilon * static_cast<T>(10));
956 }
957#endif
958#if FB_CPP_USE_BOOST_DECIMAL != 0
959 else if constexpr (std::same_as<T, BoostDecimal32> || std::same_as<T, BoostDecimal64> ||
960 std::same_as<T, BoostDecimal128>)
961 {
962 const auto epsilon = std::numeric_limits<T>::epsilon();
963 return epsilon * static_cast<T>(10);
964 }
965#endif
966 else
967 return std::numeric_limits<T>::epsilon();
968 }
969
970 private:
971 Client* client;
972 };
973} // namespace fbcpp::impl
974
975
976#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:116
FB_DEC16 OpaqueDecFloat16
Opaque 16-digit decimal floating point exposed by the Firebird API.
Definition types.h:236
boost::multiprecision::number< boost::multiprecision::cpp_dec_float< 16 > > BoostDecFloat16
16-digit decimal floating point using Boost.Multiprecision.
Definition types.h:111
boost::decimal::decimal128_t BoostDecimal128
34-digit decimal floating point using Boost.Decimal.
Definition types.h:133
boost::decimal::decimal32_t BoostDecimal32
7-digit decimal floating point using Boost.Decimal.
Definition types.h:123
FB_I128 OpaqueInt128
Opaque 128-bit integer exposed by the Firebird API.
Definition types.h:231
FB_DEC34 OpaqueDecFloat34
Opaque 34-digit decimal floating point exposed by the Firebird API.
Definition types.h:241
boost::multiprecision::int128_t BoostInt128
128-bit integer using Boost.Multiprecision.
Definition types.h:99
boost::decimal::decimal64_t BoostDecimal64
16-digit decimal floating point using Boost.Decimal.
Definition types.h:128