Russian Qt Forum

Программирование => С/C++ => Тема начата: Alex Custov от Июнь 27, 2018, 12:22



Название: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 27, 2018, 12:22
На 32-битной версии MSVC и Qt 5.9.2 наблюдается такой интересный баг... В 64-битной версии такого нет.

Коротко:
Код
C++ (Qt)
   float f1 = QVector2D::dotProduct(v1,v2); // возвращает float
   double d1 = f1;
   double d2 = QVector2D::dotProduct(v1,v2); // повторяем

Результат: Значения d1 и d2 различаются после пятнадцатого знака после запятой, а именно: d1 = 1.00000000000000000000 d2 = 1.00000000000000355271. Какая-то сумасшедшая оптимизация? Никаких своих флагов в .pro файл я не добавлял.

Подлиннее:
Код
C++ (Qt)
#include <QtCore>
#include <QtGui>
 
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   QTextStream ts(stdout, QIODevice::WriteOnly);
   QVector2D v1(8.10837e-09, -1);
   QVector2D v2(4.45843e-07, -1);
 
   float f1 = QVector2D::dotProduct(v1,v2);
   double d1 = f1;
   double d2 = QVector2D::dotProduct(v1,v2);
 
   ts.setRealNumberNotation(QTextStream::FixedNotation);
   ts.setRealNumberPrecision(20);
   ts
           << v1.x() << " "
           << v1.y() << " "
           << v2.x() << " "
           << v2.y() << " " << endl
           << f1 << " " << d1 << " " << d2 << endl;
   ts.flush();
 
   return 0;
}
 

Выхлоп:

Цитировать
0.00000000810836997545 -1.00000000000000000000 0.00000044584299985218 -1.00000000000000000000
1.00000000000000000000 1.00000000000000000000 1.00000000000000355271 <--- f, d1 и d2


Проверьте пожалуйста на MSVC 2017 и MSVC 2013 в 32-битной версии. Какие есть гипотезы? :)


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: ViTech от Июнь 27, 2018, 12:55
Мне в винду сейчас лезть далеко, но такие варианты не проверяли:
Код
C++ (Qt)
   float f1 = QVector2D::dotProduct(v1,v2);
   float f2 = QVector2D::dotProduct(v1,v2);
   double d1 = f1;
   double d2 = f2;
   double d3 = QVector2D::dotProduct(v1,v2);
   double d4 = QVector2D::dotProduct(v1,v2);
 

Цель - повторяемость и стабильность результатов. Посмотреть на ассемблерный код. Посмотреть на d1, d2, d3, d4 в памяти во время отладки, может вывод в поток чудит. И сборка release или debug?


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 27, 2018, 14:24
повторяемость 100%, это не рандом. Вывод в поток верен, т.к. в более полном коде из проекта я ещё сравниваю полученное double значение с нужным мне, и первый double проходит проверку, а второй - нет. На release и release+debug symbols работает одинаково неправильно. Я подозреваю, что проблема с оптимизацией RVO и внутренним представлением float. На 64-бит не воспроизводится вероятно потому, что в 64-бит ошибки float меньше.


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 27, 2018, 14:54
GCC в Ubuntu 16.04 работает нормально, d1 и d2 полностью равны


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: deMax от Июнь 27, 2018, 16:21
В первом случае вы убиваете точность, real(double) -> float -> double, float 24бита на минтиссу 16000000. а у вас 15ый знак
Вопрос, откуда погрешность берется? Попробуйте ручками скалярное произведение посчитать, с типами float, double.


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 27, 2018, 16:28
В первом случае вы убиваете точность, real(double) -> float -> double

Во втором также. В Qt5 QVector2D принимает и возвращает float. Поэтому и во втором случае идёт преобразование входных double литералов во float, потом dotProduct() считает результат также используя значения float, и возвращает также float!


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Igors от Июнь 27, 2018, 16:35
А что тут обсуждать? Баг явно, у MSVC они всегда были и будут. Придется отказаться от точного сравнения напр
Код
C++ (Qt)
if (fabs(d1 - d2) < FUDGE_FACTOR)  // считаем ==
 
Чисто для спортивного интереса проверить так
Код
C++ (Qt)
QVector2D v1(8.10837e-09f, -1.0f);
QVector2D v2(4.45843e-07f, -1.0f);


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 27, 2018, 16:37
deMax Продолжаем мистику! :)

Код
C++ (Qt)
int main(int argc, char *argv[])
{
   QCoreApplication app(argc, argv);
 
   QTextStream ts(stdout, QIODevice::WriteOnly);
 
   QVector2D v1(8.10837e-09, -1);
   QVector2D v2(4.45843e-07, -1);
 
   // считаем руками
   const double d1 = v1.x() * v2.x() + v1.y() * v2.y();
   // считаем стандартно
   const double d2 = QVector2D::dotProduct(v1,v2);
 
   ts.setRealNumberNotation(QTextStream::FixedNotation);
   ts.setRealNumberPrecision(20);
   ts
           << v1.x() << " "
           << v1.y() << " "
           << v2.x() << " "
           << v2.y() << " " << endl
           << d1 << " " << d2 << endl;
   ts.flush();
 
   return 0;
}

Выхлоп разный!

Цитировать
1.00000000000000000000 1.00000000000000355271


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 27, 2018, 16:39
Igors суффикс f не помог. Я проверил - внутри QVector2D данных хранятся правильно и одинаково как в 32-бит, так и в 64-бит версии. Проблема появляется только при вызове функции dotProduct (см. код выше) в 32-бит версии. Когда считаем руками - всё нормально :)


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: kuzulis от Июнь 27, 2018, 17:50
Запости баг об хреновеньком QVector2D::dotProduct(), если ручками все нормально :)


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Авварон от Июнь 27, 2018, 18:25
Igors суффикс f не помог. Я проверил - внутри QVector2D данных хранятся правильно и одинаково как в 32-бит, так и в 64-бит версии. Проблема появляется только при вызове функции dotProduct (см. код выше) в 32-бит версии. Когда считаем руками - всё нормально :)

Что дизассемблирование говорит? С мелкомягкого компилятора станется возвращать результат в дабле.


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 27, 2018, 19:27
Запости баг об хреновеньком QVector2D::dotProduct(), если ручками все нормально :)

Исходники dotProduct() - это копия того что делаю руками: https://code.woboq.org/qt5/qtbase/src/gui/math3d/qvector2d.cpp.html#_ZN9QVector2D10dotProductERKS_S1_ . Проблема явно не в коде как в таковом. Проблема с конпилятором как мне кажется.


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 27, 2018, 19:29
Авварон дизассемблирование пока не смотрел


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: ViTech от Июнь 28, 2018, 00:18
Цитировать
Оптимизирующий компилятор Microsoft (R) C/C++ версии 19.14.26430 для x86
(C) Корпорация Майкрософт (Microsoft Corporation).  Все права защищены.
тот же вывод:
Цитировать
0.00000000810836997545 -1.00000000000000000000 0.00000044584299985218 -1.00000000000000000000
1.00000000000000000000 1.00000000000000000000 1.00000000000000355271

Кусок асма (без оптимизации):
Цитировать
; 12   :     float  f1 = QVector2D::dotProduct(v1, v2);

   lea   edx, DWORD PTR _v2$[ebp]
   push   edx
   lea   eax, DWORD PTR _v1$[ebp]
   push   eax
   call   DWORD PTR __imp_?dotProduct@QVector2D@@SAMABV1@0@Z
   add   esp, 8
   fstp   DWORD PTR _f1$[ebp]

; 13   :     double d1 = f1;

   cvtss2sd xmm0, DWORD PTR _f1$[ebp]
   movsd   QWORD PTR _d1$[ebp], xmm0

; 14   :     double d2 = QVector2D::dotProduct(v1, v2);

   lea   ecx, DWORD PTR _v2$[ebp]
   push   ecx
   lea   edx, DWORD PTR _v1$[ebp]
   push   edx
   call   DWORD PTR __imp_?dotProduct@QVector2D@@SAMABV1@0@Z
   add   esp, 8
   fstp   QWORD PTR _d2$[ebp]

Я не спец в ассемблере, но настораживают fstp:
Цитировать
   fstp   DWORD PTR _f1$[ebp]
        ...
   fstp   QWORD PTR _d2$[ebp]

Похоже в случае с float он снимает со стека float и потом его переводит в double, а в случае с double сразу double и снимает, хотя dotProduct() float возвращает. Может тут собака порылась :).


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: ViTech от Июнь 28, 2018, 00:29
Костыль:
Код
C++ (Qt)
   double d2 = 0.0f + QVector2D::dotProduct(v1, v2);

Вывод (без оптимизации):
Код:
0.00000000810836997545 -1.00000000000000000000 0.00000044584299985218 -1.00000000000000000000 
1.00000000000000000000 1.00000000000000000000 1.00000000000000000000

:)

Если включить оптимизацию, то всё возвращается на круги своя.


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Авварон от Июнь 28, 2018, 01:05

Похоже в случае с float он снимает со стека float и потом его переводит в double, а в случае с double сразу double и снимает, хотя dotProduct() float возвращает. Может тут собака порылась :).


Да, так и есть. А получится войти в dotProduct и посмотреть, что он возвращает реально? Мб кутешники криво собрали? (хотя qreal уже давно везде double, а тут явно указан float). То есть версии на 5.2 я бы поставил на косяк версий, а тут даже не знаю...


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Igors от Июнь 28, 2018, 10:01
Как охотно постится предмет по существу бесполезный :) Что баг компилятора - очевидно по первым строкам стартового поста. Тогда что мы хотим? Найти точно ту (неправильную) команду? Ну допустим даже нашли, проблему это не решит, компилятор не изменить. Найти кукую-то опцию компилятора? Шансов мало и это решается "методом втыка"

Использовать точное сравнение для double можно, но случай это довольно редкий, практически любой код пестрит чем-то типа NEAR_ONE, NEAR_ZERO и.т.п. А конкретно в случае скалярного произведения требование  точного совпадения вообще ничем не оправдано. Так может просто найти все вызовы dotProduct и вставить проверки с каким-то epsilon ?


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Пантер от Июнь 28, 2018, 10:11
Как охотно постится предмет по существу бесполезный :) Что баг компилятора - очевидно по первым строкам стартового поста. Тогда что мы хотим? Найти точно ту (неправильную) команду? Ну допустим даже нашли, проблему это не решит, компилятор не изменить. Найти кукую-то опцию компилятора? Шансов мало и это решается "методом втыка"

Использовать точное сравнение для double можно, но случай это довольно редкий, практически любой код пестрит чем-то типа NEAR_ONE, NEAR_ZERO и.т.п. А конкретно в случае скалярного произведения требование  точного совпадения вообще ничем не оправдано. Так может просто найти все вызовы dotProduct и вставить проверки с каким-то epsilon ?
Если это баг компилятора, то нужно майкрософтовцам об этом сообщить. Но возможно это баг сборки Кьюта. И тема намного полезнее твоих тем по решению твоих проблем.


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Alex Custov от Июнь 28, 2018, 10:56
В MinGW 5.3.0 на Windows и GCC 5.3.1 на Linux бага нет. И в MinGW и в GCC получается строго 1.00000.... безо всяких хвостов-сюрпризов  в мантиссе.


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: Igors от Июнь 28, 2018, 11:14
В MinGW 5.3.0 на Windows и GCC 5.3.1 на Linux бага нет. И в MinGW и в GCC получается строго 1.00000.... безо всяких хвостов-сюрпризов  в мантиссе.
Спокойнее к этому относитесь, не тратьте время на всякие проверки. Ну да, это в такой-о версии MSVC не работает, у меня подобных ситуевин было с десяток (если не 2). Это ж чудесный компилятор, там возможно мноогое  (что и не снилось стандарту :)). Как говорится "умный гору обойдет".


Название: Re: MSVC 2015 чудит: float -> double
Отправлено: ViTech от Июнь 28, 2018, 13:02
Да, так и есть. А получится войти в dotProduct и посмотреть, что он возвращает реально? Мб кутешники криво собрали? (хотя qreal уже давно везде double, а тут явно указан float). То есть версии на 5.2 я бы поставил на косяк версий, а тут даже не знаю...

В debug сборке вычисляется нормально (чистая 1.00000000000000000000).

Если упростить пример до:
Код
C++ (Qt)
#include <QtCore>
#include <QtGui>
 
int main(int argc, char* argv[])
{
   QVector2D   v1(8.10837e-09, -1);
   QVector2D   v2(4.45843e-07, -1);
 
   volatile float  f1 = QVector2D::dotProduct(v1, v2);
   volatile double d1 = f1;
   volatile double d2 = QVector2D::dotProduct(v1, v2);
 
   return 0;
}

То для него release сборка с оптимизацией выглядит так (если я всё правильно сделал):
Код:
        4    {
0x1361000                    sub     esp,18h
        8        volatile float  f1 = QVector2D::dotProduct(v1, v2);
0x1361003  <+0x0003>         lea     eax,[esp+8]
        5        QVector2D   v1(8.10837e-09, -1);
0x1361007  <+0x0007>         mov     dword ptr [esp+10h],320B4CFDh
        8        volatile float  f1 = QVector2D::dotProduct(v1, v2);
0x136100f  <+0x000f>         push    eax
0x1361010  <+0x0010>         lea     eax,[esp+14h]
        5        QVector2D   v1(8.10837e-09, -1);
0x1361014  <+0x0014>         mov     dword ptr [esp+18h],0BF800000h
        8        volatile float  f1 = QVector2D::dotProduct(v1, v2);
0x136101c  <+0x001c>         push    eax
        6        QVector2D   v2(4.45843e-07, -1);
0x136101d  <+0x001d>         mov     dword ptr [esp+10h],34EF5C32h
0x1361025  <+0x0025>         mov     dword ptr [esp+14h],0BF800000h
        8        volatile float  f1 = QVector2D::dotProduct(v1, v2);
0x136102d  <+0x002d>         call    dword ptr [FloatDouble!_imp_?dotProductQVector2DSAMABV1 (01362034)]
0x1361033  <+0x0033>         fstp    dword ptr [esp+8]
        9        volatile double d1 = f1;
0x1361037  <+0x0037>         movss   xmm0,dword ptr [esp+8]
        10        volatile double d2 = QVector2D::dotProduct(v1, v2);
0x136103d  <+0x003d>         lea     eax,[esp+10h]
0x1361041  <+0x0041>         cvtps2pd xmm0,xmm0
0x1361044  <+0x0044>         push    eax
0x1361045  <+0x0045>         lea     eax,[esp+1Ch]
0x1361049  <+0x0049>         push    eax
0x136104a  <+0x004a>         movsd   mmword ptr [esp+10h],xmm0
0x1361050  <+0x0050>         call    dword ptr [FloatDouble!_imp_?dotProductQVector2DSAMABV1 (01362034)]
0x1361056  <+0x0056>         fstp    qword ptr [esp+20h]
        12        return 0;
0x136105a  <+0x005a>         xor     eax,eax
        13    }
0x136105c  <+0x005c>         add     esp,28h
0x136105f  <+0x005f>         ret

Исходник QVector2D::dotProduct (Qt 5.10.0):
Код
C++ (Qt)
float QVector2D::dotProduct(const QVector2D& v1, const QVector2D& v2)
{
   return v1.xp * v2.xp + v1.yp * v2.yp;
}

Асм (Qt 5.10.0 MSVC 2015 32-bit):
Код:
        Qt5Gui!QVector2D::dotProduct [c:\users\qt\work\qt\qtbase\src\gui\math3d\qvector2d.cpp @ 346]:
0xfc672c0                    mov     ecx,dword ptr [esp+4]
0xfc672c4  <+0x0004>         mov     eax,dword ptr [esp+8]
0xfc672c8  <+0x0008>         fld     dword ptr [ecx+4]
0xfc672cb  <+0x000b>         fmul    dword ptr [eax+4]
0xfc672ce  <+0x000e>         fld     dword ptr [ecx]
0xfc672d0  <+0x0010>         fmul    dword ptr [eax]
0xfc672d2  <+0x0012>         faddp   st(1),st
0xfc672d4  <+0x0014>         ret

Так что, кто понимает тонкости в этих закорючках, можете расшифровывать :).

PS. Для сравнения асм debug сборки:
Код:
        4    {
0x901000                    push    ebp
0x901001  <+0x0001>         mov     ebp,esp
0x901003  <+0x0003>         sub     esp,24h
        5        QVector2D   v1(8.10837e-09, -1);
0x901006  <+0x0006>         push    ecx
0x901007  <+0x0007>         movss   xmm0,dword ptr [FloatDouble!_realbf800000 (00904a48)]
0x90100f  <+0x000f>         movss   dword ptr [esp],xmm0
0x901014  <+0x0014>         push    ecx
0x901015  <+0x0015>         movss   xmm0,dword ptr [FloatDouble!_real (00904a40)]
0x90101d  <+0x001d>         movss   dword ptr [esp],xmm0
0x901022  <+0x0022>         lea     ecx,[ebp-14h]
0x901025  <+0x0025>         call    dword ptr [FloatDouble!_imp_??0QVector2DQAEMMZ (00903038)]
        6        QVector2D   v2(4.45843e-07, -1);
0x90102b  <+0x002b>         push    ecx
0x90102c  <+0x002c>         movss   xmm0,dword ptr [FloatDouble!_realbf800000 (00904a48)]
0x901034  <+0x0034>         movss   dword ptr [esp],xmm0
0x901039  <+0x0039>         push    ecx
0x90103a  <+0x003a>         movss   xmm0,dword ptr [FloatDouble!_real (00904a44)]
0x901042  <+0x0042>         movss   dword ptr [esp],xmm0
0x901047  <+0x0047>         lea     ecx,[ebp-0Ch]
0x90104a  <+0x004a>         call    dword ptr [FloatDouble!_imp_??0QVector2DQAEMMZ (00903038)]
        8        volatile float  f1 = QVector2D::dotProduct(v1, v2);
0x901050  <+0x0050>         lea     eax,[ebp-0Ch]
0x901053  <+0x0053>         push    eax
0x901054  <+0x0054>         lea     ecx,[ebp-14h]
0x901057  <+0x0057>         push    ecx
0x901058  <+0x0058>         call    dword ptr [FloatDouble!_imp_?dotProductQVector2DSAMABV1 (00903034)]
0x90105e  <+0x005e>         add     esp,8
0x901061  <+0x0061>         fstp    dword ptr [ebp-4]
        9        volatile double d1 = f1;
0x901064  <+0x0064>         movss   xmm0,dword ptr [ebp-4]
0x901069  <+0x0069>         cvtss2sd xmm0,xmm0
0x90106d  <+0x006d>         movsd   mmword ptr [ebp-1Ch],xmm0
        10        volatile double d2 = QVector2D::dotProduct(v1, v2);
0x901072  <+0x0072>         lea     edx,[ebp-0Ch]
0x901075  <+0x0075>         push    edx
0x901076  <+0x0076>         lea     eax,[ebp-14h]
0x901079  <+0x0079>         push    eax
0x90107a  <+0x007a>         call    dword ptr [FloatDouble!_imp_?dotProductQVector2DSAMABV1 (00903034)]
0x901080  <+0x0080>         add     esp,8
0x901083  <+0x0083>         fstp    qword ptr [ebp-24h]
        12        return 0;
0x901086  <+0x0086>         xor     eax,eax
        13    }
0x901088  <+0x0088>         mov     esp,ebp
0x90108a  <+0x008a>         pop     ebp
0x90108b  <+0x008b>         ret

Код:
        Qt5Guid!QVector2D::dotProduct [c:\users\qt\work\qt\qtbase\src\gui\math3d\qvector2d.cpp @ 346]:
0x67ff1e10                    push    ebp
0x67ff1e11  <+0x0001>         mov     ebp,esp
0x67ff1e13  <+0x0003>         push    ecx
0x67ff1e14  <+0x0004>         mov     eax,dword ptr [ebp+8]
0x67ff1e17  <+0x0007>         mov     ecx,dword ptr [ebp+0Ch]
0x67ff1e1a  <+0x000a>         movss   xmm0,dword ptr [eax]
0x67ff1e1e  <+0x000e>         mulss   xmm0,dword ptr [ecx]
0x67ff1e22  <+0x0012>         mov     edx,dword ptr [ebp+8]
0x67ff1e25  <+0x0015>         mov     eax,dword ptr [ebp+0Ch]
0x67ff1e28  <+0x0018>         movss   xmm1,dword ptr [edx+4]
0x67ff1e2d  <+0x001d>         mulss   xmm1,dword ptr [eax+4]
0x67ff1e32  <+0x0022>         addss   xmm0,xmm1
0x67ff1e36  <+0x0026>         movss   dword ptr [ebp-4],xmm0
0x67ff1e3b  <+0x002b>         fld     dword ptr [ebp-4]
0x67ff1e3e  <+0x002e>         mov     esp,ebp
0x67ff1e40  <+0x0030>         pop     ebp
0x67ff1e41  <+0x0031>         ret