Como duas versões da mesma função, diferindo apenas em uma sendo embutida e a outra não, podem retornar valores diferentes? Aqui está um código que escrevi hoje e não tenho certeza de como funciona.
#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;
}
Eu esperaria que todas as saídas fossem iguais a 1
, mas na verdade ele produz isso (g ++ 8.3.1, sem sinalizadores):
1
0
1
ao invés de
1
1
1
Editar: o clang ++ 7.0.0 resulta em:
0
0
0
e g ++ -Fast this:
1
1
1
==
sempre é um pouco imprevisível com valores de ponto flutuante?-Ofast
opção que permite essas otimizações?cbrt(27.0)
o valor de0x0000000000000840
enquanto a biblioteca padrão retorna0x0100000000000840
. As duplas diferem no 16º número após a vírgula. Meu sistema: archlinux4.20 x64 gcc8.2.1 glibc2.28 Verificado com isso . Gostaria de saber se gcc ou glibc está certo.Respostas:
Explicação
Alguns compiladores (notavelmente GCC) usam maior precisão ao avaliar expressões em tempo de compilação. Se uma expressão depende apenas de entradas e literais constantes, ela pode ser avaliada em tempo de compilação, mesmo se a expressão não for atribuída a uma variável constexpr. Se isso ocorre ou não depende de:
Se uma expressão for fornecida explicitamente, como no primeiro caso, ela terá uma complexidade menor e o compilador provavelmente irá avaliá-la no momento da compilação.
Da mesma forma, se uma função for marcada em linha, é mais provável que o compilador avalie-a em tempo de compilação porque as funções em linha aumentam o limite no qual a avaliação pode ocorrer.
Níveis de otimização mais altos também aumentam esse limite, como no exemplo -Ofast, onde todas as expressões são avaliadas como verdadeiras no gcc devido à avaliação de tempo de compilação de maior precisão.
Podemos observar esse comportamento aqui no compilador explorer. Quando compilado com -O1, apenas a função marcada inline é avaliada em tempo de compilação, mas em -O3 ambas as funções são avaliadas em tempo de compilação.
-O1
: https://godbolt.org/z/u4gh0g-O3
: https://godbolt.org/z/nVK4SoNB: Nos exemplos do compilador-explorador, eu uso
printf
iostream porque reduz a complexidade da função principal, tornando o efeito mais visível.Demonstrando que
inline
não afeta a avaliação do tempo de execuçãoPodemos garantir que nenhuma das expressões seja avaliada em tempo de compilação obtendo o valor da entrada padrão e, quando fazemos isso, todas as 3 expressões retornam falso, conforme demonstrado aqui: 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 }
Compare com este exemplo , onde usamos as mesmas configurações do compilador, mas fornecemos o valor em tempo de compilação, resultando em uma avaliação de tempo de compilação de maior precisão.
fonte
Conforme observado, o uso do
==
operador para comparar valores de ponto flutuante resultou em diferentes saídas com diferentes compiladores e em diferentes níveis de otimização.Uma boa maneira de comparar valores de ponto flutuante é o teste de tolerância relativa descrito no artigo: Tolerâncias de ponto flutuante revisitadas .
Primeiro calculamos o valor
Epsilon
(a tolerância relativa ) que, neste caso, seria:double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
E então use-o nas funções inline e não inline desta maneira:
return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
As funções agora são:
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); }
Agora a saída será a esperada (
[1 1 1]
) com diferentes compiladores e em diferentes níveis de otimização.Demonstração ao vivo
fonte
max()
ligação? Por definição,floor(x)
é menor ou igual ax
, entãomax(x, floor(x))
sempre será igualx
.max
é apenas ofloor
do outro, não é necessário. Mas considerei um caso geral em que os argumentos demax
podem ser valores ou expressões independentes uns dos outros.operator==(double, double)
fazer exatamente isso, verifique se a diferença é menor do que um épsilon dimensionado? Cerca de 90% das questões relacionadas ao ponto flutuante no SO não existiriam então.Epsilon
valor dependendo de sua necessidade particular.