В численных вычислениях часто необходимо масштабировать числа, чтобы они находились в безопасном диапазоне.
Например, вычисление евклидова расстояния: sqrt(a^2+b^2)
. Здесь, если величина a
или b
слишком мала/велика, может произойти недополнение/переполнение.
Обычный подход к решению этой проблемы состоит в том, чтобы разделить числа на наибольшее число. Однако это решение:
- медленный (деление идет медленно)
- вызывает небольшую дополнительную неточность
Поэтому я подумал, что вместо того, чтобы делить на наибольшее число величины, давайте умножим его на близкое обратное число степени двойки. Это кажется лучшим решением, так как:
- умножение намного быстрее деления
- лучшая точность, так как умножение на число степени 2 является точным
Итак, я хотел бы создать небольшую служебную функцию с такой логикой (под ^
я имею в виду возведение в степень):
void getScaler(double value, double &scaler, double &scalerReciprocal) {
int e = <exponent of value>;
if (e<-1022) { scaler=2^-1022; scalerReciprocal = 2^1022; }
} else if (e>1022) { scaler=2^1022; scalerReciprocal = 2^-1022; }
} else { scaler=2^e; scalerReciprocal = 2^(2046-e); }
}
Эта функция должна возвращать нормализованные числа scaler
и scalerReciprocal
, оба являются числами степени 2, где scaler
близко к value
, а scalerReciprocal
является обратным scaler
.
Максимально допустимые показатели степени для scaler
/scaleReciprocal
равны -1022..1022
(я не хочу работать с субнормальными scaler
, так как субнормальные числа могут быть медленными).
Как это сделать быстро? Можно ли это сделать с помощью операций с плавающей запятой? Или я должен извлечь экспоненту из value
и использовать простые if
для выполнения логики? Есть ли какой-то трюк, чтобы сделать сравнение с (-) 1022 быстрым (поскольку диапазон симметричен)?
Примечание. scaler
не обязательно должно быть ближайшей степенью двойки. Если в этом нуждается какая-то логика, scaler
может быть небольшой степенью двойки от ближайшего значения.
__m256 _mm256_castps128_ps256 (__m128 a)
. К сожалению, за исключением clang, большинство компиляторов действительно тратят впустую инструкцию, расширяющую ноль для_mm_set_sd(t)
. Как объединить скаляр в вектор без того, чтобы компилятор тратил впустую инструкцию, обнуляющую верхние элементы? Ограничение дизайна во встроенных функциях Intel?. Просто используйте версию с каламбуром типа union, я думаю, что все основные компиляторы x86 su 22.01.2019ANDPS
/ORPS
даже в цикле и фактически извлекают в GP regs сmovq
. В x86-64 System V нет регистров XMM с сохранением вызовов, поэтому они не могли поднять константы, но использование их из памяти все равно было бы выигрышем. Будем надеяться, что компиляторы будут автоматически векторизовать чистую версию C. 22.01.2019get_scale
немного более шаткая, чем моя, тем не менее, она может хорошо работать для вашего приложения. 23.01.2019sqrt(a^2+b^2)
и тому подобное. Оставить большее значение в диапазоне[1..2)
вместо[0.5 .. 1)
вполне нормально. ИспользованиеOR
для применения минимума — действительно хорошая идея для этого варианта использования. 23.01.2019