Округление целого числа без использования 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;
постоянное преобразование будет выполняться оптимизирующим компилятором во время компиляции.