itoa solution
#include<algorithm>
#include<type_traits>
#include<cstdint>
static constexpr char digits[]{ "0123456789ABCDEF" };
enum class Base : uint8_t
{
BIN = 2,
OCT = 8,
DEC = 10,
HEX = 16
};
template<typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
char* itoa(T number, char* buf, const Base base) noexcept
{
if (base < Base::BIN || base > Base::HEX) {
*buf = '\0';
return buf;
}
bool negative{ false };
if (number < 0 && base == Base::DEC) {
number = -number;
negative = true;
}
typename std::make_unsigned<T>::type unsigned_number = number;
uint8_t index{ 0 };
do {
buf[index++] = digits[unsigned_number % static_cast<decltype(unsigned_number)>(base)];
unsigned_number /= static_cast<decltype(unsigned_number)>(base);
} while (unsigned_number);
if (negative) {
buf[index++] = '-';
}
std::reverse(buf, buf + index);
buf[index] = '\0';
return &buf[index];
}
ftoa solution
Это было немного сложнее, так как мне пришлось создать тип, который гарантирует, что значение точности будет в границах:
#include<cfloat>
#include<cstdint>
template<uint8_t v, uint8_t min, uint8_t max>
struct BoundIntegral
{
static_assert(min < max, "min bound must be lower than max");
static_assert(v >= min && v <= max, "value out of bounds");
static constexpr uint8_t value{ v };
};
constexpr uint8_t MIN_PRECISION{ 1 };
template<typename T, uint8_t prec> struct Precision {};
template<uint8_t prec>
struct Precision<long double, prec> : BoundIntegral<prec, MIN_PRECISION, LDBL_DIG> {};
template<uint8_t prec>
struct Precision<double, prec> : BoundIntegral<prec, MIN_PRECISION, DBL_DIG> {};
template<uint8_t prec>
struct Precision<float, prec> : BoundIntegral<prec, MIN_PRECISION, FLT_DIG> {};
Следующей была фактическая реализация fptoa:
#include<cstdint>
#include<cmath>
#include<type_traits>
constexpr uint8_t ERR_LEN{ 3 };
template<typename T, uint8_t prec, typename = typename std::enable_if<std::is_floating_point<T>::value>::type>
char* fptoa(T number, char* buf) noexcept
{
auto memcpy = [](char* dest, const char* const src, uint8_t length) noexcept {
for (uint8_t index{}; index < length; ++index) {
dest[index] = src[index];
}
return &dest[length];
};
if (std::isnan(number)) {
buf = memcpy(buf, "NAN", ERR_LEN);
}
else if (std::isinf(number)) {
buf = memcpy(buf, "INF", ERR_LEN);
}
else if (number > INT32_MAX) {
buf = memcpy(buf, "OVF", ERR_LEN);
}
else if (number < INT32_MIN) {
buf = memcpy(buf, "OVF", ERR_LEN);
}
else {
T rounding = 0.5 / std::pow(10.0, Precision<T, prec>::value);
if (number < 0.0) {
number -= rounding;
}
else {
number += rounding;
}
int32_t integral_part = static_cast<int32_t>(number);
buf = itoa(integral_part, buf, Base::DEC);
*buf++ = '.';
T fractional_part = std::abs(number - static_cast<T>(integral_part));
uint8_t precision = Precision<T, prec>::value;
while (precision--) {
fractional_part *= 10;
*buf++ = digits[static_cast<uint8_t>(fractional_part)];
fractional_part -= static_cast<uint8_t>(fractional_part);
}
}
*buf = '\0';
return buf;
}
Вариант использования
Для самого варианта использования я создал шаблон утилиты, который может определить размер буфера, который мне понадобится:
#include<climits>
#include<type_traits>
#include<cstdint>
template<typename T>
struct bits_constant : std::integral_constant<std::size_t, sizeof(T) * CHAR_BIT> {};
И используйте его следующим образом:
#include<cstdio>
int main()
{
char fpbuf[bits_constant<double>::value + 1];
double d{ 3.123 };
fptoa<double, 3>(3.123, fpbuf); //convert double value with 3 digits precision
std::printf("%s", fpbuf);
char ibuf[bits_constant<int32_t>::value + 1];
int32_t i{-255};
itoa(i, ibuf, Base::DEC);
return 0;
}
Сводка
Не стесняйтесь использовать код в своем проекте. И я был бы признателен, если бы вы могли предложить улучшения в коде, поскольку цель этого поста - помочь другим использовать портативное решение для преобразования целочисленных значений и значений с плавающей запятой в строки.
Ссылка Godbolt для вывода сборки: https://godbolt.org/z/PGiw7m