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
This one was a bit more tricky as I had to create a type that ensures the precision value will be in bounds:
#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> {};
Next up was the actual fptoa implementation:
#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;
}
Use case
For the use case itself I made a utility template that can deduce the buffer size I will need:
#include<climits>
#include<type_traits>
#include<cstdint>
template<typename T>
struct bits_constant : std::integral_constant<std::size_t, sizeof(T) * CHAR_BIT> {};
And use it as following:
#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;
}
Summary
Feel free to use the code in your project. And I would appreciate it if you could suggest improvements to the code, as the goal of this post is to help others use a portable solution to converting integral and floating point values to strings.
Godbolt link for assembly output: https://godbolt.org/z/PGiw7m