Ответ 1
Сейчас я протестировал несколько возможных решений, в том числе странные/умные из других источников, таких как объединение 32-битного div & мод & добавить или использовать крестьянскую математику, и вот мои выводы:
Во-первых, если вы ориентируетесь только на Windows и используете VSC++, просто используйте MulDiv(). Он довольно быстрый (быстрее, чем прямое использование 64-битных переменных в моих тестах), но при этом он также точен и округляет результат для вас. Я не смог найти какой-либо превосходный метод для такого рода действий в Windows с VSC++, даже с учетом ограничений, таких как только без знака и N & lt; = D.
Однако в моем случае наличие функции с детерминированными результатами даже на разных платформах даже важнее скорости. На другой платформе, которую я использовал в качестве теста, 64-разрядное деление намного, намного медленнее, чем 32-разрядное при использовании 32-разрядных библиотек, и нет функции MulDiv() для использования. 64-разрядное деление на этой платформе занимает ~ 26x столько же, сколько 32-разрядное деление (однако 64-разрядное умножение так же быстро, как и 32-разрядная версия...).
Поэтому, если у вас есть такой случай, как я, я поделюсь лучшими результатами, которые я получил, что оказалось просто оптимизацией ответа chux.
Оба метода, о которых я расскажу ниже, используют следующую функцию (хотя встроенные функции компилятора на самом деле помогли только с MSVC в Windows):
inline u32 bitsRequired(u32 val)
{
#ifdef _MSC_VER
DWORD r = 0;
_BitScanReverse(&r, val | 1);
return r+1;
#elif defined(__GNUC__) || defined(__clang__)
return 32 - __builtin_clz(val | 1);
#else
int r = 1;
while (val >>= 1) ++r;
return r;
#endif
}
Теперь, если x - это константа размером 16 бит или меньше, и вы можете предварительно вычислить требуемые биты, я нашел лучшие результаты по скорости и точности этой функции:
u32 multConstByPropFrac(u32 x, u32 nMaxBits, u32 n, u32 d)
{
//assert(nMaxBits == 32 - bitsRequired(x));
//assert(n <= d);
const int bitShift = bitsRequired(n) - nMaxBits;
if( bitShift > 0 )
{
n >>= bitShift;
d >>= bitShift;
}
// Remove the + d/2 part if don't need rounding
return (x * n + d/2) / d;
}
На платформе с медленным 64-разрядным делением вышеуказанная функция работала в ~ 16,75 раза быстрее, чем return ((u64)x * n + d/2) / d;
, и со средней точностью 99,999981% (сравнивая разницу в возвращаемом значении с ожидаемым в диапазоне х, т.е. возвращая + / -1 от ожидаемого, когда x равно 2048, будет 100 - (1/2048 * 100) = точность 99,95%) при тестировании его с миллионом или около того рандомизированных входных данных, где примерно половина из них обычно была бы переполнением. В худшем случае точность составила 99,951172%.
Для общего случая использования я нашел лучшие результаты из следующих (и без необходимости ограничивать загрузку N & lt; = D!):
u32 scaleToFraction(u32 x, u32 n, u32 d)
{
u32 bits = bitsRequired(x);
int bitShift = bits - 16;
if( bitShift < 0 ) bitShift = 0;
int sh = bitShift;
x >>= bitShift;
bits = bitsRequired(n);
bitShift = bits - 16;
if( bitShift < 0 ) bitShift = 0;
sh += bitShift;
n >>= bitShift;
bits = bitsRequired(d);
bitShift = bits - 16;
if( bitShift < 0 ) bitShift = 0;
sh -= bitShift;
d >>= bitShift;
// Remove the + d/2 part if don't need rounding
u32 r = (x * n + d/2) / d;
if( sh < 0 )
r >>= (-sh);
else //if( sh > 0 )
r <<= sh;
return r;
}
На платформе с медленным 64-разрядным делением вышеуказанная функция работала в ~ 18,5 раза быстрее, чем при использовании 64-разрядных переменных, со средним значением 99,999426% и точностью наихудшего случая 99,947479%.
Я смог добиться большей скорости или большей точности, путаясь со сдвигом, например, пытаясь не переключаться полностью до 16-битного режима, если это не было строго необходимо, но любое увеличение скорости приводило к высокой стоимости в точности и наоборот.
Ни один из других протестированных мною методов не приблизился даже к той же скорости или точности, большинство из которых медленнее, чем просто использование 64-битного метода или с огромными потерями в точности, поэтому не стоит углубляться в них.
Очевидно, нет гарантии, что кто-то еще получит аналогичные результаты на других платформах!
ОБНОВЛЕНИЕ: Заменить некоторые хитрые хаки с простым кодом, который на самом деле работает быстрее в любом случае, позволяя компилятору делать свою работу.