Округление целого числа без использования float, double или division

его встроенная платформа, поэтому такие ограничения.

original equation: 0.02035*c*c - 2.4038*c

это:

int32_t val = 112; // this value is arbitrary
int32_t result = (val*((val * 0x535A8) - 0x2675F70));
result = result>>24;

точность еще бедных. Когда мы умножаем val*0x535A8 есть ли способ улучшить точность путем округления, но без использования какого-либо поплавка, двойного или деления.

4 ответов


проблема не в точности. Ты используешь много битов.

Я подозреваю, что проблема в том, что вы сравниваете два разных метода преобразования в int. Первый-это бросок double, второй-это усечение путем сдвига вправо.

преобразование с плавающей запятой в целое число просто отбрасывает дробную часть, что приводит к круг к нулю; смещение вправо делает округлить или пол. Для положительных чисел нет разница, но для отрицательных чисел два метода будут 1 друг от друга. См. пример вhttp://ideone.com/rkckuy и некоторые фоновые чтения в Википедия.

исходный код легко исправить:

int32_t result = (val*((val * 0x535A8) - 0x2675F70));
if (result < 0)
    result += 0xffffff;
result = result>>24;

см. результаты в http://ideone.com/D0pNPF

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

Edit: если вы хотите сделать округление вместо усечения ответ еще проще.

int32_t result = (val*((val * 0x535A8) - 0x2675F70));
result = (result + (1L << 23)) >> 24;

Я собираюсь присоединиться к некоторым другим в предложении использовать постоянное выражение, чтобы заменить эти магические константы чем-то, что документирует, как они были получены.

static const int32_t a = (int32_t)(0.02035 * (1L << 24) + 0.5);
static const int32_t b = (int32_t)(2.4038 * (1L << 24) + 0.5);
int32_t result = (val*((val * a) - b));

Как насчет просто масштабирования констант на 10000. Максимальное число, которое вы получите, 2035*120*120 - 24038*120 = 26419440, что намного ниже предела 2^31. Так что, возможно, нет необходимости делать настоящую бит-настройку здесь.

Как отметил Джо Хасс, ваша проблема заключается в том, что вы переносите свои биты точности в мусорную корзину.

смещение десятичных знаков на 2 или на 10 влево на самом деле не имеет значения. Просто представь, что твоя запятой не за последние но смещенная позиция. Если вы продолжаете вычислять с результатом, сдвиг на 2, вероятно, легче обрабатывать. Если вы просто хотите вывести результат, сдвиньте на десять степеней, как предложено выше, преобразуйте цифры и вставьте десятичную точку 5 символов справа.


Гивенс:

предположим, что 1 исходное уравнение: 0.02035*c*c - 2.4038 * c
тогда -70.98586 --> -71 <= result <= 5
округление f (c) до ближайшего int32_t.
Аргументы A = 0.02035 и B = 2.4038
A & B может немного измениться с последующими компиляциями, но не во время выполнения.


разрешить кодеру вводить значения, такие как 0.02035 & 2.4038. Ключевые компоненты, показанные здесь и другими масштабируйте факторы, такие как 0.02035, на некоторую степень-2, Сделайте уравнение (упрощенное в форме (A*c-B)*c) и масштабируйте результат обратно.

важные особенности:

1 при определении A и B убедитесь, что умножение с плавающей запятой времени компиляции и окончательное преобразование происходит через раунд, а не усечение. С положительными значениями,+ 0.5 добивается этого. Без округленного ответа UD_A*UD_Scaling смогл закончить вверх как раз под всем числом и усечь прочь 0.999999 когда преобразование в int32_t

2 вместо того, чтобы делать дорогостоящее разделение во время выполнения, мы делаем >> (правый сдвиг). Добавив половину делителя (как предложил @Joe Hass), перед разделением мы получаем красиво округленный ответ. Важно не код / здесь some_signed_int / 4 и some_signed_int >> 2 не вокруг того же пути. С дополнением 2,>> усекает в сторону INT_MIN, тогда как / усекает к 0.

#define UD_A          (0.02035)
#define UD_B          (2.4038)
#define UD_Shift      (24)
#define UD_Scaling    ((int32_t) 1 << UD_Shift)
#define UD_ScA        ((int32_t) (UD_A*UD_Scaling + 0.5))
#define UD_ScB        ((int32_t) (UD_B*UD_Scaling + 0.5))

for (int32_t val = 1; val <= 120; val++) {
  int32_t result = ((UD_A*val - UD_B)*val + UD_Scaling/2) >> UD_Shift; 
  printf("%" PRId32 "%" PRId32 "\n", val, result);
}

пример различия:

val,   OP equation,  OP code, This code
  1,      -2.38345,       -3,       -2
 54,     -70.46460,      -71,      -70
120,       4.58400,        4,        5

это новый ответ. Мой старый +1 ответ удален.


если вход r использует максимум 7 бит, и у вас есть 32 бит, то лучше всего сдвинуть все на столько бит, сколько возможно, и работать с этим:

int32_t result;
result = (val * (int32_t)(0.02035 * 0x1000000)) - (int32_t)(2.4038 * 0x1000000);
result >>= 8; // make room for another 7 bit multiplication
result *= val;
result >>= 16;

постоянное преобразование будет выполняться оптимизирующим компилятором во время компиляции.