• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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