GLSL(330) по модулю возвращает неожиданное значение

В настоящее время я работаю с GLSL 330 и наткнулся на странное поведение функции mod(). Я работаю под Windows 8 с Radeon HD 6470M. Я не могу воссоздать это поведение на своем настольном ПК с Windows 7 и GeForce GTX 260.

Вот мой тестовый код:

float testvalf = -126;
vec2 testval = vec2(-126, -126);

float modtest1 = mod(testvalf, 63.0);   //returns 63
float modtest2 = mod(testval.x, 63.0);  //returns 63
float modtest3 = mod(-126, 63.0);   //returns 0

Редактировать:

Вот еще несколько результатов тестов, сделанных по предложению IceCools ниже.

int y = 63;
int inttestval = -126;
ivec2 intvectest(-126, -126);
float floattestval = -125.9;


float modtest4 = mod(inttestval, 63); //returns 63
float modtest5 = mod(intvectest, 63); //returns vec2(63.0, 63.0)
float modtest6 = mod(intvectest.x, 63); //returns 63
float modtest7 = mod(floor(floattestval), 63); //returns 63
float modtest8 = mod(inttestval, y); //returns 63
float modtest9 = mod(-126, y); //returns 63

Я обновил свои драйверы и снова проверил, те же результаты. Еще раз не воспроизводится на рабочем столе. Согласно документам GLSL на mod возможные комбинации параметров: GenType, float) и (GenType, GenType) (без двойного числа, так как у нас ‹ 4.0). Также возвращаемый тип принудительно плавает, но это не должно иметь значения для этой проблемы.


person wacki    schedule 22.05.2013    source источник
comment
Не могли бы вы проверить, возвращает ли int x = -126/63 -3? Это звучит странно, но деление int не так уж и тривиально, возможно, реализация ATI понизила его до деления с плавающей запятой и возвращает результат обратно к int. И как мы видим в modtest1 (int)(-126.0/63.0) это -3   -  person IceCool    schedule 23.05.2013
comment
Я только что протестировал -126/63, присвоив его переменной и просто проверив возвращаемое значение в предложении if. Оба раза результат был -2. Я также протестировал int((-126.0/63.0)), который также возвращает -2, как и ожидалось. У меня постепенно заканчиваются идеи, ваша ошибка точности с плавающей запятой имеет наиболее логичный смысл, и я не могу придумать ничего другого, что могло бы ее вызвать.   -  person wacki    schedule 24.05.2013
comment
Похоже, что происходит нечто иное, чем проблема точности, поскольку для ВСЕХ возможных значений точности и ВСЕХ возможных значений x и y инвариант !(abs(mod(x, y)) >= abs(y)) должен ВСЕГДА быть истинным. Ломать то, что ломает мод для графических программ. (странное !...›= таково, что оно всегда верно и для значений NaN).   -  person Chris Dodd    schedule 02.09.2019


Ответы (3)


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

Кстати о модуле: Обратите внимание, что вызываются 2 разные функции:

Первые две строки:

float mod(float, float);

Последняя строка:

int mod(int, float);

Если я прав, мод рассчитывается так:

genType mod(genType x, float y){
return x - y*floor(x/y);
}

Теперь обратите внимание, что если x/y оценивает -2,0, он вернет 0, но если он оценивает как -2,00000001, то будет возвращено 63,0. Эта разница не является невозможной между делением int/float и float/float.

Таким образом, причина может заключаться только в том, что вы используете смешанные целые числа и числа с плавающей запятой.

person IceCool    schedule 22.05.2013
comment
Это хороший момент, если я правильно помню, я думал, что уже тестировал (int, int) как параметры с тем же результатом. Хотя могу ошибаться. Завтра еще раз проверю и отпишусь о результатах. - person wacki; 23.05.2013

Думаю, я нашел ответ.

Я ошибся в том, что ключевое слово mangssl для genType не означает универсальный тип, как в шаблоне C++.

GenType — это сокращение для float, vec2, vec3 и vec4 (см. ссылка — ctrl+f genType ).

Кстати, имя genType выглядит так:

genType - floats
genDType - doubles
genIType - ints
genBType - bools 

Это означает, что genType mod(genType, float) подразумевает отсутствие такой функции, как int mod(int, float).

Весь приведенный выше код вызывал float mod(float, float) (к счастью, для параметров функции существует неявное приведение типов, поэтому mod(int, int) тоже работает, но на самом деле вызывается mod(float, float)).

Просто как доказательство:

int x = mod(-126, 63);
Doesn't compile: error C7011: implicit cast from "float" to "int"

Это не работает только потому, что возвращает число с плавающей запятой, поэтому оно работает так:

float x = mod(-126, 63);

Поэтому float mod(float, float) называется.

Итак, мы вернулись к исходной проблеме:

  • деление с плавающей запятой неточное
  • int to float cast неточен
  • Это не должно быть проблемой на большинстве графических процессоров, так как числа с плавающей запятой считаются равными, если разница между ними меньше 10 ^ -5 (это может варьироваться в зависимости от оборудования, но это относится к моему графическому процессору). Таким образом, пол (-2,0000001) равен -2. Высокие поплавки гораздо более точны, чем это.
  • Следовательно, либо вы не используете highp float (тогда precision highp float; должен это исправить), либо ваш GPU имеет более строгие ограничения на равенство float, либо некоторые функции возвращают менее точное значение.
  • Если ничего не помогает, попробуйте:

    #extension BlackMagic : enable

person IceCool    schedule 24.05.2013
comment
Очень хорошая информация, я изучу точность с плавающей запятой моего графического процессора, когда вернусь домой. Может быть, даже обратиться в службу поддержки AMD, если там что-то для этого есть. Но даже если точность является ответом, это все равно не объясняет, почему вы получаете 63 по модулю (переменная, 63,0) и 0 по модулю (-126, 63,0). - person wacki; 24.05.2013

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

Если это произойдет, все определенные вами переменные будут средними, однако числа, введенные в код, все равно останутся высокими. Рассмотрим этот код:

precision mediump float;
float x = 0.4121551, y = 0.4121552;
x == y; // true
0.4121551 == 0.4121552; // false, as highp they still differ.

Так что mod(-126,63.0) все еще может быть достаточно точным, чтобы возвращать правильное значение, поскольку его работа с высокой точностью плавает, однако, если вы укажете переменную (как и во всех других случаях), которая будет только средней, у функции не будет достаточной точности, чтобы вычислить правильное значение, и когда вы посмотрите на свои тесты, вот что происходит:

  • Все функции, которые принимают хотя бы одну переменную, недостаточно точны.
  • Единственный вызов функции, который принимает 2 введенных числа, возвращает правильное значение.
person IceCool    schedule 24.05.2013