Оптимальная функция Base-10 только itoa()?

В 20-летнем программировании на C я использовал базу, отличную от 10, поэтому, когда я обнаружил, что мой надежный MSVC _itoa() отсутствует в другой среде, я решил написать тот, который содержит только базу 10, и ставит аргумент буфера назначения, указывающий на хранилище, возвращаемое функцией, слева, а не справа, как и все строковые функции в стандартной библиотеке C. Я считаю, что этот код также является потокобезопасным.

Есть ли более быстрый способ сделать это?

Я также хотел спросить о правильности, но я считаю, что включенный тестовый код доказывает, что он работает даже для конкретного случая LONG_MIN, который является (-1 * LONG_MAX) -1, что вызвало сбой в коде до Я изменил тактику, отметил знак и затем скопировал подписанный int в неподписанный int. Затем я выполнил всю основную работу в функции без знака ints, которая с радостью выполнялась в 75% случаев.

char * _i32toa(char *const rtn, int32_t i)    {
    if (NULL == rtn) return NULL;

    // declare local buffer, and write to it back-to-front
    char buff[12];
    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = buff + sizeof(buff)-1;
    *p-- = 0;    // nul-terminate buffer

    // deal with negative numbers while using an unsigned integer
    if (i < 0)    {
        minus_sign = '-';
        ui = (uint32_t)((int)-1 * (int)i);
    }    else    {
        ui = i;
    }

    // core code here...
    while (ui > 9) {
        ut = ui;
        ui /= 10;
        *p-- = (ut - (ui * 10)) + '0';
    }
    *p = ui + '0';

    if ('-' == minus_sign) *--p = minus_sign;

    // knowing how much storage we needed, copy chars from buff to rtn...
    memcpy(rtn, p, sizeof(buff)-(p - buff));

    return rtn;
}

// ------------------------------------------------------------------------------------------------
#define LOOP_KNT (SHRT_MAX * 1024)
// ------------------------------------------------------------------------------------------------
int main(void)    {
    time_t start = clock();

    int32_t t = 123456, i;
    char *buff = (char *)malloc(256);

    for (i = (SHRT_MIN *1024); i < LOOP_KNT; i++)    {
        _i32toa(buff, i);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    start = clock();
    for (i = (SHRT_MIN * 1024); i < LOOP_KNT; i++)    {
        _itoa(i, buff, 10);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    start = clock();
    for (i = (SHRT_MIN * 1024); i < LOOP_KNT; i++)    {
        ___itoa(i, buff, 10);
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    printf("\nString from integer %i is %s\n", t, _i32toa(buff, t));
    printf("\nString from integer %i is %s\n", -0, _i32toa(buff, -0));
    printf("\nString from integer %i is %s\n", -1, _i32toa(buff, -1));
    printf("\nString from integer %i is %s\n", LONG_MIN, _i32toa(buff, LONG_MIN));

    start = clock();
    for (int i = LONG_MIN; i < LONG_MAX; i++) {
        if (i != atoi(_i32toa(buff, (int32_t)i))) {
            printf("\nError for %i", i);
        }
        if (!i) printf("\nAt zero");
    }
    printf("\nElapsed time was %f milliseconds", (double)clock() - (double)(start));

    getchar();
    return 0;
}

Производительность составляет 2-4X от не-части-C-стандарта _itoa() в Visual Studio 2013 и 10-15X от sprintf().

Этот подход является несколько новым и зависит от знания требуемого размера буфера для завершенной строки - проблемы, которую выполняет функция, выделяющая его собственный буфер строки, buff [], который делает его поточно-безопасным одновременно.

Зная, где конец буфера позволяет писать символы строки с обратной стороны вперед, решая проблему обратного порядка. Вызывающей функции не нужно каким-либо образом готовить * rtn, так как рабочая строка, которая возвращает memcpy() ed в * ptr, уже завершена нулем.

TVMIA за ваши отзывы. Отсутствие хорошей функции _atoi() является достаточно стойкой проблемой, которая заслуживает хорошего решения. Позвольте сделать один.

PS: В моем ящике i7 Hazwell с 64-битной версией MSVS С++ с полной оптимизацией полный цикл от LONG_MIN до LONG_MAX составляет в среднем 116 тактов за конверсию, для кругового и всего 28 часов для _itoa(). Это более 725 мегабайт в секунду строки - если сравнивать с кодом Ben Voigt. Я думаю, что выиграл Бен!

Ответы

Ответ 1

Вы можете удалить memcpy, записав непосредственно в область памяти вызывающего абонента.
Вы должны передать вызывающему абоненту размер буфера.

Другим узким местом является разделение, но я не вижу, как обойти это.

Изменить 1: правильная инициализация указателя буфера

char * _i32toa(char *const rtn, unsigned int buff_size, int32_t i)  
{
    if (NULL == rtn) return NULL;

    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = rtn + buff_size - 1;
    // As before, without memcpy.
    return rtn;
}

Ответ 2

Избавьтесь от массива auto char и попросите их передать размер, чтобы вы могли проверить переполнение.

#define I32TOA( buff, val ) _i32toa( (buff), sizeof(buff), (val) )

char * _i32toa(char *const rtn, size_t size, int32_t i)    {
    if (NULL == rtn) return NULL;

    uint32_t  ut, ui;
    char minus_sign=0;
    char *p = rtn + size-1;
    *p-- = 0;    // nul-terminate buffer
    assert( p >= rtn );

    if (i < 0)    {
        minus_sign = '-';
        ui = (uint32_t)((int)-1 * (int)i);
    }    else    {
        ui = i;
    }

    while (ui > 9) {
        ut = ui;
        ui /= 10;
        *p-- = (ut - (ui * 10)) + 48;
        assert( p >= rtn );
    }
    *p = ui + 48;

    if ('-' == minus_sign) {
        *--p = minus_sign;
        assert( p >= rtn );
    }

    return p;
}