fb-cpp 0.0.1
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 <concepts>
35#include <cstddef>
36#include <cstdlib>
37#include <format>
38#include <limits>
39#include <string>
40#include <string_view>
41#include <type_traits>
42#include <utility>
43
44
45namespace fbcpp::impl
46{
47 template <typename T>
48 struct NumberTypePriority
49 {
50 static constexpr int value = 0;
51 };
52
53#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
54 template <>
55 struct NumberTypePriority<BoostDecFloat34>
56 {
57 static constexpr int value = 8;
58 };
59
60 template <>
61 struct NumberTypePriority<BoostDecFloat16>
62 {
63 static constexpr int value = 7;
64 };
65#endif
66
67 template <>
68 struct NumberTypePriority<double>
69 {
70 static constexpr int value = 6;
71 };
72
73 template <>
74 struct NumberTypePriority<float>
75 {
76 static constexpr int value = 5;
77 };
78
79#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
80 template <>
81 struct NumberTypePriority<BoostInt128>
82 {
83 static constexpr int value = 4;
84 };
85#endif
86
87 template <>
88 struct NumberTypePriority<std::int64_t>
89 {
90 static constexpr int value = 3;
91 };
92
93 template <>
94 struct NumberTypePriority<std::int32_t>
95 {
96 static constexpr int value = 2;
97 };
98
99 template <>
100 struct NumberTypePriority<std::int16_t>
101 {
102 static constexpr int value = 1;
103 };
104
105 template <typename T>
106 inline constexpr int NumberTypePriorityValue =
107 NumberTypePriority<std::remove_cv_t<std::remove_reference_t<T>>>::value;
108
109 template <typename T1, typename T2>
110 using GreaterNumberType = std::conditional_t<(NumberTypePriorityValue<T1> >= NumberTypePriorityValue<T2>), T1, T2>;
111
112 template <typename T>
113 inline constexpr bool IsFloatingNumber = std::is_floating_point_v<T>
114#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
115 || std::same_as<T, BoostDecFloat16> || std::same_as<T, BoostDecFloat34>
116#endif
117 ;
118
119 template <typename T>
120 concept FloatingNumber = IsFloatingNumber<T>;
121
122 template <typename T>
123 concept IntegralNumber = std::is_integral_v<T>
124#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
125 || std::same_as<T, BoostInt128>
126#endif
127 ;
128
129 template <typename T>
130 struct MakeUnsigned
131 {
132 using type = std::make_unsigned_t<T>;
133 };
134
135#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
136 template <>
137 struct MakeUnsigned<BoostInt128>
138 {
139 using type = boost::multiprecision::uint128_t;
140 };
141#endif
142
143 template <typename T>
144 using MakeUnsignedType = typename MakeUnsigned<T>::type;
145
146 class NumericConverter final
147 {
148 public:
149 explicit NumericConverter(Client& client, StatusWrapper* statusWrapper)
150 : client{client},
151 statusWrapper{statusWrapper}
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 ComputeType value{from};
208 const ComputeType eps = conversionEpsilon<ComputeType>();
209
210 if (toScale > 0)
211 value /= powerOfTen(toScale);
212 else if (toScale < 0)
213 value *= powerOfTen(-toScale);
214
215 if (value > 0)
216 value += 0.5f + eps;
217 else
218 value -= 0.5f + eps;
219
220 static const auto minLimit = static_cast<ComputeType>(std::numeric_limits<To>::min());
221 static const auto maxLimit = static_cast<ComputeType>(std::numeric_limits<To>::max());
222
223 if (value < minLimit)
224 {
225 if (value > minLimit - 1.0f)
226 return std::numeric_limits<To>::min();
227 throwNumericOutOfRange();
228 }
229
230 if (value > maxLimit)
231 {
232 if (value < maxLimit + 1.0f)
233 return std::numeric_limits<To>::max();
234 throwNumericOutOfRange();
235 }
236
237 return static_cast<To>(value);
238 }
239
240 template <FloatingNumber To, typename From>
241 To numberToNumber(const ScaledNumber<From>& from, int toScale = 0)
242 {
243 assert(toScale == 0);
244
245 using ComputeType = GreaterNumberType<double, To>;
246
247 ComputeType value = static_cast<ComputeType>(from.value); // FIXME: decfloat
248
249 if (from.scale != 0)
250 {
251 if (std::abs(from.scale) > std::numeric_limits<To>::max_exponent10)
252 throwNumericOutOfRange();
253
254 if (from.scale > 0)
255 value *= powerOfTen(from.scale);
256 else if (from.scale < 0)
257 value /= powerOfTen(-from.scale);
258 }
259
260 return static_cast<To>(value);
261 }
262
263 template <FloatingNumber To, FloatingNumber From>
264 To numberToNumber(const From& from, int toScale = 0)
265 {
266 assert(toScale == 0);
267
268 if constexpr (std::is_floating_point_v<From> && !std::is_floating_point_v<To>)
269 return To{std::format("{:.16e}", from)};
270 else
271 return static_cast<To>(from);
272 }
273
274 template <IntegralNumber From>
275 std::string numberToString(const ScaledNumber<From>& from)
276 {
277 char buffer[64];
278
279 const bool isNegative = from.value < 0;
280 const bool isMinLimit = from.value == std::numeric_limits<decltype(from.value)>::min();
281
282 using UnsignedType = MakeUnsignedType<decltype(from.value)>;
283
284 auto unsignedValue = isMinLimit ? static_cast<UnsignedType>(-(from.value + 1)) + 1
285 : static_cast<UnsignedType>(isNegative ? -from.value : from.value);
286
287 int digitCount = 0;
288
289 do
290 {
291 buffer[digitCount++] = static_cast<char>((unsignedValue % 10) + '0');
292 unsignedValue /= 10;
293 } while (unsignedValue > 0);
294
295 std::string result;
296
297 if (isNegative)
298 result += '-';
299
300 if (from.scale >= 0)
301 {
302 for (int i = digitCount - 1; i >= 0; --i)
303 result += buffer[i];
304
305 result.append(static_cast<std::string::size_type>(from.scale), '0');
306 }
307 else
308 {
309 const int decimalPlaces = -from.scale;
310
311 if (decimalPlaces >= static_cast<int>(digitCount))
312 {
313 result += "0.";
314 const int leadingZeros = decimalPlaces - digitCount;
315 result.append(static_cast<std::string::size_type>(leadingZeros), '0');
316
317 for (int i = digitCount - 1; i >= 0; --i)
318 result += buffer[i];
319 }
320 else
321 {
322 for (int i = digitCount - 1; i >= decimalPlaces; --i)
323 result += buffer[i];
324
325 result += '.';
326
327 for (int i = decimalPlaces - 1; i >= 0; --i)
328 result += buffer[i];
329 }
330 }
331
332 return result;
333 }
334
335 template <FloatingNumber From>
336 std::string numberToString(const From& from)
337 {
338 if constexpr (std::is_floating_point_v<From>)
339 return std::to_string(from);
340 else
341 return from.str();
342 }
343
344 std::string opaqueInt128ToString(const OpaqueInt128& opaqueInt128, int scale)
345 {
346 const auto int128Util = client.getUtil()->getInt128(statusWrapper);
347 char buffer[fb::IInt128::STRING_SIZE + 1];
348 int128Util->toString(statusWrapper, &opaqueInt128, scale, static_cast<unsigned>(sizeof(buffer)), buffer);
349 return buffer;
350 }
351
352 std::string opaqueDecFloat16ToString(const OpaqueDecFloat16& opaqueDecFloat16)
353 {
354 const auto decFloat16Util = client.getDecFloat16Util(statusWrapper);
355 char buffer[fb::IDecFloat16::STRING_SIZE + 1];
356 decFloat16Util->toString(statusWrapper, &opaqueDecFloat16, static_cast<unsigned>(sizeof(buffer)), buffer);
357 return buffer;
358 }
359
360 std::string opaqueDecFloat34ToString(const OpaqueDecFloat34& opaqueDecFloat34)
361 {
362 const auto decFloat34Util = client.getDecFloat34Util(statusWrapper);
363 char buffer[fb::IDecFloat34::STRING_SIZE + 1];
364 decFloat34Util->toString(statusWrapper, &opaqueDecFloat34, static_cast<unsigned>(sizeof(buffer)), buffer);
365 return buffer;
366 }
367
368#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
369 OpaqueInt128 boostInt128ToOpaqueInt128(const BoostInt128& boostInt128)
370 {
371 const boost::multiprecision::uint128_t boostUInt128{boostInt128};
372
373 OpaqueInt128 opaqueInt128;
374 opaqueInt128.fb_data[0] = static_cast<std::uint64_t>(boostUInt128 & 0xFFFFFFFFFFFFFFFFULL);
375 opaqueInt128.fb_data[1] = static_cast<std::uint64_t>(boostUInt128 >> 64);
376
377 return opaqueInt128;
378 }
379
380 BoostInt128 opaqueInt128ToBoostInt128(const OpaqueInt128& opaqueInt128)
381 {
382 const auto high = static_cast<std::int64_t>(opaqueInt128.fb_data[1]);
383 BoostInt128 boostInt128 = static_cast<BoostInt128>(high);
384 boostInt128 <<= 64;
385 boostInt128 += static_cast<BoostInt128>(opaqueInt128.fb_data[0]);
386 return boostInt128;
387 }
388
389 OpaqueDecFloat16 boostDecFloat16ToOpaqueDecFloat16(const BoostDecFloat16& boostDecFloat16)
390 {
391 const auto decFloat16Util = client.getDecFloat16Util(statusWrapper);
392 OpaqueDecFloat16 opaqueDecFloat16;
393 decFloat16Util->fromString(statusWrapper, boostDecFloat16.str().c_str(), &opaqueDecFloat16);
394 return opaqueDecFloat16;
395 }
396
397 BoostDecFloat16 opaqueDecFloat16ToBoostDecFloat16(const OpaqueDecFloat16& opaqueDecFloat16)
398 {
399 return BoostDecFloat16{opaqueDecFloat16ToString(opaqueDecFloat16)};
400 }
401
402 OpaqueDecFloat34 boostDecFloat34ToOpaqueDecFloat34(const BoostDecFloat34& boostDecFloat34)
403 {
404 const auto decFloat34Util = client.getDecFloat34Util(statusWrapper);
405 OpaqueDecFloat34 opaqueDecFloat34;
406 decFloat34Util->fromString(statusWrapper, boostDecFloat34.str().c_str(), &opaqueDecFloat34);
407 return opaqueDecFloat34;
408 }
409
410 BoostDecFloat34 opaqueDecFloat34ToBoostDecFloat34(const OpaqueDecFloat34& opaqueDecFloat34)
411 {
412 return BoostDecFloat34{opaqueDecFloat34ToString(opaqueDecFloat34)};
413 }
414#endif
415
416 // FIXME: move
417 std::byte stringToBoolean(std::string_view value)
418 {
419 auto trimmed = value;
420
421 while (!trimmed.empty() && std::isspace(static_cast<unsigned char>(trimmed.front())))
422 trimmed.remove_prefix(1);
423
424 while (!trimmed.empty() && std::isspace(static_cast<unsigned char>(trimmed.back())))
425 trimmed.remove_suffix(1);
426
427 std::string normalized{trimmed};
428 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
429 [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
430
431 if (normalized == "true")
432 return std::byte{1};
433 else if (normalized == "false")
434 return std::byte{0};
435
436 throwConversionErrorFromString(std::string{value});
437 }
438
439 private:
440 double powerOfTen(int scale) noexcept // FIXME: only for double?
441 {
442 /* FIXME:
443 BoostDecFloat34 powerOfTenDecInternal(unsigned scale)
444 {
445 return boost::multiprecision::pow(BoostDecFloat34{10}, scale);
446 }
447 */
448
449 static constexpr double UPPER_PART[] = {
450 1.e000,
451 1.e032,
452 1.e064,
453 1.e096,
454 1.e128,
455 1.e160,
456 1.e192,
457 1.e224,
458 1.e256,
459 1.e288,
460 };
461
462 static constexpr double LOWER_PART[] = {
463 1.e00,
464 1.e01,
465 1.e02,
466 1.e03,
467 1.e04,
468 1.e05,
469 1.e06,
470 1.e07,
471 1.e08,
472 1.e09,
473 1.e10,
474 1.e11,
475 1.e12,
476 1.e13,
477 1.e14,
478 1.e15,
479 1.e16,
480 1.e17,
481 1.e18,
482 1.e19,
483 1.e20,
484 1.e21,
485 1.e22,
486 1.e23,
487 1.e24,
488 1.e25,
489 1.e26,
490 1.e27,
491 1.e28,
492 1.e29,
493 1.e30,
494 1.e31,
495 };
496
497 assert((scale >= 0) && (scale < 320));
498
499 const auto upper = UPPER_PART[scale >> 5];
500 const auto lower = LOWER_PART[scale & 0x1F];
501
502 return upper * lower;
503 }
504
505 template <typename T>
506 void adjustScale(T& val, int scale, const T minLimit, const T maxLimit)
507 {
508 if (scale > 0)
509 {
510 int fraction = 0;
511
512 do
513 {
514 if (scale == 1)
515 fraction = int(val % 10);
516 val /= 10;
517 } while (--scale);
518
519 if (fraction > 4)
520 ++val;
521 else if (fraction < -4)
522 {
523 static_assert((-85 / 10 == -8) && (-85 % 10 == -5),
524 "If we port to a platform where ((-85 / 10 == -9) && (-85 % 10 == 5)), we'll have to change "
525 "this depending on the platform");
526 --val;
527 }
528 }
529 else if (scale < 0)
530 {
531 do
532 {
533 if ((val > maxLimit / 10) || (val < minLimit / 10))
534 throwNumericOutOfRange();
535
536 val *= 10;
537
538 if (val > maxLimit || val < minLimit)
539 throwNumericOutOfRange();
540 } while (++scale);
541 }
542 }
543
544 template <typename T>
545 T conversionEpsilon()
546 {
547 if constexpr (std::is_same_v<T, float>)
548 return static_cast<T>(1e-5f);
549 else if constexpr (std::is_same_v<T, double>)
550 return static_cast<T>(1e-14);
551#if FB_CPP_USE_BOOST_MULTIPRECISION != 0
552 else if constexpr (std::same_as<T, BoostDecFloat16> || std::same_as<T, BoostDecFloat34>)
553 {
554 const auto epsilon = std::numeric_limits<T>::epsilon();
555 return static_cast<T>(epsilon * static_cast<T>(10));
556 }
557#endif
558 else
559 return std::numeric_limits<T>::epsilon();
560 }
561
562 private:
563 Client& client;
564 StatusWrapper* statusWrapper;
565 };
566} // namespace fbcpp::impl
567
568
569#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