C ++ легкая встроенная реализация itoa и fptoa - PullRequest
0 голосов
/ 04 мая 2020

Backstory

Я занимаюсь разработкой библиотеки TFT LCD (которую я открою с открытым исходным кодом, как только я закончу sh), и пришел к выводу, что мне нужны облегченные функции itoa и fptoa для преобразования целых значений и значений с плавающей запятой в строки. После прочтения нескольких сообщений здесь я не нашел конкретной реализации здесь, поэтому я решил опубликовать ее сам.

Ограничение

  1. нет динамических c выделений
  2. c ++ 11 совместим.

Проблема

Первый подход состоял в том, чтобы посмотреть, есть ли решение в стандартной библиотеке и, к сожалению, itoa не является частью стандарт и fptoa не существует. Тем не менее, есть еще спринт, верно? Проблема заключается в накладных расходах для всех внутренних проверок, которые он выполняет, и другие методы c ++ не намного привлекательнее:

  1. std :: string - heavy + dynamici c использование памяти (если число слишком большой для оптимизации коротких строк).
  2. Потоки - слишком медленные и тяжелые.

1 Ответ

0 голосов
/ 04 мая 2020

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

...