ts.tv_nsec+=timeout;
if (ts.tv_nsec>=1000000000){
ts.tv_nsec = ts.tv_nsec % 1000000000;
ts.tv_sec += ts.tv_nsec / 1000000000;
}
В этом методе есть недостаток. Мы не можем применять такой подход для
контроллеров. На архитектуре типа Cortex-M23 нет деления. Эмуляция деления
будет вычисляться удивительно неэффективно. Надо исключить операцию деления и
операцию взятия остатка от деления.
Идеально было бы исключить и проверку, чтобы код стал линейным.
Я перебирал варианты, как это вообще можно сделать. На ум приходят разные трюки с операциями: сложение с насыщением, с проверкой переполнения, сложение с переносом. Деление на константу можно заменить на умножение и сдвиг, но это опять не сильно эффективная операция.
Есть способ проведения вычислений с двоично-десятичными числами (Binary-coded decimal, BCD), когда при сложении выполняется коррекция операции сложения - добавление константы, чтобы заработать перенос между разрядами десятичного числа. В случае двоично-десятичных чисел к десятичному разряду добавляется дополнение (число 6), если разряд больше 9. Число 6 получается, как инверсия (0x9^0xF) или в нашей математике (16-10). Аналогично мы расчитываем дополнение для миллиарда в двоичном представлении ((1<<30) - 1000000000) или (0x3B9ACA00^0xFFFFFFFF).
Получился такой код, он предполагает использование 64 битного упакованного времени, при этом в малдших 32 битах числа расположены наносекунды, а в старших секунды.
// коррекция сложения интервалов времени
if ((uint32_t)t64 > 999999999UL) t64 += (uint32_t)~999999999UL;
Полусумматор и входящий перенос
А был ли перенос между разрядами? Сумматор (операция сложения двоичных чисел) состоит из цепочки полусумматоров, в которой каждый последующий разряд использует перенос с предыдущего разряда. Нужно выделить входящий перенос каждого разряда. Рассмотрим как работает аппаратная функция переноса разрядов в полусумматоре.
ci = carry_in -- входящий перенос: 0 или 1
s = a^b^ci -- результат сумма разрядов
сo = ci? (a|b) : (a&b); -- исходящий перенос
Нас интересуют входящие переносы (сi) по маске двоично-десятичного числа
0x11111110
при сложении с дополнением (a+b+D). Сложение с
дополнением формирует правильную цепочку переносов между десятичными
разрядами. При этом коррекция результата сложения требуется, если между
десятичными разрядами был перенос.
s1 = a+b; -- формирует правильные результаты в каждом разряде, если переноса
не было,
s2 = a+b+D; -- формирует правильную цепочку переносов между разрядами.
s = co? s2: s1; -- коррекция результата сложения
struct _BCD_Time {
unsigned int susec:8; //сотые
unsigned int sec:8; //секунды
unsigned int mins:8; //минуты
unsigned int hour:8; //часы
};
#define BCD_TIME_CORRECT 0x66A6A666UL
#define BCD_TIME_CARRYIN 0x11111110UL
time_add(a, b) {
s1 = a + b;
s2 = a + b + BCD_TIME_CORRECT; // коррекция BCD для вычисления переносов
m =(a ^ b ^ s2) & BCD_TIME_CARRYIN; // из переносов делаем маску
m = m - (m>>4); // маска для выделения десятичных разрядов
return s1 + (m & BCD_TIME_CORRECT); // коррекция сложения
}
Вот в таком виде можно применять к вычислению времени и временных интервалов независимо от архитектуры ядра процессора.
Комментариев нет:
Отправить комментарий