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, StatusWrapper* statusWrapper)
151 : client{client},
152 statusWrapper{statusWrapper}
153 {
154 }
155
156 [[noreturn]] void throwNumericOutOfRange()
157 {
158 static constexpr std::intptr_t STATUS_NUMERIC_OUT_OF_RANGE[] = {
159 isc_arith_except,
160 isc_numeric_out_of_range,
161 isc_arg_end,
162 };
163
164 throw DatabaseException(client, STATUS_NUMERIC_OUT_OF_RANGE);
165 }
166
167 [[noreturn]] void throwConversionErrorFromString(const std::string& str)
168 {
169 const std::intptr_t STATUS_CONVERSION_ERROR_FROM_STRING[] = {
170 isc_convert_error,
171 reinterpret_cast<std::intptr_t>(str.c_str()),
172 isc_arg_end,
173 };
174
175 throw DatabaseException(client, STATUS_CONVERSION_ERROR_FROM_STRING);
176 }
177
178 public:
179 template <IntegralNumber To, IntegralNumber From>
180 To numberToNumber(const ScaledNumber<From>& from, int toScale)
181 {
182 const int scaleDiff = toScale - from.scale;
183
184 using ComputeType = GreaterNumberType<decltype(from.value), To>;
185
186 ComputeType result = static_cast<ComputeType>(from.value);
187
188 if (scaleDiff != 0)
189 {
190 adjustScale(result, scaleDiff, static_cast<ComputeType>(std::numeric_limits<To>::min()),
191 static_cast<ComputeType>(std::numeric_limits<To>::max()));
192 }
193
194 if (result < static_cast<ComputeType>(std::numeric_limits<To>::min()) ||
195 result > static_cast<ComputeType>(std::numeric_limits<To>::max()))
196 {
197 throwNumericOutOfRange();
198 }
199
200 return static_cast<To>(result);
201 }
202
203 template <IntegralNumber To, FloatingNumber From>
204 To numberToNumber(const From& from, int toScale)
205 {
206 using ComputeType = GreaterNumberType<double, From>;
207
208 if constexpr (std::is_floating_point_v<From>)
209 {
210 if (std::isnan(from) || std::isinf(from))
211 throwNumericOutOfRange();
212 }
213
214 ComputeType value{from};
215 const ComputeType eps = conversionEpsilon<ComputeType>();
216
217 if (toScale > 0)
218 value /= powerOfTen(toScale);
219 else if (toScale < 0)
220 value *= powerOfTen(-toScale);
221
222 if (value > 0)
223 value += 0.5f + eps;
224 else
225 value -= 0.5f + eps;
226
227 static const auto minLimit = static_cast<ComputeType>(std::numeric_limits<To>::min());
228 static const auto maxLimit = static_cast<ComputeType>(std::numeric_limits<To>::max());
229
230 if (value < minLimit)
231 {
232 if (value > minLimit - 1.0f)
233 return std::numeric_limits<To>::min();
234 throwNumericOutOfRange();
235 }
236
237 if (value > maxLimit)
238 {
239 if (value < maxLimit + 1.0f)
240 return std::numeric_limits<To>::max();
241 throwNumericOutOfRange();
242 }
243
244 return static_cast<To>(value);
245 }
246
247 template <FloatingNumber To, typename From>
248 To numberToNumber(const ScaledNumber<From>& from, int toScale = 0)
249 {
250 assert(toScale == 0);
251
252 using ComputeType = GreaterNumberType<double, To>;
253
254 ComputeType value = static_cast<ComputeType>(from.value); // FIXME: decfloat
255
256 if (from.scale != 0)
257 {
258 if (std::abs(from.scale) > std::numeric_limits<To>::max_exponent10)
259 throwNumericOutOfRange();
260
261 if (from.scale > 0)
262 value *= powerOfTen(from.scale);
263 else if (from.scale < 0)
264 value /= powerOfTen(-from.scale);
265 }
266
267 return static_cast<To>(value);
268 }
269
270 template <FloatingNumber To, FloatingNumber From>
271 To numberToNumber(const From& from, int toScale = 0)
272 {
273 assert(toScale == 0);
274
275 if constexpr (std::is_floating_point_v<From> && !std::is_floating_point_v<To>)
276 return To{std::format("{:.16e}", from)};
277 else
278 return static_cast<To>(from);
279 }
280
281 template <IntegralNumber From>
282 std::string numberToString(const ScaledNumber<From>& from)
283 {
284 char buffer[64];
285
286 const bool isNegative = from.value < 0;
287 const bool isMinLimit = from.value == std::numeric_limits<decltype(from.value)>::min();
288
289 using UnsignedType = MakeUnsignedType<decltype(from.value)>;
290
291 auto unsignedValue = isMinLimit ? static_cast<UnsignedType>(-(from.value + 1)) + 1
292 : static_cast<UnsignedType>(isNegative ? -from.value : from.value);
293
294 int digitCount = 0;
295
296 do
297 {
298 buffer[digitCount++] = static_cast<char>((unsignedValue % 10) + '0');
299 unsignedValue /= 10;
300 } while (unsignedValue > 0);
301
302 std::string result;
303
304 if (isNegative)
305 result += '-';
306
307 if (from.scale >= 0)
308 {
309 for (int i = digitCount - 1; i >= 0; --i)
310 result += buffer[i];
311
312 result.append(static_cast<std::string::size_type>(from.scale), '0');
313 }
314 else
315 {
316 const int decimalPlaces = -from.scale;
317
318 if (decimalPlaces >= static_cast<int>(digitCount))
319 {
320 result += "0.";
321 const int leadingZeros = decimalPlaces - digitCount;
322 result.append(static_cast<std::string::size_type>(leadingZeros), '0');
323
324 for (int i = digitCount - 1; i >= 0; --i)
325 result += buffer[i];
326 }
327 else
328 {
329 for (int i = digitCount - 1; i >= decimalPlaces; --i)
330 result += buffer[i];
331
332 result += '.';
333
334 for (int i = decimalPlaces - 1; i >= 0; --i)
335 result += buffer[i];
336 }
337 }
338
339 return result;
340 }
341
342 template <FloatingNumber From>
343 std::string numberToString(const From& from)
344 {
345 if constexpr (std::is_floating_point_v<From>)
346 {
347 if (std::isnan(from))
348 return "NaN";
349 if (std::isinf(from))
350 return from > 0 ? "Infinity" : "-Infinity";
351 return std::to_string(from);
352 }
353 else
354 return from.str();
355 }
356
357 std::string opaqueInt128ToString(const OpaqueInt128& opaqueInt128, int scale)
358 {
359 const auto int128Util = client.getUtil()->getInt128(statusWrapper);
360 char buffer[fb::IInt128::STRING_SIZE + 1];
361 int128Util->toString(statusWrapper, &opaqueInt128, scale, static_cast<unsigned>(sizeof(buffer)), buffer);
362 return buffer;
363 }
364
365 std::string opaqueDecFloat16ToString(const OpaqueDecFloat16& opaqueDecFloat16)
366 {
367 const auto decFloat16Util = client.getDecFloat16Util(statusWrapper);
368 char buffer[fb::IDecFloat16::STRING_SIZE + 1];
369 decFloat16Util->toString(statusWrapper, &opaqueDecFloat16, static_cast<unsigned>(sizeof(buffer)), buffer);
370 return buffer;
371 }
372
373 std::string opaqueDecFloat34ToString(const OpaqueDecFloat34& opaqueDecFloat34)
374 {
375 const auto decFloat34Util = client.getDecFloat34Util(statusWrapper);
376 char buffer[fb::IDecFloat34::STRING_SIZE + 1];
377 decFloat34Util->toString(statusWrapper, &opaqueDecFloat34, static_cast<unsigned>(sizeof(buffer)), buffer);
378 return buffer;
379 }
380
381#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
382 OpaqueInt128 boostInt128ToOpaqueInt128(const BoostInt128& boostInt128)
383 {
384 const boost::multiprecision::uint128_t boostUInt128{boostInt128};
385
386 OpaqueInt128 opaqueInt128;
387 opaqueInt128.fb_data[0] = static_cast<std::uint64_t>(boostUInt128 & 0xFFFFFFFFFFFFFFFFULL);
388 opaqueInt128.fb_data[1] = static_cast<std::uint64_t>(boostUInt128 >> 64);
389
390 return opaqueInt128;
391 }
392
393 BoostInt128 opaqueInt128ToBoostInt128(const OpaqueInt128& opaqueInt128)
394 {
395 const auto high = static_cast<std::int64_t>(opaqueInt128.fb_data[1]);
396 BoostInt128 boostInt128 = static_cast<BoostInt128>(high);
397 boostInt128 <<= 64;
398 boostInt128 += static_cast<BoostInt128>(opaqueInt128.fb_data[0]);
399 return boostInt128;
400 }
401
402 OpaqueDecFloat16 boostDecFloat16ToOpaqueDecFloat16(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(const OpaqueDecFloat16& opaqueDecFloat16)
411 {
412 return BoostDecFloat16{opaqueDecFloat16ToString(opaqueDecFloat16)};
413 }
414
415 OpaqueDecFloat34 boostDecFloat34ToOpaqueDecFloat34(const BoostDecFloat34& boostDecFloat34)
416 {
417 const auto decFloat34Util = client.getDecFloat34Util(statusWrapper);
418 OpaqueDecFloat34 opaqueDecFloat34;
419 decFloat34Util->fromString(statusWrapper, boostDecFloat34.str().c_str(), &opaqueDecFloat34);
420 return opaqueDecFloat34;
421 }
422
423 BoostDecFloat34 opaqueDecFloat34ToBoostDecFloat34(const OpaqueDecFloat34& opaqueDecFloat34)
424 {
425 return BoostDecFloat34{opaqueDecFloat34ToString(opaqueDecFloat34)};
426 }
427#endif
428
429 // FIXME: move
430 std::byte stringToBoolean(std::string_view value)
431 {
432 auto trimmed = value;
433
434 while (!trimmed.empty() && std::isspace(static_cast<unsigned char>(trimmed.front())))
435 trimmed.remove_prefix(1);
436
437 while (!trimmed.empty() && std::isspace(static_cast<unsigned char>(trimmed.back())))
438 trimmed.remove_suffix(1);
439
440 std::string normalized{trimmed};
441 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
442 [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
443
444 if (normalized == "true")
445 return std::byte{1};
446 else if (normalized == "false")
447 return std::byte{0};
448
449 throwConversionErrorFromString(std::string{value});
450 }
451
452 private:
453 double powerOfTen(int scale) noexcept // FIXME: only for double?
454 {
455 /* FIXME:
456 BoostDecFloat34 powerOfTenDecInternal(unsigned scale)
457 {
458 return boost::multiprecision::pow(BoostDecFloat34{10}, scale);
459 }
460 */
461
462 static constexpr double UPPER_PART[] = {
463 1.e000,
464 1.e032,
465 1.e064,
466 1.e096,
467 1.e128,
468 1.e160,
469 1.e192,
470 1.e224,
471 1.e256,
472 1.e288,
473 };
474
475 static constexpr double LOWER_PART[] = {
476 1.e00,
477 1.e01,
478 1.e02,
479 1.e03,
480 1.e04,
481 1.e05,
482 1.e06,
483 1.e07,
484 1.e08,
485 1.e09,
486 1.e10,
487 1.e11,
488 1.e12,
489 1.e13,
490 1.e14,
491 1.e15,
492 1.e16,
493 1.e17,
494 1.e18,
495 1.e19,
496 1.e20,
497 1.e21,
498 1.e22,
499 1.e23,
500 1.e24,
501 1.e25,
502 1.e26,
503 1.e27,
504 1.e28,
505 1.e29,
506 1.e30,
507 1.e31,
508 };
509
510 assert((scale >= 0) && (scale < 320));
511
512 const auto upper = UPPER_PART[scale >> 5];
513 const auto lower = LOWER_PART[scale & 0x1F];
514
515 return upper * lower;
516 }
517
518 template <typename T>
519 void adjustScale(T& val, int scale, const T minLimit, const T maxLimit)
520 {
521 if (scale > 0)
522 {
523 int fraction = 0;
524
525 do
526 {
527 if (scale == 1)
528 fraction = int(val % 10);
529 val /= 10;
530 } while (--scale);
531
532 if (fraction > 4)
533 ++val;
534 else if (fraction < -4)
535 {
536 static_assert((-85 / 10 == -8) && (-85 % 10 == -5),
537 "If we port to a platform where ((-85 / 10 == -9) && (-85 % 10 == 5)), we'll have to change "
538 "this depending on the platform");
539 --val;
540 }
541 }
542 else if (scale < 0)
543 {
544 do
545 {
546 if ((val > maxLimit / 10) || (val < minLimit / 10))
547 throwNumericOutOfRange();
548
549 val *= 10;
550
551 if (val > maxLimit || val < minLimit)
552 throwNumericOutOfRange();
553 } while (++scale);
554 }
555 }
556
557 template <typename T>
558 T conversionEpsilon()
559 {
560 if constexpr (std::is_same_v<T, float>)
561 return static_cast<T>(1e-5f);
562 else if constexpr (std::is_same_v<T, double>)
563 return static_cast<T>(1e-14);
564#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
565 else if constexpr (std::same_as<T, BoostDecFloat16> || std::same_as<T, BoostDecFloat34>)
566 {
567 const auto epsilon = std::numeric_limits<T>::epsilon();
568 return static_cast<T>(epsilon * static_cast<T>(10));
569 }
570#endif
571 else
572 return std::numeric_limits<T>::epsilon();
573 }
574
575 private:
576 Client& client;
577 StatusWrapper* statusWrapper;
578 };
579} // namespace fbcpp::impl
580
581
582#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