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