1 /**
2 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <cstdlib>
17 #include "dtoa_helper.h"
18 #include "ets_intrinsics_helpers.h"
19 #include "include/mem/panda_string.h"
20 #include "types/ets_field.h"
21 #include "types/ets_string.h"
22
23 namespace ark::ets::intrinsics::helpers {
24
25 // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
26 #define RETURN_IF_CONVERSION_END(p, end, result) \
27 if ((p) == (end)) { \
28 return (result); \
29 }
30
31 namespace parse_helpers {
32
33 template <typename ResultType>
34 struct ParseResult {
35 ResultType value;
36 uint8_t *pointerPosition = nullptr;
37 bool isSuccess = false;
38 };
39
ParseExponent(const uint8_t * start,const uint8_t * end,const uint8_t radix,const uint32_t flags)40 ParseResult<int32_t> ParseExponent(const uint8_t *start, const uint8_t *end, const uint8_t radix, const uint32_t flags)
41 {
42 constexpr int32_t MAX_EXPONENT = INT32_MAX / 2;
43 auto p = const_cast<uint8_t *>(start);
44 if (radix == 0) {
45 return {0, p, false};
46 }
47
48 char exponentSign = '+';
49 int32_t additionalExponent = 0;
50 bool undefinedExponent = false;
51 ++p;
52 if (p == end) {
53 undefinedExponent = true;
54 }
55
56 if (!undefinedExponent && (*p == '+' || *p == '-')) {
57 exponentSign = static_cast<char>(*p);
58 ++p;
59 if (p == end) {
60 undefinedExponent = true;
61 }
62 }
63 if (!undefinedExponent) {
64 uint8_t digit;
65 while ((digit = ToDigit(*p)) < radix) {
66 if (additionalExponent > MAX_EXPONENT / radix) {
67 additionalExponent = MAX_EXPONENT;
68 } else {
69 additionalExponent = additionalExponent * static_cast<int32_t>(radix) + static_cast<int32_t>(digit);
70 }
71 if (++p == end) {
72 break;
73 }
74 }
75 } else if ((flags & flags::ERROR_IN_EXPONENT_IS_NAN) != 0) {
76 return {0, p, false};
77 }
78 if (exponentSign == '-') {
79 return {-additionalExponent, p, true};
80 }
81 return {additionalExponent, p, true};
82 }
83
84 } // namespace parse_helpers
85
86 // CC-OFFNXT(G.FUN.01-CPP,huge_cyclomatic_complexity[C++],huge_method[C++]) solid logic
87 // CC-OFFNXT(huge_cca_cyclomatic_complexity[C++]) solid logic
StringToDouble(const uint8_t * start,const uint8_t * end,uint8_t radix,uint32_t flags)88 double StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, uint32_t flags)
89 {
90 // 1. skip space and line terminal
91 if (IsEmptyString(start, end)) {
92 if ((flags & flags::EMPTY_IS_ZERO) != 0) {
93 return 0.0;
94 }
95 return NAN_VALUE;
96 }
97
98 radix = 0;
99 auto p = const_cast<uint8_t *>(start);
100
101 GotoNonspace(&p, end);
102
103 // 2. get number sign
104 Sign sign = Sign::NONE;
105 if (*p == '+') {
106 RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
107 sign = Sign::POS;
108 } else if (*p == '-') {
109 RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
110 sign = Sign::NEG;
111 }
112 bool ignoreTrailing = (flags & flags::IGNORE_TRAILING) != 0;
113
114 // 3. judge Infinity
115 static const char INF[] = "Infinity"; // NOLINT(modernize-avoid-c-arrays)
116 if (*p == INF[0]) {
117 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
118 for (const char *i = &INF[1]; *i != '\0'; ++i) {
119 if (++p == end || *p != *i) {
120 return NAN_VALUE;
121 }
122 }
123 ++p;
124 if (!ignoreTrailing && GotoNonspace(&p, end)) {
125 return NAN_VALUE;
126 }
127 return sign == Sign::NEG ? -POSITIVE_INFINITY : POSITIVE_INFINITY;
128 }
129
130 // 4. get number radix
131 bool leadingZero = false;
132 bool prefixRadix = false;
133 if (*p == '0' && radix == 0) {
134 RETURN_IF_CONVERSION_END(++p, end, SignedZero(sign));
135 if (*p == 'x' || *p == 'X') {
136 if ((flags & flags::ALLOW_HEX) == 0) {
137 return ignoreTrailing ? SignedZero(sign) : NAN_VALUE;
138 }
139 RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
140 if (sign != Sign::NONE) {
141 return NAN_VALUE;
142 }
143 prefixRadix = true;
144 radix = HEXADECIMAL;
145 } else if (*p == 'o' || *p == 'O') {
146 if ((flags & flags::ALLOW_OCTAL) == 0) {
147 return ignoreTrailing ? SignedZero(sign) : NAN_VALUE;
148 }
149 RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
150 if (sign != Sign::NONE) {
151 return NAN_VALUE;
152 }
153 prefixRadix = true;
154 radix = OCTAL;
155 } else if (*p == 'b' || *p == 'B') {
156 if ((flags & flags::ALLOW_BINARY) == 0) {
157 return ignoreTrailing ? SignedZero(sign) : NAN_VALUE;
158 }
159 RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
160 if (sign != Sign::NONE) {
161 return NAN_VALUE;
162 }
163 prefixRadix = true;
164 radix = BINARY;
165 } else {
166 leadingZero = true;
167 }
168 }
169
170 if (radix == 0) {
171 radix = DECIMAL;
172 }
173 auto pStart = p;
174 // 5. skip leading '0'
175 while (*p == '0') {
176 RETURN_IF_CONVERSION_END(++p, end, SignedZero(sign));
177 leadingZero = true;
178 }
179 // 6. parse to number
180 uint64_t intNumber = 0;
181 uint64_t numberMax = (UINT64_MAX - (radix - 1)) / radix;
182 int digits = 0;
183 int exponent = 0;
184 do {
185 uint8_t c = ToDigit(*p);
186 if (c >= radix) {
187 if (!prefixRadix || ignoreTrailing || (pStart != p && !GotoNonspace(&p, end))) {
188 break;
189 }
190 // "0b" "0x1.2" "0b1e2" ...
191 return NAN_VALUE;
192 }
193 ++digits;
194 if (intNumber < numberMax) {
195 intNumber = intNumber * radix + c;
196 } else {
197 ++exponent;
198 }
199 } while (++p != end);
200
201 auto number = static_cast<double>(intNumber);
202 if (sign == Sign::NEG) {
203 if (number == 0) {
204 number = -0.0;
205 } else {
206 number = -number;
207 }
208 }
209
210 // 7. deal with other radix except DECIMAL
211 if (p == end || radix != DECIMAL) {
212 if ((digits == 0 && !leadingZero) || (p != end && !ignoreTrailing && GotoNonspace(&p, end))) {
213 // no digits there, like "0x", "0xh", or error trailing of "0x3q"
214 return NAN_VALUE;
215 }
216 return number * std::pow(radix, exponent);
217 }
218
219 // 8. parse '.'
220 if (*p == '.') {
221 RETURN_IF_CONVERSION_END(++p, end, (digits > 0) ? (number * std::pow(radix, exponent)) : NAN_VALUE);
222 while (ToDigit(*p) < radix) {
223 --exponent;
224 ++digits;
225 if (++p == end) {
226 break;
227 }
228 }
229 }
230 if (digits == 0 && !leadingZero) {
231 // no digits there, like ".", "sss", or ".e1"
232 return NAN_VALUE;
233 }
234 auto pEnd = p;
235
236 // 9. parse 'e/E' with '+/-'
237 if (radix == DECIMAL && (p != end && (*p == 'e' || *p == 'E'))) {
238 auto parseExponentResult = parse_helpers::ParseExponent(p, end, radix, flags);
239 if (!parseExponentResult.isSuccess) {
240 return NAN_VALUE;
241 }
242 p = parseExponentResult.pointerPosition;
243 exponent += parseExponentResult.value;
244 }
245 if (!ignoreTrailing && GotoNonspace(&p, end)) {
246 return NAN_VALUE;
247 }
248
249 // 10. build StringNumericLiteral string
250 PandaString buffer;
251 if (sign == Sign::NEG) {
252 buffer += "-";
253 }
254 for (uint8_t *i = pStart; i < pEnd; ++i) { // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
255 if (*i != static_cast<uint8_t>('.')) {
256 buffer += *i;
257 }
258 }
259
260 // 11. convert none-prefix radix string
261 return Strtod(buffer.c_str(), exponent, radix);
262 }
263
264 // CC-OFFNXT(G.FUN.01-CPP,huge_cyclomatic_complexity[C++],huge_method[C++]) solid logic
265 // CC-OFFNXT(huge_cca_cyclomatic_complexity[C++]) solid logic
StringToDoubleWithRadix(const uint8_t * start,const uint8_t * end,int radix)266 double StringToDoubleWithRadix(const uint8_t *start, const uint8_t *end, int radix)
267 {
268 auto p = const_cast<uint8_t *>(start);
269 // 1. skip space and line terminal
270 if (!GotoNonspace(&p, end)) {
271 return NAN_VALUE;
272 }
273
274 // 2. sign bit
275 bool negative = false;
276 if (*p == '-') {
277 negative = true;
278 RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
279 } else if (*p == '+') {
280 RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
281 }
282 // 3. 0x or 0X
283 bool stripPrefix = true;
284 // 4. If R != 0, then
285 // a. If R < 2 or R > 36, return NaN.
286 // b. If R != 16, let stripPrefix be false.
287 if (radix != 0) {
288 if (radix < MIN_RADIX || radix > MAX_RADIX) {
289 return NAN_VALUE;
290 }
291 if (radix != HEXADECIMAL) {
292 stripPrefix = false;
293 }
294 } else {
295 radix = DECIMAL;
296 }
297 int size = 0;
298 if (stripPrefix) {
299 if (*p == '0') {
300 size++;
301 if (++p != end && (*p == 'x' || *p == 'X')) {
302 RETURN_IF_CONVERSION_END(++p, end, NAN_VALUE);
303 radix = HEXADECIMAL;
304 }
305 }
306 }
307
308 double result = 0;
309 bool isDone = false;
310 do {
311 double part = 0;
312 uint32_t multiplier = 1;
313 for (; p != end; ++p) {
314 // The maximum value to ensure that uint32_t will not overflow
315 const uint32_t maxMultiper = 0xffffffffU / 36U;
316 uint32_t m = multiplier * static_cast<uint32_t>(radix);
317 if (m > maxMultiper) {
318 break;
319 }
320
321 int currentBit = static_cast<int>(ToDigit(*p));
322 if (currentBit >= radix) {
323 isDone = true;
324 break;
325 }
326 size++;
327 part = part * radix + currentBit;
328 multiplier = m;
329 }
330 result = result * multiplier + part;
331 if (isDone) {
332 break;
333 }
334 } while (p != end);
335
336 if (size == 0) {
337 return NAN_VALUE;
338 }
339
340 return negative ? -result : result;
341 }
342
DoubleToExponential(double number,int digit)343 EtsString *DoubleToExponential(double number, int digit)
344 {
345 PandaStringStream ss;
346 if (digit < 0) {
347 ss << std::setiosflags(std::ios::scientific) << std::setprecision(MAX_PRECISION) << number;
348 } else {
349 ss << std::setiosflags(std::ios::scientific) << std::setprecision(digit) << number;
350 }
351 PandaString result = ss.str();
352 size_t found = result.find_last_of('e');
353 if (found != PandaString::npos && found < result.size() - 2U && result[found + 2U] == '0') {
354 result.erase(found + 2U, 1); // 2:offset of e
355 }
356 if (digit < 0) {
357 size_t end = found;
358 while (--found > 0) {
359 if (result[found] != '0') {
360 break;
361 }
362 }
363 if (result[found] == '.') {
364 found--;
365 }
366 if (found < end - 1) {
367 result.erase(found + 1, end - found - 1);
368 }
369 }
370 return EtsString::CreateFromMUtf8(result.c_str());
371 }
372
DoubleToFixed(double number,int digit)373 EtsString *DoubleToFixed(double number, int digit)
374 {
375 PandaStringStream ss;
376 const double scientificNotationThreshold = 1e21;
377 if (std::fabs(number) < scientificNotationThreshold) {
378 ss << std::setiosflags(std::ios::fixed) << std::setprecision(digit) << number;
379 return EtsString::CreateFromMUtf8(ss.str().c_str());
380 }
381
382 ss << std::defaultfloat << std::setprecision(digit) << number;
383 auto str = ss.str();
384 auto ePos = str.find('e');
385 if (ePos == PandaString::npos) {
386 return EtsString::CreateFromMUtf8(str.c_str());
387 }
388
389 auto dotPos = str.find('.');
390 if (dotPos == PandaString::npos || dotPos >= ePos) {
391 return EtsString::CreateFromMUtf8(str.c_str());
392 }
393
394 bool allZero = true;
395 for (size_t i = dotPos + 1; i < ePos; ++i) {
396 if (str[i] != '0') {
397 allZero = false;
398 break;
399 }
400 }
401
402 const size_t digitsAfterDotToKeep = 2;
403 if (allZero) {
404 str.erase(dotPos + digitsAfterDotToKeep, ePos - dotPos - digitsAfterDotToKeep);
405 }
406
407 return EtsString::CreateFromMUtf8(str.c_str());
408 }
409
DoubleToPrecision(double number,int digit)410 EtsString *DoubleToPrecision(double number, int digit)
411 {
412 if (number == 0.0) {
413 return DoubleToFixed(number, digit - 1);
414 }
415 PandaStringStream ss;
416 double positiveNumber = number > 0 ? number : -number;
417 int logDigit = std::floor(log10(positiveNumber));
418 int radixDigit = digit - logDigit - 1;
419 const int minExponentDigit = -6;
420 if (logDigit >= minExponentDigit && logDigit < digit) {
421 return DoubleToFixed(number, std::abs(radixDigit));
422 }
423 return DoubleToExponential(number, digit - 1);
424 }
425
426 // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
427 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
GetBase(FpType d,int digits,int * decpt,Span<char> buf)428 void GetBase(FpType d, int digits, int *decpt, Span<char> buf)
429 {
430 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
431 auto ret = snprintf_s(buf.begin(), buf.size(), buf.size() - 1, "%.*e", digits - 1, d);
432 if (ret == -1) {
433 LOG(FATAL, ETS) << "snprintf_s failed";
434 UNREACHABLE();
435 }
436 char *end = buf.begin() + ret;
437 ASSERT(*end == 0);
438 const size_t positive = (digits > 1) ? 1 : 0;
439 char *ePos = buf.begin() + digits + positive;
440 ASSERT(*ePos == 'e');
441 char *signPos = ePos + 1;
442 char *from = signPos + 1;
443 // exponent
444 if (std::from_chars(from, end, *decpt).ec != std::errc()) {
445 UNREACHABLE();
446 }
447 if (*signPos == '-') {
448 *decpt *= -1;
449 }
450 ++*decpt;
451 }
452
453 constexpr bool USE_GET_BASE_FAST =
454 #ifdef __cpp_lib_to_chars
455 true;
456 #else
457 false;
458 #endif
459
460 template <typename FpType, std::enable_if_t<std::is_floating_point_v<FpType>, bool> = true>
GetBaseFast(FpType d,int * decpt,Span<char> buf)461 int GetBaseFast([[maybe_unused]] FpType d, int *decpt, Span<char> buf)
462 {
463 ASSERT(d >= 0);
464 char *end;
465 #ifdef __cpp_lib_to_chars
466 auto ret = std::to_chars(buf.begin(), buf.end(), d, std::chars_format::scientific);
467 if (ret.ec != std::errc()) {
468 LOG(FATAL, ETS) << "to_chars failed";
469 UNREACHABLE();
470 }
471 end = ret.ptr;
472 #else
473 UNREACHABLE();
474 #endif
475 *end = '\0';
476 // exponent
477 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
478 auto from = std::find(buf.begin(), end, 'e');
479 auto digits = from - buf.begin();
480 if (digits > 1) {
481 digits--;
482 }
483 ASSERT(from != end);
484 if (std::from_chars(from + 2U, end, *decpt).ec != std::errc()) {
485 UNREACHABLE();
486 }
487 if (from[1] == '-') {
488 *decpt *= -1;
489 }
490 ++*decpt;
491 return digits;
492 }
493
494 template <typename FpType>
GetBaseBinarySearch(FpType d,int * decpt,Span<char> buf)495 int GetBaseBinarySearch(FpType d, int *decpt, Span<char> buf)
496 {
497 // find the minimum amount of digits
498 int minDigits = 1;
499 int maxDigits = std::is_same_v<FpType, double> ? DOUBLE_MAX_PRECISION : FLOAT_MAX_PRECISION;
500 int digits;
501
502 while (minDigits < maxDigits) {
503 digits = (minDigits + maxDigits) / 2_I;
504 GetBase(d, digits, decpt, buf);
505
506 bool same = StrToFp<FpType>(buf.begin(), nullptr) == d;
507 if (same) {
508 // no need to keep the trailing zeros
509 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
510 while (digits >= 2_I && buf[digits] == '0') { // 2 means ignore the integer and point
511 digits--;
512 }
513 maxDigits = digits;
514 } else {
515 minDigits = digits + 1;
516 }
517 }
518 digits = maxDigits;
519 GetBase(d, digits, decpt, buf);
520 return digits;
521 }
522
523 template <typename FpType>
RecheckGetMinimumDigits(FpType d,Span<char> buf)524 [[maybe_unused]] static bool RecheckGetMinimumDigits(FpType d, Span<char> buf)
525 {
526 ASSERT(StrToFp<FpType>(buf.begin(), nullptr) == d);
527 std::string str(buf.begin());
528 auto pos = str.find('e');
529 std::copy(str.begin() + pos, str.end(), str.begin() + pos - 1);
530 str[str.size() - 1] = '\0';
531 return StrToFp<FpType>(str.data(), nullptr) != d;
532 }
533
534 // result is written starting with buf[1]
535 template <typename FpType>
GetMinimumDigits(FpType d,int * decpt,Span<char> buf)536 int GetMinimumDigits(FpType d, int *decpt, Span<char> buf)
537 {
538 if (std::is_same_v<FpType, double>) {
539 DtoaHelper helper(buf.begin() + 1);
540 helper.Dtoa(d);
541 *decpt = helper.GetPoint();
542 return helper.GetDigits();
543 }
544 int digits;
545 if constexpr (USE_GET_BASE_FAST) {
546 digits = GetBaseFast(d, decpt, buf);
547 } else {
548 digits = GetBaseBinarySearch(d, decpt, buf);
549 }
550 ASSERT(RecheckGetMinimumDigits(d, buf));
551 ASSERT(digits == 1 || buf[1] == '.');
552 buf[1] = buf[0];
553 return digits;
554 }
555
556 template <typename FpType>
SmallFpToString(FpType number,bool negative,char * buffer)557 char *SmallFpToString(FpType number, bool negative, char *buffer)
558 {
559 using SignedInt = typename ark::helpers::TypeHelperT<sizeof(FpType) * CHAR_BIT, true>;
560 if (negative) {
561 *(buffer++) = '-';
562 }
563 *(buffer++) = '0';
564 *(buffer++) = '.';
565 SignedInt power = TEN;
566 SignedInt s = 0;
567 int maxDigits = std::is_same_v<FpType, double> ? DOUBLE_MAX_PRECISION : FLOAT_MAX_PRECISION;
568 int digits = maxDigits;
569 for (int k = 1; k <= maxDigits; ++k) {
570 s = static_cast<SignedInt>(number * power);
571 if (k == maxDigits || s / static_cast<FpType>(power) == number) { // s * (10 ** -k)
572 digits = k;
573 break;
574 }
575 power *= TEN;
576 }
577 for (int k = digits - 1; k >= 0; k--) {
578 auto digit = s % TEN;
579 s /= TEN;
580 *(buffer + k) = '0' + digit;
581 }
582 return buffer + digits;
583 }
584
585 template <typename FpType>
FpToStringDecimalRadixMainCase(FpType number,bool negative,Span<char> buffer)586 Span<char> FpToStringDecimalRadixMainCase(FpType number, bool negative, Span<char> buffer)
587 {
588 auto bufferStart = buffer.begin() + 2U;
589 ASSERT(number > 0);
590 int n = 0;
591 int k = intrinsics::helpers::GetMinimumDigits(number, &n, buffer.SubSpan(1));
592 auto bufferEnd = bufferStart + k;
593
594 if (0 < n && n <= 21_I) { // NOLINT(readability-magic-numbers)
595 if (k <= n) {
596 // 6. If k ≤ n ≤ 21, return the String consisting of the code units of the k digits of the decimal
597 // representation of s (in order, with no leading zeroes), followed by n−k occurrences of the code unit
598 // 0x0030 (DIGIT ZERO).
599 std::fill_n(bufferEnd, n - k, '0');
600 bufferEnd += n - k;
601 } else {
602 // 7. If 0 < n ≤ 21, return the String consisting of the code units of the most significant n digits of the
603 // decimal representation of s, followed by the code unit 0x002E (FULL STOP), followed by the code units of
604 // the remaining k−n digits of the decimal representation of s.
605 auto fracStart = bufferStart + n;
606 if (memmove_s(fracStart + 1, buffer.end() - (fracStart + 1), fracStart, k - n) != EOK) {
607 UNREACHABLE();
608 }
609 *fracStart = '.';
610 bufferEnd++;
611 }
612 } else if (-6_I < n && n <= 0) { // NOLINT(readability-magic-numbers)
613 // 8. If −6 < n ≤ 0, return the String consisting of the code unit 0x0030 (DIGIT ZERO), followed by the code
614 // unit 0x002E (FULL STOP), followed by −n occurrences of the code unit 0x0030 (DIGIT ZERO), followed by the
615 // code units of the k digits of the decimal representation of s.
616 auto length = -n + 2U;
617 auto fracStart = bufferStart + length;
618 if (memmove_s(fracStart, buffer.end() - fracStart, bufferStart, k) != EOK) {
619 UNREACHABLE();
620 }
621 std::fill_n(bufferStart, length, '0');
622 bufferStart[1] = '.';
623 bufferEnd += length;
624 } else {
625 if (k == 1) {
626 // 9. Otherwise, if k = 1, return the String consisting of the code unit of the single digit of s
627 ASSERT(bufferEnd == bufferStart + 1);
628 } else {
629 *(bufferStart - 1) = *bufferStart;
630 *(bufferStart--) = '.';
631 }
632 // followed by code unit 0x0065 (LATIN SMALL LETTER E), followed by the code unit 0x002B (PLUS SIGN) or the code
633 // unit 0x002D (HYPHEN-MINUS) according to whether n−1 is positive or negative, followed by the code units of
634 // the decimal representation of the integer abs(n−1) (with no leading zeroes).
635 *(bufferEnd++) = 'e';
636 if (n >= 1) {
637 *(bufferEnd++) = '+';
638 }
639 bufferEnd = std::to_chars(bufferEnd, buffer.end(), n - 1).ptr;
640 }
641 if (negative) {
642 *--bufferStart = '-';
643 }
644 return {bufferStart, bufferEnd};
645 }
646 // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
647
648 template char *SmallFpToString<double>(double number, bool negative, char *buffer);
649 template char *SmallFpToString<float>(float number, bool negative, char *buffer);
650
651 template Span<char> FpToStringDecimalRadixMainCase<double>(double number, bool negative, Span<char> buffer);
652 template Span<char> FpToStringDecimalRadixMainCase<float>(float number, bool negative, Span<char> buffer);
653
654 } // namespace ark::ets::intrinsics::helpers
655