Преобразуем в строку. Часть 2. Числа с фиксированной и плавающей точкой.

Продолжаем преобразовывать всё, что можно в строки.
В предыдущей статье были целые числа, теперь очередь чисел с фиксированной и плавающей точкой.
Все рассмотренные примеры с фиксированной точкой используют формат с 16-ю битами для дробной части и 16-ю битами для целой части, так называемый формат Q16, однако легко могут быть адаптированы для других форматов.

В качестве чисел с плавающей точкой использован 32-х разрядный float.


Числа с фиксированной точкой.


Стандартная библиотека языка Си не предоставляет никаких средств для вывода чисел с фиксированной точкой. Зато есть вывод чисел с плавающей точкой — функции *printf. Можно преобразовать число с фиксированной в число с плавающей точкой и вывести его с помощью, например, sprintf. Первый наивный способ преобразовать фиксированную точку в плавающую, состоит в том, что целочисленное представление числа с фиксированной точкой непосредственно приводится к типу с плавающей точкой, после чего делится на целочисленное представление на единицы в формате с фиксированной точкой:
char *fix_tostr_float_sprintf(uint32_t value, char *buffer)
{
// сколько бит в дробной части
    const int fractBits = 16;
// единица в формате с фиксированной точкой
    const uint32_t fixedOne = 1ul << fractBits;
    double fvalue = (double)value / (double)fixedOne;
    sprintf(buffer, "%g", fvalue);
    return buffer;
}

Тип double, а не float, в качестве типа с плавающей точкой использован потому, что sprintf принимает именно его. Для AVR это не имеет значения, так как float и double там одно и тоже.
Этот метод очень прост и быстр в реализации, к тому-же достаточно надёжен: меньше кода — меньше ошибок. На этом его преимущества заканчиваются и остаются недостатки:
— медленный: AVR 4200 тактов, STM32 — 4234 тактов в среднем;
— использует sprintf и тянет за собой всё её обвязку;
— использует целых две операции с плавающей точкой.

Первый недостаток усугубляется третьим. Попробуем избавится от последнего недостатка, вручную преобразовав число с фиксированной точкой во float. Для этого надо найти в числе с фиксированной точкой самый старший единичный бит и сдвинуть его на место неявного старшего единичного бита числа с плавающей точкой — 23-его бита. После чего этот бит надо обнулить, так как там хранится младший бит экспоненты. Таким образом получится мантисса искомого float-а. Экспонента вычисляется по формуле:
exponent = 127 + «число бит в дробной части» — 9 + «насколько бит сдвинули исходное число»
— 9 здесь нужно потому, что целевой бит, куда нужно сдвинуть старший единичный бит — девятый с конца.
«насколько бит сдвинули исходное число» — положительное, если сдвигали вправо, отрицательное, если влево.
При сдвиге вправо могут потеряться до 8 бит младших дробной части.
char *fix_tostr_float_sprintf2(uint32_t value, char *buffer)
{
// сколько бит в дробной части
    const int fractBits = 16;
// экспонента числа с плавающей точкой
    uint_fast8_t exponent = 127 + fractBits - 9;
    if(value != 0)
    {
        if(value & 0xff000000)
        {
            while(value & 0xff000000)
            {
                value >>= 1;
                exponent ++;
            }
        }
        else
        {
            while((value & 0xff800000) == 0)
            {
                value <<= 1;
                exponent --;
            }
        }
        value &= 0x007fffff;
        value |= (uint32_t)exponent << 23;
    }
    float fvalue = *(float*)&value;
    sprintf(buffer, "%g", (double)fvalue);
    return buffer;
}

Число с фиксированной точкой в этом примере без знаковое, поэтому знак не трогаем.
В результате на AVR такой метод работает за 2298 тактов в среднем — почти в два раза быстрее. На STM32 всё по прежнему «плохо» — 4194 такта — операции с плавающей точкой там не являются узким местом — это sprintf так медленно работает.

Следующий вариант — это преобразовывать целую часть отдельно по одному из методов из предыдущей статьи, а дробную — методом умножения на 10. Для преобразования целой части целесообразно взять самый быстрый метод — деление на 10 сдвигами и сложениями.
char *fix_tostr1(uint32_t value, char *buffer)
{
    const int fractBits = 16;
    const uint32_t fractPartMask = (1ul << fractBits) - 1;
    if(value == 0)
    {
        buffer[0] = '0';
        buffer[1] = 0;
        return buffer;
    }
    uint32_t intPart = value >> fractBits;
    value &= fractPartMask;
    char *ptr = buffer + 5;
// преобразуем целую часть
    char *str_begin = utoa_fast_div(intPart, ptr);
// если есть дробная часть
    if(value != 0)
    {
        *ptr = '.';
        for(uint_fast8_t i=0; i < 6; i++)
        {
            value &= fractPartMask;
            //value *= 10;
            value <<= 1;
            value += value << 2;
            *++ptr = (uint8_t)(value >> fractBits) + '0';
        }
// удаляем завершаюшие нули
        while(ptr[0] == '0') --ptr;
        ptr[1] = 0;
    }
    return str_begin;
}

Получилось неплохо никаких внешних зависимостей и кода не много. На AVR этот метод работает в среднем за 490 тактов, на STM32 — за 148. Результат отличный — быстрее, чем 32-х битное целое преобразуется по методу быстрого деления.

Также для чисел с фиксированной точкой применим метод умножения на 10. Он почти ничем не отличается от аналогичного для целых чисел. Только на этот раз для умножения исходного числа на AVR-ках я применил не тупой цикл со сдвигами и сложениями, честное 64-битное умножение по алгоритму Д. Кнута. Дело в том, что встроенное 64-битное умножение в avr-gcc не оптимизировано и работает неприлично долго порядка 800 тактов. Реализация, приведённая ниже работает за чуть более 100 тактов. Функция mul32hu возвращает старшие 32 бита от произведения, которые получаются в переменной u32[1]. Если нужно полное 64-х битное произведение, то можно вытащить и младшую часть — она находится в переменной u32[0].
static inline uint32_t mul32hu(uint32_t u, uint32_t v)
{
    #if defined(AVR) || defined (__AVR__)
        uint8_t a0 = (uint8_t)(u >> 0);
        uint8_t a1 = (uint8_t)(u >> 8);
        uint8_t a2 = (uint8_t)(u >> 16);
        uint8_t a3 = (uint8_t)(u >> 24);
        uint8_t b0 = (uint8_t)(v >> 0);
        uint8_t b1 = (uint8_t)(v >> 8);
        uint8_t b2 = (uint8_t)(v >> 16);
        uint8_t b3 = (uint8_t)(v >> 24);
        
        union{
            struct{
                uint8_t w0, w1, w2, w3, w4, w5, w6, w7;
            } u8;
            uint32_t u32[2];
        };
        
        u8.w0 = u8.w1 = u8.w2 = u8.w3 = u8.w4 = u8.w5 = u8.w6 = u8.w7 = 0;
        uint8_t k = 0;
        uint16_t t;

        t = a0 * b0 + u8.w0 + k;	u8.w0 = t;	k = t >> 8;
        t = a1 * b0 + u8.w1 + k;	u8.w1 = t;	k = t >> 8;
        t = a2 * b0 + u8.w2 + k;	u8.w2 = t;	k = t >> 8;
        t = a3 * b0 + u8.w3 + k;	u8.w3 = t;	k = t >> 8;
        u8.w4 = k;	k = 0;
        t = a0 * b1 + u8.w1 + k;	u8.w1 = t;	k = t >> 8;
        t = a1 * b1 + u8.w2 + k;	u8.w2 = t;	k = t >> 8;
        t = a2 * b1 + u8.w3 + k;	u8.w3 = t;	k = t >> 8;
        t = a3 * b1 + u8.w4 + k;	u8.w4 = t;	k = t >> 8;
        u8.w5 = k;	k = 0;
        t = a0 * b2 + u8.w2 + k;	u8.w2 = t;	k = t >> 8;
        t = a1 * b2 + u8.w3 + k;	u8.w3 = t;	k = t >> 8;
        t = a2 * b2 + u8.w4 + k;	u8.w4 = t;	k = t >> 8;
        t = a3 * b2 + u8.w5 + k;	u8.w5 = t;	k = t >> 8;
        u8.w6 = k;	k = 0;
        t = a0 * b3 + u8.w3 + k;	u8.w3 = t;	k = t >> 8;
        t = a1 * b3 + u8.w4 + k;	u8.w4 = t;	k = t >> 8;
        t = a2 * b3 + u8.w5 + k;	u8.w5 = t;	k = t >> 8;
        t = a3 * b3 + u8.w6 + k;	u8.w6 = t;	k = t >> 8;
        u8.w7 = k;
        return u32[1];
    #else
        return uint32_t((uint64_t(u) * v) >> 32);
    #endif
}

На STM32 имеется инструкция умножения 32x32=>64, по этому используем встроенное умножение.

Кстати, если из метода с умножением на 10 из предыдущей статьи (utoa_fract) выкинуть цикл умножения и заменить его на mul32hu, то он будет выполняться на AVR в среднем за 648 тактов, что делает его быстрее, чем метод вычитания степеней 10 (utoa_cycle_sub).

В самом методе, как и для целых чисел, сначала умножаем исходное число на множитель, который представляет из себя степень двойки, соответствующую той битовой позиции, где будут извлекаться цифры, делённую на степень 10, такую, чтоб не было переполнения. Затем в цикле извлекаются десятичные цифры с определённой битовой позиции, в данном случае с 29 бита, и число умножается на 10. После извлечения всех цифр производится коррекция коррекция старшей цифры, так как она может оказаться больше 10. Для этой коррекции в строковом буфере предусмотрен 1 символ, который инициализируется в '0'. После удаляются ведущие и завершающие нули.
char *fix_tostr_fract(uint32_t value, char *buffer)
{
    const int fractBits = 16;
    const int intPartShift = 32 - fractBits - 4;
    const uint32_t pow2 = 1ul << intPartShift;
    const uint32_t pow10 = 1000;
    const uint_fast8_t pointPosition = 5;
    const uint32_t multiplier = (1ull << 32) * pow2 / pow10 / 10;

    char *ptr = buffer;
    *ptr++ = '0';
    if(value == 0)
    {
        buffer[1] = 0;
        return buffer;
    }
    uint32_t t = mul32hu(value, multiplier)+1;
    for(uint_fast8_t i = 0; i < 9; i++)
    {
        if(i == pointPosition)
            *ptr++ = '.';
        // uint8_t digit = (uint8_t)(t >> 28);
        uint8_t digit = (uint8_t)(t >> 24);
        digit >>= 4;

        *ptr++ = digit + '0';
        t &= 0x0fffffff;
        // t *= 10;
        t <<= 1;
        t += t << 2;
    }
    ptr[0] = 0;
// коррекция старшей цифры
    while(buffer[1] > '9')
    {
        buffer[1] -= 10;
        buffer[0]++;
    }
    // remove leading zeros
    while(buffer[0] == '0' && buffer[1] != '.') ++buffer;
    // remove traling zeros
    ptr--;
    while(ptr[0] == '0')
        --ptr;
    ptr[1] = 0;
    if(ptr[0] == '.')
        ptr[0] = 0;
    return buffer;
}

На AVR этот метод работает в среднем за 604 такта, на STM32 — 236. Не плохо, но всё-же хуже чем у предыдущего метода. Однако, если дробная часть будет не 16 разрядов, как в примере, а занимать все 32 бита, то умножение будет не нужно и этот метод станет проще и быстрее предыдущего.
Внимательный читатель заметит эту конструкцию.
// uint8_t digit = (uint8_t)(t >> 28);
uint8_t digit = (uint8_t)(t >> 24);
digit >>= 4;

Почему нельзя оставить только закомментированный фрагмент?
Потому, что в avr-gcc сдвиг 32-х разрядного числа плохо оптимизирован. Вместо того, чтоб взять старший байт и сдвинуть его на 4 разряда, компилятор генерирует цикл, в котором честно сдвигает все 4 байта на 28 разрядов. Это у него занимает 28*(4+1) + 2 = 142 цикла вместо 3 (mov, swap, andi), которые должны быть. А ведь это выполняется в цикле 9 раз, итого 1278 такта — дофига для такой мелочи. Приходится компилятору немного помогать.

Плавающая точка.

Для преобразования чисел с плавающей точкой в строку тоже можно придумать много вариантов, но я этого делать не буду. Приведу только один вариант и сравню его с sprintf.
Этот вариант будет, естественно, умножение на 10, потому, что чтоб вытащить цифры из мантиссы, её всё-равно придётся умножать. В методах умножения на 10 для целых и чисел с фиксированной точкой, начальное 64 битное умножение занимало заметную долю времени. Поэтому они оказывались несколько медленнее, чем некоторые другие, несмотря на то, что умножение на 10 само по себе быстрее, чем деление. В случае с плавающей точкой он должен оказаться безоговорочным лидером.
Для начала посмотрим на что способна sprintf, при выводе float-ов.
Для AVR sprintf работает в среднем за 2000 такта, на STM32 — 3950. Не стоит ругать sprintf на STM32 — помним, что там преобразуется double, а не float.
При этом sprintf с ключиком %g округляет результат до заданной точности, удаляет незначащие нули и выводит результат в наиболее подходящем виде: в обычном 123.45, или научном 1.2345e+2.
Задача состоит в том, чтоб получить вывод идентичный sprintf, но гораздо быстрее.
Разделим задачу на две части:
1 — извлечение из float-а значащих цифр и десятичной экспоненты;
2 — форматирование результата в нужном виде.
Для первой части нужно:
— распаковать float, вытащить из него мантиссу, двоичную экспоненту и знак;
— обработать особые случаи: ±0, ±inf, nan.
— получить десятичную экспоненту;
— получить множитель 2 в степени экспоненты, делённая на степень 10, такую, чтоб не было переполнения;
— умножить мантиссу на этот множитель;
— извлечь требуемое количество значащих цифр + одну для округления;
— округлить полученные цифры;
— удалить незначащие нули.

Извлечение цифр и десятичной экспоненты выделим в отдельную функцию:
int ftoaEngine(float value, char *buffer, int presc)
{

presc — это необходимое количество значащих цифр.
Теперь распакуем float:

    uint32_t uvalue = *reinterpret_cast<uint32_t*>(&value);
    uint16_t uvalue_hi16 = (uint16_t)(uvalue >> 16);
    uint8_t exponent = (uint8_t(uvalue_hi16 >> 7));
    uint32_t fraction = (uvalue & 0x00ffffff) | 0x00800000;

Финт ушами со сгвигим экспоненты на 16 бит, а потом еще на 7, опять-же чтоб задобрить компилятор.
Теперь извлечём знак:

    char *ptr = buffer;
    if(uvalue & 0x80000000)
        *ptr++ = '-';
    else
        *ptr++ = '+';

Первый символ в выходном буфере — знак + или -.
Обрабатываем ±0, ±inf, nan:

    if(exponent == 0) // don't care about a subnormals
    {
        ptr[0] = '0';
        ptr[1] = 0;
        return 0xff;
    }
    if(exponent == 0xff)
    {
        if(fraction & 0x007fffff)
        {
            ptr[0] = 'n';
            ptr[1] = 'a';
            ptr[2] = 'n';
            ptr[3] = 0;
        }
        else
        {
            ptr[0] = 'i';
            ptr[1] = 'n';
            ptr[2] = 'f';
            ptr[3] = 0;
        }
        return 0xff;
    }

Денормализованные числа считаем равными нулю. Это не совсем правильно, но мне не хотелось с ними возиться.
Резервируем один символ в буфере для округления:
*ptr++ = '0';

Вычислить требуемый множитель за приемлемое время не получится, можно конечно, сдвигать влево-вправо, умножать-делить на 10 константу в цикле в зависимости от экспоненты, но это очень медленно. Поэтому множитель нужно брать из таблицы. Но 256 32-х битный чисел — этож целый килобайт, слишком жирно. Соседние элементы этой таблицы будут отличаться на степень 2, следовательно результат умножения для соседних элементов можно получить сдвигая его в нужную сторону на количество бит, равное расстоянию между этими элементами. Поскольку мантисса 24-х разрядная, то сдвигать её можно на 8 разрядов без потери точности. Это значит, что в таблице можно оставить только каждый 8 элемент, 32 элемента — уже приемлемо. Чтоб сохранить точность, сначала будем сдвигать мантиссу на 8 бит влево, потом умножать, потом сдвигать влево на требуемое количество бит. Так удаётся сохранить все 24 бита точности не выходя за 32-х битную арифметику (кроме умножения, конечно).
Десятичную экспоненту несложно вычислить из двоичной, они связаны линейной зависимостью. Сложность только в том, чтоб подобрать масштабирующие и сдвигающие коэффициенты, при которых будет верный результат во всём диапазоне входных значений, и не вылезти при этом за пределы 16-ти битной арифметики.
int exp10 = ((((exponent>>3))*77+63)>>5) - 38;
    fraction <<= 8;
    uint32_t t = mul32hu(fraction, pgm_read_dword(&table2[exponent / 8])) + 1;
    uint_fast8_t shift = 7 - (exponent & 7);
    t >>= shift;

Теперь удалим ведущие нули:

    uint8_t digit = t >> 24;
    digit >>= 4;
    while(digit == 0)
    {
        t &= 0x0fffffff;
        t <<= 1;
        t += t << 2;
        digit = (uint8_t)(t >> 24);
        digit >>= 4;
        exp10--;
    }

Извлечение цифр:
for(uint_fast8_t i = presc+1; i > 0; i--)
    {
        digit = (uint8_t)(t >> 24);
        digit >>= 4;
        *ptr++ = digit + '0';
        t &= 0x0fffffff;
        t <<= 1;
        t += t << 2;
    }

Округление:
if(buffer[presc+2] >= '5')
        buffer[presc+1]++;
    ptr[-1] = 0;
    ptr-=2;
    for(uint_fast8_t i = presc + 1; i > 1; i--)
    {
        if(buffer[i] > '9')
        {
            buffer[i]-=10;
            buffer[i-1]++;
        }
    }

Удаление завершающих нулей.
while(ptr[0] == '0')
    {
        *ptr-- = 0;
    }
    return exp10;
}

В итоге в буфере знак и значащие цифры, десятичная экспонента в возвращаемом из функции значении.
Теперь дело за форматированием:
char * my_ftoa(float value, char *result)
{
    uint8_t precision = 6;
    char *out_ptr = result;
    const int bufferSize = 10;
    char buffer[bufferSize+1];
// получили цифры
    int exp10 = ftoaEngine(value, buffer, precision);
// если там inf или nan - выводим как есть.
    if(exp10 == 0xff)
    {
        uint8_t digits = strlen(buffer);
        uint_fast8_t i = 0;
        if(buffer[0] == '+')
            i = 1;

        for(; i < digits; i++)
            *out_ptr++ = buffer[i];
        *out_ptr = 0;
        return result;
    }
// если был перенос старшей цифры при округлении
    char *str_begin = &buffer[2];
    if(buffer[1] != '0')
    {
        exp10++;
        str_begin--;
    }
// количество значащих цифр <= precision
    uint_fast8_t digits = strlen(str_begin);

    uint_fast8_t intDigits=0, leadingZeros = 0;
    if(abs(exp10) >= precision)
    {
        intDigits = 1;
    }else if(exp10 >= 0)
    {
        intDigits = exp10+1;
        exp10 = 0;
    }else
    {
        intDigits = 0;
        leadingZeros = -exp10 - 1;
        exp10 = 0;
    }
    uint_fast8_t fractDigits = digits > intDigits ? digits - intDigits : 0;
// целая часть
    if(intDigits)
    {
        uint_fast8_t count = intDigits > digits ? digits : intDigits;
        while(count--)
            *out_ptr++ = *str_begin++;
        int_fast8_t tralingZeros = intDigits - digits;
        while(tralingZeros-- > 0)
            *out_ptr++ ='0';
    }
    else
        *out_ptr++ = '0';
// дробная часть
    if(fractDigits)
    {
        *out_ptr++ = '.';
        while(leadingZeros--)
            *out_ptr++ = '0';
        while(fractDigits--)
            *out_ptr++ = *str_begin++;
    }
// десятичная экспонента
    if(exp10 != 0)
    {
        *out_ptr++ = 'e';
        uint_fast8_t upow10;
        if(exp10 < 0)
        {
            *out_ptr++ = '-';
            upow10 = -exp10;
        }
        else
        {
            *out_ptr++ = '+';
            upow10 = exp10;
        }
        char *powPtr = utoa_fast_div(upow10, buffer + bufferSize);
        while(powPtr < buffer + bufferSize)
        {
            *out_ptr++ = *powPtr++;
        }
    }
    *out_ptr = 0;
    return result;
}

Получилось достаточно многословно, но результат того стоил. На AVR в среднем 759 тактов, на STM32 — 425. Для сравнения в avr-libc есть функция dtostre, которая использует для преобразования тот-же движок, что и sprintf, но выводит всегда в экспоненциальном формате и не удаляет незначащие нули. Её среднее время в этом тесте — 1215 тактов. И это при том, что преобразование там реализовано на ассемблере.

Результат тестов с фиксированной точкой для AVR:

Результат тестов с фиксированной точкой для STM32:

Результат тестов с плавающей точкой для AVR:

Результат тестов с плавающей точкой для STM32:


Все тесты проведены на компиляторах avr-gcc и arm-gcc соответственно, с оптимизацией -O3.
  • +14
  • 07 января 2013, 13:08
  • neiver
  • 1
Файлы в топике: float_and_fixed.zip

Комментарии (35)

RSS свернуть / развернуть
Отличная работа! Обязательно использую где-нибудь
0
Только аккуратнее, оно еще не дотестировано до конца. Особенно float.
0

            value <<= 1;
            value += value << 2;


Новые версии avr-gcc распознают в этом умножение и делают вызов всей этой своей __mulhisi3… на -Os по крайней мере так.

Да и кстати более интересен может оказаться размер кода, а не скорость выполнения.
0
«Для AVR это не имеет значения, так как float и double там одно и тоже». Смело. Оказывается, от платформы зависит разрядность double?
А для чего же я порой ставлю галочку в проекте для AVR «Use 64-bit doubles»? :)
0
Так в этом и дело, если галачку не ставить, то флоат и дубле одно и тоже. Кстати, где это вы нашли эту галачку (какая среда)?
+1
ИАР конечно. Остальное душа не приняла. :)
+2
В avr-gcc — всегда одно и тоже.
0
Так выкинуть его!
0
ты себя, блин, выкинь лучше
+1
Зачем? Я давно сделал свой выбор — и купил нормальную среду.
Инструмент должен быть подходящим для работы.
0
Прям сам купил? Что-то не верится мне :-/
0
Да, сам, 4 года назад. А продукцией ИАР пользуюсь лет 15, еще под ДОСом помню, «фитоновская» была среда с их компилятором, под i51.
Теперь подумываю о покупке среды для ARM, т.к. уже не хватает 32К бесплатного КикСтарта.
Хорошая среда разработки — успешная работа.
0
И чем она хорошая? Меня как-то не впечатляет. Новая AVRStudio, если говорить о AVR, поинтересней будет. Компилятор неплохой, но GCC не хуже.
0
Наверно, качеством генерируемого кода.
nxtgcc.sourceforge.net/wiki/GCC_and_IAR_compiler_comparison
0
IAR-овский компилятор тоже иногда чудит, например, кладёт переменные с стек, когда не надо, неумеет оптимизировать ссылки в С++, не шибко хорошо оптимизирует циклы. Вообще у каждого компилятора свои сильные и слабые стороны.
IAR:
+ отдельный стек для данных — быстро работает, меньше кода;
+ отличная поддержка набора инструкций;
+ хорошо портированные библиотеки
+ хорошая cross-call оптимизация — компактный код, но невысокая скорость
— плохо распределяет регистры, avr-gcc обходится меньшим их числом.
— плохо оптимизирует циклы: не умеет их разворачивать, не умеет заменять индексы на указатели и.т.д
— есть косяки в С\С++ фронтендах, некоторые возможности язаков не работают как должны. Бывает падает в segfault, при компиляции некоторого кода.

GCC:
+ хорошо распределяет регистры — обходится минимальнвм числом;
+ хорошо оптимизирует циклы;
+ хорошо оптимизирует математические выражения;
+ хорошо инлайнит функции
— плохо оптимизированы некоторые низкоуровневые операции: длинные сдвиги uint32_t, uint64_t вообще не оптимизирован
— не умеет использовать некоторые инструкции;
— не умеет cross-call optimization.
— библиотеки хуже чем у IAR.

В общем паритет, я считаю. GCC часто генерирует более быстрый код, IAR — более компактный.
+1
А что такое cross-call optimization?
0
Наверное находят-делают общие куски кода и выделяют из для обшего использования различными функциями. Экономят код.
0
Ага. Оно самое.
Но иногда этот коссколл чудит, и делает делает такое
func1:
    ...
_func1_ret:
    clr A    ; 1 байт 1 такт
    ret      ; 1 байт

func2:
...
    goto _func1_ret    ;    3 байта 2 такта
А порой и просто переходы на рет. То есть однозначная потеря памяти и производительности.
Впрочем у меня устаревшая версия, новая, надеюсь, такого не творит.
А так вещь очень полезная для релиза (отладка такого кода — особенное удовольствие).
+1
Там несколько другая платформа (хотя по идее, как раз ARM GCC должен хорошо поддерживать). На AVR GCC хорошо себя показывает, хотя и зависит от кода, скажем V-USB на GCC лучше. В принципе, Neiver достаточно подробно расписал.
Ну а на x86 GCC и вовсе в тройке лидеров (ICC, VC, GCC).
IAR лично меня привлекает большой базой поддерживаемых таргетов (из более-менее известного там нет разве что PIC'ов, есть только старый для PIC18, и тот, говорят, не блещет) и наличие универсального крека для всех версий (впрочем, последнее уже неверно — новая схема лицензирования пока требует индивидуальных креков для каждой версии). Из минусов — страшно примитивная среда и платность.
В целом, где есть GCC — я предпочитаю GCC, хотя там обычно приходится поплясать с бубном для создание нормальной среды и отладки. В IAR'е отладка из коробки и все уже увязано воедино.
0
Из минусов — страшно примитивная среда
Eclipse IDE now available
… лед тронулся, минус один минус
0
Эт хорошо, правда из заметки непонятно, что для этого качать и как собственно включать интеграцию в эклипс. Ну и вроде пока только IAR EWARM.
0
Объяснения, как именно включать интеграцию в эклипс не появилось?
0
Пока они не перестанут юзеров принуждать к винде, от нуля они все равно отличаться не будут.
-2
+100500 Даже не говоря про 64-битовые числа, GCC вызывет просто остервенение своим примитивным стеком данных. Вложенные прерывания требуют отдельных танцев с бубном. Короче, присоединяюсь к мнению, высказаному на electronix: «Для AVR сравнивать GCC и IAR бесполезно — IAR вне конкуренции, начиная с более правильного calling conversion. Код получается и меньше и быстрее. GNU в принципе IAR не догнать пока calling conversion не поменяют!». electronix.ru/forum/lofiversion/index.php//t108151.html
+1
Несколько сомнительное утверждение. И хотелось бы подробней.
0
Это я про копипасту с электроникса.
0
Хотите сказать, что у Вы лично приобрели лицензию ИАРа?
0
Что значит лично? На одного человека? Это невыгодно. В фирме, где я работаю, 3 сотрудника, мы же учредители. Я прикрываю embedded-часть. Поговорили — надо, и я купил с дисконтом лицензию на several users simultaneously (до 5 юзеров, т.к на большие работы привлекаем людей).
0
А не в курсе стоимости лицензии на кортексовую версию 1 пользователь? А то там прямо замкнутый круг — иар посылает к региональным представителям, а региональные представители — к иар.
0
Нет, пока не занимался. МТ-систем официальный дистриб — свяжитесь.
www.mt-system.ru/news/mt-sistem/programmnoe-obespechenie-i-otladochnye-sredstva-iar-systems-so-sklada-mt-sistem
0
И что, иар для нее подходит? Может он уже и под линух есть?
0
Так в этом и дело, если галачку не ставить, то флоат и дубле одно и тоже. Кстати, где это вы нашли эту галачку (какая среда)?
0
Спасибо за статью. Ваши статьи всегда отличаются глубиной погружения в тематику и проработкой материала.
+1
По моему в функции char *fix_tostr1(uint32_t value, char *buffer) вкралась ошибка, вместо char *str_begin = utoa_fast_div(intPart, ptr); должно было быть char *str_begin = utoa_fast_div(intPart, buffer);. У меня только так заработало.
0
Можно ещё преобразовать fixed-point в строку как целое, а потом в строковом виде поделить на 2 нужное количество раз. Довольно просто, но много возни с дописыванием точки и нулей, а потом лишние нули отрезать.

Мне показалось, что так удобнее когда есть много чисел с различным (или даже изменяемым) положением точки.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.