Comment deux versions de la même fonction, ne différant que l'une étant en ligne et l'autre non, peuvent-elles renvoyer des valeurs différentes? Voici un code que j'ai écrit aujourd'hui et je ne suis pas sûr de son fonctionnement.
#include <cmath>
#include <iostream>
bool is_cube(double r)
{
return floor(cbrt(r)) == cbrt(r);
}
bool inline is_cube_inline(double r)
{
return floor(cbrt(r)) == cbrt(r);
}
int main()
{
std::cout << (floor(cbrt(27.0)) == cbrt(27.0)) << std::endl;
std::cout << (is_cube(27.0)) << std::endl;
std::cout << (is_cube_inline(27.0)) << std::endl;
}
Je m'attendrais à ce que toutes les sorties soient égales à 1
, mais il génère en fait ceci (g ++ 8.3.1, pas de drapeaux):
1
0
1
au lieu de
1
1
1
Edit: clang ++ 7.0.0 génère ceci:
0
0
0
et g ++ -Ofast ceci:
1
1
1
==
toujours un peu imprévisible avec des valeurs en virgule flottante?-Ofast
option, qui permet de telles optimisations?cbrt(27.0)
la valeur de0x0000000000000840
tandis que la bibliothèque standard retourne0x0100000000000840
. Les doubles diffèrent par le 16e numéro après la virgule. Mon système: archlinux4.20 x64 gcc8.2.1 glibc2.28 Vérifié avec ceci . Je me demande si gcc ou glibc a raison.Réponses:
Explication
Certains compilateurs (notamment GCC) utilisent une précision plus élevée lors de l'évaluation des expressions au moment de la compilation. Si une expression dépend uniquement d'entrées et de littéraux constants, elle peut être évaluée au moment de la compilation même si l'expression n'est pas affectée à une variable constexpr. Que cela se produise ou non dépend de:
Si une expression est explicitement fournie, comme dans le premier cas, elle a une complexité moindre et le compilateur est susceptible de l'évaluer au moment de la compilation.
De même, si une fonction est marquée en ligne, le compilateur est plus susceptible de l'évaluer au moment de la compilation, car les fonctions en ligne élèvent le seuil auquel l'évaluation peut avoir lieu.
Des niveaux d'optimisation plus élevés augmentent également ce seuil, comme dans l'exemple -Ofast, où toutes les expressions sont évaluées à true sur gcc en raison d'une évaluation plus précise au moment de la compilation.
Nous pouvons observer ce comportement ici sur l'explorateur du compilateur. Lorsqu'elle est compilée avec -O1, seule la fonction marquée en ligne est évaluée au moment de la compilation, mais à -O3 les deux fonctions sont évaluées au moment de la compilation.
-O1
: https://godbolt.org/z/u4gh0g-O3
: https://godbolt.org/z/nVK4SoNB: Dans les exemples du compilateur-explorateur, j'utilise à la
printf
place iostream car il réduit la complexité de la fonction principale, rendant l'effet plus visible.Démontrer que cela
inline
n'affecte pas l'évaluation d'exécutionNous pouvons nous assurer qu'aucune des expressions n'est évaluée au moment de la compilation en obtenant la valeur de l'entrée standard, et lorsque nous faisons cela, les 3 expressions renvoient false comme démontré ici: https://ideone.com/QZbv6X
#include <cmath> #include <iostream> bool is_cube(double r) { return floor(cbrt(r)) == cbrt(r); } bool inline is_cube_inline(double r) { return floor(cbrt(r)) == cbrt(r); } int main() { double value; std::cin >> value; std::cout << (floor(cbrt(value)) == cbrt(value)) << std::endl; // false std::cout << (is_cube(value)) << std::endl; // false std::cout << (is_cube_inline(value)) << std::endl; // false }
Contrairement à cet exemple , où nous utilisons les mêmes paramètres du compilateur mais fournissons la valeur au moment de la compilation, ce qui entraîne une évaluation plus précise au moment de la compilation.
la source
Comme observé, l'utilisation de l'
==
opérateur pour comparer des valeurs en virgule flottante a donné lieu à différentes sorties avec différents compilateurs et à différents niveaux d'optimisation.Un bon moyen de comparer les valeurs en virgule flottante est le test de tolérance relative décrit dans l'article: Les tolérances en virgule flottante revisitées .
Nous calculons d'abord la valeur
Epsilon
(de la tolérance relative ) qui dans ce cas serait:double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
Et puis utilisez-le dans les fonctions en ligne et non en ligne de cette manière:
return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
Les fonctions sont maintenant:
bool is_cube(double r) { double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon(); return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon); } bool inline is_cube_inline(double r) { double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon(); return (std::fabs(std::round(std::cbrt(r)) - std::cbrt(r)) < Epsilon); }
Maintenant, la sortie sera comme prévu (
[1 1 1]
) avec différents compilateurs et à différents niveaux d'optimisation.Démo en direct
la source
max()
appel? Par définition,floor(x)
est inférieur ou égal àx
, doncmax(x, floor(x))
sera toujours égalx
.max
est juste lefloor
de l'autre, il n'est pas obligatoire. Mais j'ai considéré un cas général où les arguments àmax
peuvent être des valeurs ou des expressions indépendantes les unes des autres.operator==(double, double)
faire exactement cela, vérifier si la différence est plus petite qu'un epsilon à l'échelle? Environ 90% des questions en virgule flottante sur SO n'existeraient pas alors.Epsilon
valeur en fonction de ses besoins particuliers.