Встроенное деление с отрицательными числами

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

В настоящее время я имею дело с показаниями датчика, которые могут быть положительными или отрицательными, и их необходимо масштабировать на 0,006, сохраняя знак. Чтобы избежать ненужных вычислений с плавающей запятой во время выполнения, у меня есть алгоритм, который преобразует это в числитель и знаменатель (3/500). Все работает, как и ожидалось, с положительными числами, но вот что происходит с отрицательными:

Raw data:         -103
Multiplied by 3:  -309
Divided by 500:   36893488147419102

Я понял, откуда берется это число, и у меня есть обходной путь, но я предпочитаю верить, что математика есть математика.

Вот тот же расчет в шестнадцатеричном формате:

Raw data:         0xFFFFFFFFFFFFFF99
Multiplied by 3:  0xFFFFFFFFFFFFFECB
Divided by 500:   0x0083126E978D4FDE

В калькуляторе (SpeedCrunch):

0xFFFFFFFFFFFFFECB/500 = 0x83126E978D4FDE.9D2F1A9FBE76C8B44

Этот исходный 36893488147419102 является неотъемлемой частью 0x83126E978D4FDE результата SpeedCrunch.

Я не хочу сохранять знак, делать положительное деление, а затем снова добавлять знак каждый раз, когда я делаю деление с отрицательными числами. Что тут происходит?

Среда — CortexM3 micro, GCC4.9.3 с использованием С++ 11. Вычисления выполняются на int64_t, а числитель/знаменатель uint64_t.

Изменить: Вот фрагмент кода в ответ на комментарий Майкла ниже:

int64_t data = -103;
uint64_t resolutionNumerator = 3;
uint64_t resolutionDenominator = 500;

data *= resolutionNumerator;
data /= resolutionDenominator;

person Jeff Lamb    schedule 11.12.2015    source источник
comment
Я знаю, что вы говорите, что расчеты выполняются на ..., но чтобы исключить любые догадки, вы действительно должны показать фактический источник выражений, участвующих в расчете, а также типы для любых задействованных переменных.   -  person Michael Burr    schedule 12.12.2015
comment
Все эти цифры основаны на таблицах и необработанных данных, поступающих из других источников. Это чрезвычайно общий код без каких-либо магических чисел. Тем не менее, я сократил его и добавил фрагмент.   -  person Jeff Lamb    schedule 12.12.2015
comment
Выражения со смешанными типами данных, как правило, очень плохая идея. Вы должны стремиться к соглашению типов во всем, а там, где это нецелесообразно (в данном случае это не так), вы должны использовать явные приведения, чтобы указать, что несоответствие типов является преднамеренным. Язык определяет неявные преобразования и продвижение типов, которые могут не соответствовать вашим ожиданиям или потребностям (как в данном случае).   -  person Clifford    schedule 12.12.2015
comment
Кажется маловероятным, что ваш датчик предоставляет данные с разрешением, которое гарантирует 64-битное разрешение, и при делении на 0,0006 вы в любом случае отбрасываете более 10 бит информации. Вы уверены, что вам нужна 64-битная версия или что это подходящее место для выполнения масштабирования? Обычно вы масштабируете данные только для представления в реальных единицах измерения. Внутренние вычисления лучше всего выполнять при полном разрешении данных датчика.   -  person Clifford    schedule 12.12.2015
comment
Да, ты прав. Этот датчик всего 16 бит. Однако это чрезвычайно общий код, и другой датчик может передавать данные до 64 бит. Существует конфигурация для каждого показания датчика, которая позволяет повысить разрешение путем умножения на 10 или 100 и т. д. с единственной целью избежать математики с плавающей запятой и перевода в реальные единицы измерения, о которых заботится отдельное приложение.   -  person Jeff Lamb    schedule 17.12.2015


Ответы (2)


Операция с целыми числами со знаком и без знака одинаковой ширины, такими как uint64_t и int64_t, приводит к преобразованию операнда со знаком в тип операнда без знака.

Следовательно, эти два выражения эквивалентны:

  • (int64_t) -103 * (uint64_t) 3 / (uint64_t) 500
  • (uint64_t) -103 * (uint64_t) 3 / (uint64_t) 500

Если бы знаковый тип int64_t использовался для числителя и знаменателя, результат сохранил бы знак.

person D Krueger    schedule 11.12.2015
comment
Переключил числитель и знаменатель на uint32_t, и все мои модульные тесты проходят. Спасибо! - person Jeff Lamb; 12.12.2015

Что тут происходит?

Когда вы переводите его из беззнакового типа в знаковый, начальные 1-биты остаются для отрицательных чисел из представления дополнения 2.

Вы можете восстановить ожидаемый результат, приведя окончательный результат обратно к значению со знаком. В качестве значения со знаком первые 1 биты будут означать его отрицательное значение.


Мы можем сказать только может, потому что не видели подпрограмм. Майкл указал на то же самое в комментарии.

int64_t data = -103;
uint64_t resolutionNumerator = 3;
uint64_t resolutionDenominator = 500;

Интегральные правила продвижения C/C++ означают, что подписанный тип повышается до беззнакового типа. Вот почему -1 > 1:

int i = -1;
unsigned int j = i;

if ( i > j )
    printf("-1 is greater than 1\n");

Вы не выполнили приведение, упомянутое в ответе выше; но компилятор выполнил интегральное преобразование. Для того, что вы наблюдаете, это та же разница.

person jww    schedule 11.12.2015
comment
0x0083126E978D4FDE (результат) не имеет ведущего бита 1. Кастинг не поможет. Здесь нет специальных математических процедур. Просто умножь и раздели. - person Jeff Lamb; 12.12.2015
comment
Да, дело с плавающей запятой практически не имело отношения к вопросу. Это не о плавающей запятой. Кроме того, математика с плавающей запятой (программная реализация) в 24 раза дороже для моей цели, чем интегральная математика. Поскольку этот код запускается 250 раз в секунду, у меня есть мотивация избегать операций с плавающей запятой. - person Jeff Lamb; 12.12.2015
comment
Очень жаль? Во-первых, я реализовал «хак», который исправил это, прежде чем публиковать это. Вопрос заключался в том, почему простое деление не работает. Во-вторых, почему кто-то должен писать собственные процедуры для работы с делением негативов? Математика есть математика. Я хотел бы доверять этому. - person Jeff Lamb; 12.12.2015
comment
Да, извините за тон. Я пытался отредактировать комментарий, но есть ограничение в 5 минут. Я не хотел, чтобы это произошло таким образом. Эта штука -1 > 1 точно сбивает с толку. Спасибо за вашу помощь. - person Jeff Lamb; 12.12.2015
comment
@Jeff - Эта -1 › 1 вещь точно сбивает с толку... - о да... Это очень неожиданный результат. Я использую его в своей презентации для безопасного кодирования. - person jww; 12.12.2015
comment
Аааа, теперь вся эта ветка комментариев относится к контенту, который вы отредактировали. Ну что ж, теперь ваш ответ более актуален. - person Jeff Lamb; 12.12.2015
comment
@Jeff - да, нет причин сохранять его, особенно если это вызовет путаницу у будущих посетителей :) Извините за это. - person jww; 12.12.2015