c++11 下 fmod(和其他)的不同行为,至少在 Visual Studio 中
Posted
技术标签:
【中文标题】c++11 下 fmod(和其他)的不同行为,至少在 Visual Studio 中【英文标题】:Different behaviour of fmod (and others) under c++11, in Visual Studio at least 【发布时间】:2013-03-22 14:22:59 【问题描述】:我有一些示例代码在带有新 C++11 标头的 Visual C++ 2012 下的行为与在 VC++ 2010 下的行为不同。 它涉及当您调用包含 cmath 时获得的 std::fmod 函数时会发生什么,并且当您传递的参数不是双精度值,而是隐式转换为双精度运算符的类时:
#include <cmath>
class Num
double d_;
public:
Num(double d) : d_(d)
operator double() const return d_;
;
int main(int argc, char* argv[])
Num n1(3.14159265358979323846264338327950288419716939937510);
Num n2(2.0);
double result1 = fmod((double)n1, (double)n2);
double result2 = fmod((float)n1, (float)n2);
double result3 = fmod(n1, n2);
if (result2==result1) std::cout << "fmod(double, double) returns the same as fmod(float,float)" << std::endl;
if (result3==result1) std::cout << "fmod(Num, Num) returns the same as fmod(double,double)" << std::endl;
if (result3==result2) std::cout << "fmod(Num, Num) returns the same as fmod(float,float)" << std::endl;
令我惊讶的是,这调用了采用两个浮点数的 fmod 版本,而不是采用两个双精度数的 fmod 版本。
所以我的问题是,考虑到 C++ 11 标准,这是正确的行为吗?我能找到的关于该行为的唯一信息是 cppreference.com 文档here,其中说(强调我的):
如果任何参数具有整数类型,则将其强制转换为双精度。如果任何其他参数为 long double,则返回类型为 long double,否则为 double。
然而,Visual Studio 头文件中的实现似乎实现了“否则它是一个浮点数”。
任何人都知道意图是什么:-)?
通过在线 c++11 版本的 GCC 运行示例(否则我无法简单访问 GCC 的最新副本),它似乎调用了 fmod 的“双”版本,这就是我天真地期待。
为了清楚起见,我使用的是
Microsoft (R) C/C++ 优化编译器版本 17.00.51106.1 for x86
这是自带的
Microsoft Visual Studio Express 2012 for Windows Desktop 版本 11.0.51106.01 更新 1
【问题讨论】:
您如何确定它调用的是float
版本?
我单步调试了它。我没有一个例子可以证明一种方法或另一种方法,它正在做什么并将更新。
太棒了。我认为查看 SSCCE (sscce.org) 并了解您的编译器版本的完整详细信息会非常有帮助。
“I have not”应该说“I have now”……稍微改一下意思。
旁注:gcc 4.5 和-ffast-math
表示它们都是平等的。如果没有该选项,将按预期打印same as fmod(double,double)
。
【参考方案1】:
这与this question of mine 有关。原因是,为了提供标准要求的额外重载(并在您的问题中引用)VS 2012 为所有 2 参数数学函数定义了通用函数模板。所以你实际上并没有打电话给fmod(float, float)
,而是首先打电话给fmod<Num>(Num, Num)
。
这个模板化函数比普通的double
版本更受欢迎的原因是,双重版本需要用户定义的从Num
到double
的转换,而模板版本是可直接实例化的。
但是调用fmod
函数的实际基本类型随后由<xtgmath.h>
中的此类型特征确定:
template<class _Ty>
struct _Promote_to_float
// promote integral to double
typedef typename conditional<is_integral<_Ty>::value,
double, _Ty>::type type;
;
template<class _Ty1,
class _Ty2>
struct _Common_float_type
// find type for two-argument math function
typedef typename _Promote_to_float<_Ty1>::type _Ty1f;
typedef typename _Promote_to_float<_Ty2>::type _Ty2f;
typedef typename conditional<is_same<_Ty1f, long double>::value
|| is_same<_Ty2f, long double>::value, long double,
typename conditional<is_same<_Ty1f, double>::value
|| is_same<_Ty2f, double>::value, double,
float>::type>::type type;
;
它的作用是检查所有浮点类型的提升类型_Promote_to_float
(在你的情况下又是Num
,因为它只检查它的积分,Num
显然不是)直到它匹配,它不匹配,因此导致 float
的 else 情况。
这种错误行为的原因是,这些额外的数学重载并不是要为每种类型提供,而只是为内置算术类型提供(并且模棱两可的标准措辞即将被修复,如我对链接问题的回答)。因此,在上面解释的所有这种类型推导机制中,VS 2012 假定传入的类型要么是内置整数类型,要么是内置浮点类型,这对于Num
来说当然是失败的。所以实际的问题是 VS 提供了过于通用的数学函数,而它们应该只为内置类型提供重载(就像已经为 1 参数函数正确完成的那样)。如链接答案中所述,我已经为此提交了一个错误。
编辑: 事实上(正如您也意识到的那样)即使他们遵循当前模棱两可的标准措辞并需要提供通用函数模板,他们仍然应该定义实际的提升类型这些通用参数中的double
而不是float
。但我认为这里的实际问题是他们完全忽略了在整个类型转换过程中可能存在的非内置类型(因为对于内置类型,它们的逻辑工作得非常好)。
但根据 26.8 [c.math] 节中当前模棱两可的标准措辞(尽管已经计划更改),他们确实正确地将提升的类型推断为 float
(根据第三种情况):
应该有足够的额外重载来确保:
如果对应于 double 参数的任何实参的类型为 long double,则对应于 double 形参的所有实参都是 有效地转换为 long double。 否则,如果对应于 double 参数的任何参数的类型为 double 或整数类型,则所有对应于 double 参数被有效地转换为 double。 否则,对应于双精度参数的所有参数都将有效地转换为浮点数。
【讨论】:
正如您在回答另一个问题时所说,标准似乎要求这种不良行为。 但是这个错误似乎不是在最后一行使用“float”,而不是“double”吗?我的意思是 cppreference.com 页面中的文字说“否则它是一个双精度数”,而 Visual C++ 实现“否则它是一个浮点数”。因此我问哪个是“正确”的行为 @BenVoigt 但我还指出了一个已经批准的标准修复程序,修复了这个问题。但即使采用目前模棱两可的标准措辞,通用参数仍需要double
版本。
@TomQuarendon 没错,他们至少应该使用double
作为其他情况。但整个问题源于这样一个事实,即他们似乎在整个过程中忽略了通用非内置参数的存在。因为如果您通过了int
s 或bool
s,它会正确调用double
版本。
@TomQuarendon 哦,等等,不把它推到float
甚至是正确的行为,在答案中添加了标准参考。【参考方案2】:
正如 Christian 在其相关问答中所指出的,这是严格阅读标准所要求的行为。
但是,您可以很容易地为所有版本解决此问题:
Num fmod(const Num a, const Num b)
const double cvt_a = a;
const double cvt_b = b;
return Num(fmod(cvt_a, cvt_b));
【讨论】:
以上是关于c++11 下 fmod(和其他)的不同行为,至少在 Visual Studio 中的主要内容,如果未能解决你的问题,请参考以下文章