Преобразование в ASCII в C
используя микроконтроллер (PIC18F4580), мне нужно собрать данные и отправить их на SD-карту для последующего анализа. Данные, которые он собирает, будут иметь значения от 0 до 1023 или 0x0 и 0x3FF.
Итак, что мне нужно сделать, это преобразовать 1023 в базовую строку 10 литеральных значений ASCII (0x31, 0x30, 0x32, 0x33, ...).
моя проблема в том, что единственный способ, который я могу придумать, чтобы разделить цифры, требует большого разделения.
char temp[4];
temp[0] = 1023 % 10;
temp[1] = (1023 % 100) / 10;
temp[2] = (1023 % 1000) / 100;
temp[3] = (1023 % 10000) / 1000;
используя этот метод, находя для ASCII-значений n-разрядного десятичного числа требуется 2n-1 деление. Есть ли способ, который был бы быстрее?
конечная цель этого-закончить с a .csv-файл на SD-карте, который можно быстро подключить к любому ноутбуку, чтобы увидеть график данных в Excel.
9 ответов
есть способ сделать это с помощью вычитаний, но я не уверен, что это быстрее, чем использование вычитаний и модуля на "нормальном" процессоре (может отличаться во встроенной среде).
что-то вроде этого:
char makedigit (int *number, int base)
{
static char map[] = "0123456789";
int ix;
for (ix=0; *number >= base; ix++) { *number -= base; }
return map[ix];
}
char *makestring (int number)
{
static char tmp[5];
tmp[0] = makedigit(&number, 1000);
tmp[1] = makedigit(&number, 100);
tmp[2] = makedigit(&number, 10);
tmp[3] = makedigit(&number, 1);
tmp[5] = '';
return tmp;
}
затем вызов makestring()
должно привести к (статической, поэтому скопируйте ее перед перезаписью) строке с преобразованным числом (с нулевым префиксом, шириной 4 символа, так как исходное предположение является значением в диапазоне 0-1023).
очевидное решение не преобразовать данные в ASCII вообще, но сохранить его в двоичном формате. Таким образом, все, о чем вам нужно беспокоиться, - это полнота данных. Если система, выполняющая более поздний анализ, намного мощнее вашей встроенной цели,тогда имеет смысл позволить ей заниматься преобразованием и порядком байтов.
с другой стороны, возможно, что время выполнения / и % незначительно по сравнению с затраченным временем для передачи данных на SD карту; поэтому убедитесь, что вы оптимизировать правильно.
есть, конечно, гораздо более быстрый способ: иметь массив из 1024 предварительно вычисленных строк. Затем вы можете просто выполнить проверку границ, а затем индекс в массив.
из вашего вопроса неясно, работает ли ваш код на микроконтроллере. Если это так, у вас может не хватить памяти для этого подхода.
Я согласен с тем, что сказал Клиффорд, что вы не должны беспокоиться об оптимизации, если вам это не нужно, и что вы можете нажать очистку журнала на свою платформу анализа, а не беспокоиться о форматировании во встроенном приложении.
Это, как говорится, вот статья, которая может быть полезна для вас. Он использует цикл, сдвиги, дополнения и ветви, с линейной / постоянной сложностью: http://www.johnloomis.org/ece314/notes/devices/binary_to_BCD/bin_to_bcd.html
кроме того, я подумал, что было бы интересно сделать некоторый код, который не выполняет никаких делений, умножений или ветвей, но все же дает правильный ответ [0 - 1024). Нет обещаний, что это будет быстрее, чем другие варианты. Этот вид кода-просто возможность для изучения.
Я хотел бы посмотреть, может ли кто-нибудь предоставить некоторые трюки, чтобы сделать код меньше, требует меньше памяти или требует меньше операций, сохраняя при этом остальные графы равными или сокращая их:)
статистика:
- 224 байта в константах (без понятия о размере кода)
- 5 бит-shift-права
- 3 вычитает
- 5 побитовые ands
- 4 побитовые ОРС
- 1 больше, чем сравнение
Perf:
используя сравнения perf и процедуры itoa в Ответ Джонатан Леффлер, вот статистика у меня:
- отдела 2.15
- вычитание 4.87
- мое решение 1.56
- Поиск грубой силы 0.36
я увеличил количество итераций до 200000, чтобы убедиться, что у меня не было проблем с временным разрешением, и мне пришлось добавить volatile
сигнатурам функций, чтобы компилятор не оптимизировал цикл. Я использовал настройки VS2010 express w/ vanilla "release", на 3GHz dual core 64 бит Windows 7 машина (tho она скомпилирована до 32 бит).
код:
#include "stdlib.h"
#include "stdio.h"
#include "assert.h"
void itoa_ten_bits(int n, char s[])
{
static const short thousands_digit_subtract_map[2] =
{
0, 1000,
};
static const char hundreds_digit_map[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
0, 0, 0,
};
static const short hundreds_digit_subtract_map[10] =
{
0, 100, 200, 300, 400, 500, 600, 700, 800, 900,
};
static const char tens_digit_map[12] =
{
0, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9,
};
static const char ones_digit_map[44] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 1, 2, 3
};
/* Compiler should optimize out appX constants, % operations, and + operations */
/* If not, use this:
static const char ones_digit_append_map[16] =
{
0, 6, 2, 8, 4, 10, 6, 12, 8, 14, 10, 16, 12, 18, 14, 20,
};
*/
static const char a1 = 0x10 % 10, a2 = 0x20 % 10, a3 = 0x40 % 10, a4 = 0x80 % 10;
static const char ones_digit_append_map[16] =
{
0, a1, a2, a1 + a2,
a3, a1 + a3, a2 + a3, a1 + a2 + a3,
a4, a1 + a4, a2 + a4, a1 + a2 + a4,
a3 + a4, a1 + a3 + a4, a2 + a3 + a4, a1 + a2 + a3 + a4,
};
char thousands_digit, hundreds_digit, tens_digit, ones_digit;
assert(n >= 0 && n < 1024 && "n must be between [0, 1024)");
/* n &= 0x3ff; can use this instead of the assert */
thousands_digit = (n >> 3 & 0x7f) > 0x7c;
n -= thousands_digit_subtract_map[thousands_digit];
ones_digit = ones_digit_map[
(n & 0xf)
+ ones_digit_append_map[n >> 4 & 0xf]
+ ones_digit_append_map[n >> 8 & 0x3]
];
n -= ones_digit;
hundreds_digit = hundreds_digit_map[n >> 3 & 0x7f];
n -= hundreds_digit_subtract_map[hundreds_digit];
tens_digit = tens_digit_map[n >> 3];
s[0] = '0' | thousands_digit;
s[1] = '0' | hundreds_digit;
s[2] = '0' | tens_digit;
s[3] = '0' | ones_digit;
s[4] = '';
}
int main(int argc, char* argv)
{
int i;
for(i = 0; i < 1024; ++i)
{
char blah[5];
itoa_ten_bits(i, blah);
if(atoi(blah) != i)
printf("failed %d %s\n", i, blah);
}
}
с некоторой осторожностью в поиске нужного номера(ов) для использования, вы можете умножить на взаимные основания, а не деление на основание. Код Терье предназначен для x86, но перенос общей идеи на рис не должен быть чрезвычайно сложным.
если корректно в диапазоне (0..1023), тогда ваше последнее преобразование излишне расточительно для делений; последняя строка может быть заменена на:
temp[3] = 1023 / 1000;
или еще:
temp[3] = 1023 >= 1000;
поскольку деление-это повторное вычитание, но у вас есть очень особый случай (а не общий случай) деления, я бы соблазнился сравнить тайминги для следующего кода с версией деления. Я замечаю, что вы помещаете цифры в строку в ' reverse order' - наименьшая значимая цифра идет в temp[0]
и в temp[4]
. Кроме того, нет никаких шансов на нулевое завершение строки с учетом хранилища. Этот код использует таблицу из 8 байт статических данных-значительно меньше, чем многие другие решения.
void convert_to_ascii(int value, char *temp)
{
static const short subtractors[] = { 1000, 100, 10, 1 };
int i;
for (i = 0; i < 4; i++)
{
int n = 0;
while (value >= subtractors[i])
{
n++;
value -= subtractors[i];
}
temp[3-i] = n + '0';
}
}
тестирование производительности-Intel x86_64 Core 2 Duo 3.06 GHz (MacOS X 10.6.4)
эта платформа, вероятно, не является представителем вашего микроконтроллера, но тест показывает, что на этой платформе вычитание происходит значительно медленнее деления.
void convert_by_division(int value, char *temp)
{
temp[0] = (value % 10) + '0';
temp[1] = (value % 100) / 10 + '0';
temp[2] = (value % 1000) / 100 + '0';
temp[3] = (value % 10000) / 1000 + '0';
}
void convert_by_subtraction(int value, char *temp)
{
static const short subtractors[] = { 1000, 100, 10, 1 };
int i;
for (i = 0; i < 4; i++)
{
int n = 0;
while (value >= subtractors[i])
{
n++;
value -= subtractors[i];
}
temp[3-i] = n + '0';
}
}
#include <stdio.h>
#include <timer.h>
#include <string.h>
static void time_convertor(const char *tag, void (*function)(void))
{
int r;
Clock ck;
char buffer[32];
clk_init(&ck);
clk_start(&ck);
for (r = 0; r < 10000; r++)
(*function)();
clk_stop(&ck);
printf("%s: %12s\n", tag, clk_elapsed_us(&ck, buffer, sizeof(buffer)));
}
static void using_subtraction(void)
{
int i;
for (i = 0; i < 1024; i++)
{
char temp1[4];
convert_by_subtraction(i, temp1);
}
}
static void using_division(void)
{
int i;
for (i = 0; i < 1024; i++)
{
char temp1[4];
convert_by_division(i, temp1);
}
}
int main()
{
int i;
for (i = 0; i < 1024; i++)
{
char temp1[4];
char temp2[4];
convert_by_subtraction(i, temp1);
convert_by_division(i, temp2);
if (memcmp(temp1, temp2, 4) != 0)
printf("!!DIFFERENCE!! ");
printf("%4d: %.4s %.4s\n", i, temp1, temp2);
}
time_convertor("Using division ", using_division);
time_convertor("Using subtraction", using_subtraction);
time_convertor("Using division ", using_division);
time_convertor("Using subtraction", using_subtraction);
time_convertor("Using division ", using_division);
time_convertor("Using subtraction", using_subtraction);
time_convertor("Using division ", using_division);
time_convertor("Using subtraction", using_subtraction);
return 0;
}
компиляция с GCC 4.5.1 и работа в 32-битных, средние тайминги были (оптимизация'-O
'):
-
0.13
секунд с помощью деления -
0.65
секунд с помощью вычитания
компиляция и работа в 64-битных, средние тайминги были:
-
0.13
секунд с помощью деления -
0.48
секунд через вычитание
очевидно, что на этой машине использование вычитания не является выигрышным предложением. Вам нужно будет измерить на вашей машине, чтобы принять решение. И удаление операции по модулю 10000 приведет только к перекосу результатов в пользу деления (он выбивает около 0,02 секунды от времени с делением при замене на сравнение; это экономия 15% и стоит иметь).
есть ли какая-то причина, по которой вы особенно обеспокоены этим?
Если ваш компилятор и библиотека C предоставляют itoa()
функция, используйте это, а затем беспокоиться о написании этого кода (и связанных тестов и так далее, чтобы убедиться, что вы получили это право!) если по какой-то причине это оказывается слишком медленным или не вписывается в ОЗУ или что-то еще.
Я заменил свой предыдущий ответ на лучший. Этот код создает 4-символьную строку в правильном порядке, наиболее значимую цифру на выходе[0] до наименее значимой на выходе[3] с нулевым Терминатором на выходе[4]. Я ничего не знаю о вашем контроллере PIC или компиляторе C, но этот код не требует ничего больше, чем 16-битные целые числа, сложение/вычитание и сдвиг.
int x;
char output[5];
output[4] = 0;
x = 1023;
output[3] = '0' + DivideByTenReturnRemainder(&x);
output[2] = '0' + DivideByTenReturnRemainder(&x);
output[1] = '0' + DivideByTenReturnRemainder(&x);
output[0] = '0' + x;
ключ к этому-магическая функция DivideByTenReturnRemainder
. Без использования разделения явно можно разделить на степени 2, сдвинув вправо; проблема в том, что 10 не является степенью 2. Я обошел эту проблему, умножив значение на 25.625 перед делением на 256, позволяя целочисленному усечению округляться до правильного значения. Почему 25.625? Потому что он легко представлен степенями 2. 25.625 = 16 + 8 + 1 + 1/2 + 1/8. Опять же, умножение на 1/2-это то же самое, что сдвиг вправо на один бит, а умножение на 1/8-сдвиг вправо на 3 бита. Чтобы получить остаток, умножьте результат на 10 (8+2) и вычитайте его из исходного значения.
int DivideByTenReturnRemainder(int * p)
{
/* This code has been tested for an input range of 0 to 1023. */
int x;
x = *p;
*p = ((x << 4) + (x << 3) + x + (x >> 1) + (x >> 3)) >> 8;
return x - ((*p << 3) + (*p << 1));
}
вы должны использовать строку ASCII decimal представление? Было бы намного проще хранить его в шестнадцатеричном формате. Никакого разделения не требуется, только (относительно дешевые) операции смены. Excel должен иметь возможность читать его, если вы добавляете " 0x " к каждому номеру.