2016-01-28 3 views
6

Я делаю проект, в котором я делаю RGB для преобразования яркости, и у меня есть некоторые проблемы с округлением -mno-sse2 флаг:НКУ -mno-sse2 округления

Вот код теста:

#include <stdio.h> 
#include <stdint.h> 

static double rec709_luma_coeff[3] = {0.2126, 0.7152, 0.0722}; 

int main() 
{ 
    uint16_t n = 242 * rec709_luma_coeff[0] + 242 * rec709_luma_coeff[1] + 242 * rec709_luma_coeff[2]; 

    printf("%u\n", n); 
    return 0; 
} 

И вот что я получаю:

[email protected]>gcc -mno-sse2 test.c -o test && ./test 
241 
[email protected]> gcc test.c -o test && ./test 
242 

Я полагаю, что GCC использует SSE2 оптимизации для double умножений, но то, что я не получаю поэтому оптимизированная версия будет правильным.

Кроме того, что вы рекомендуете использовать для получения более согласованных результатов, ceil() или floor()?

+5

Это не имеет никакого отношения к оптимизации. Нет SSE2 означает использование старого x87 FPU, который ** шире **, чем SSE2. В некотором смысле, результаты x87 выполняются с большей точностью, но результаты могут отличаться от результатов, сделанных с использованием SSE2 –

+0

Странно, при компиляции с флагом '-O2' проблема исчезает ... – perror

+0

Если вы включите оптимизацию, вы получите также 242 с '-mno-sse2' – jofel

ответ

0

TL: DR использовать lrint(x) или (int)rint(x) для преобразования из float в int с округлением до ближайшего, а не усечения. К сожалению, не все компиляторы эффективно используют одни и те же математические функции. См round() for float in C++


gcc -mno-sse2 должен использовать x87 для double, даже в 64-битном коде. Регистры x87 имеют внутреннюю точность 80 бит, но SSE2 использует формат IEEE binary64 (aka double) изначально в XMM-регистрах, поэтому все временные области округлены до 64-разрядных double на каждом шаге.

Проблема не такая интересная, как the double rounding problem (80 бит -> 64 бит, а затем целое число). Это также не от gcc -O0 (по умолчанию: без дополнительных оптимизаций) округление при хранении временных данных в памяти, потому что вы сделали все это в одном выражении C, поэтому он просто использует регистры x87 для всего выражения.


Это просто, что 80-битная точность приводит к результату, который чуть ниже 242,0 и усекается до 241 от Кассиопеяна float-> Int семантики, в то время как SSE2, дает результат чуть выше 242,0, которая обрежет до 242. Для x87 округление до следующего нижнего целого происходит последовательно, а не только 242 для любого ввода от 1 до 65535. (Я сделал версию вашей программы, используя atoi(argv[1]), чтобы я мог тестировать другие значения и с -O3).

Помните, что int foo = 123.99999 равно 123, потому что C использует режим округления округления (к нулю). Для неотрицательных чисел это то же самое, что и floor (который округляется к -Infinity). https://en.wikipedia.org/wiki/Floating-point_arithmetic#Rounding_modes.


double не может представлять коэффициенты точно: я напечатал их с gdb и получил: {0.21260000000000001, 0.71519999999999995, 0.0722}. Эти десятичные представления, вероятно, не являются точными представлениями значений с плавающей запятой base-2. Но они достаточно близки, чтобы увидеть, что коэффициенты составляют до 0.99999999999999996 (с использованием калькулятора произвольной точности).

Мы получаем совпадение, поскольку внутренняя точность x87 выше, чем точность коэффициентов, поэтому ошибки округления суммы в n * rec709_luma_coeff[0] и т. Д. И, суммируя результаты, составляют ~ 2^11 меньше, чем разница между суммой коэффициентов и 1,0. (64-битное значение против 53 бит).

Реальный вопрос: как удалось работать версия SSE2! Предположительно от раунда до ближайшего, даже во временном, в достаточном количестве случается движение вверх, по крайней мере, на 242. Это случается, чтобы произвести исходный ввод для большего количества случаев, чем нет, но он производит вход-1 для 5, 7, 10, 13, 14, 20 ... (252 из первых 1000 чисел от 1..1000 являются «потеряются» по версии SSE2, так что это не так, как он всегда работает либо.)


с -O3 для вашего источника, он выполняет расчет во время компиляции с расширенной точностью и дает точный результат. то есть он компилирует то же, что и printf("%u\n", n);.


И BTW вы должны использовать staticconst для вас констант так НКА может оптимизировать лучше. static намного лучше, чем простой глобальный, хотя, поскольку компилятор может видеть, что ничто в компиляторе не записывает значения или не передает их адрес нигде, поэтому он может обрабатывать их так, как если бы они были const.

 Смежные вопросы

  • Нет связанных вопросов^_^