我们是否应该通常使用浮点数文字而不是更简单的双面文字
Should we generally use float literals for floats instead of the simpler double literals?
在C++(或者可能只有我们的编译器VC8和VC10(中,3.14
是双精度文字,3.14f
是浮点文字。
现在我有一位同事说:
我们应该使用浮点数文本进行浮点计算,使用双精度文字进行双重计算,因为在计算中使用常量时,这可能会对计算精度产生影响。
具体来说,我认为他的意思是:
double d1, d2;
float f1, f2;
... init and stuff ...
f1 = 3.1415 * f2;
f1 = 3.1415f * f2; // any difference?
d1 = 3.1415 * d2;
d1 = 3.1415f * d2; // any difference?
或者,由我添加,甚至:
d1 = 42 * d2;
d1 = 42.0f * d2; // any difference?
d1 = 42.0 * d2; // any difference?
更一般地说,我能看到使用 2.71828183f
的唯一一点是确保我尝试指定的常量实际上适合浮点数(否则编译器错误/警告(。
有人可以对此有所了解吗?是否指定f
后缀?为什么?
引用一个我含蓄地认为理所当然的答案:
如果您正在使用浮点变量和双精度文字,则整体 操作将作为双精度完成,然后转换回浮点数。
这会有什么坏处吗?(除了非常非常理论上的性能影响?
进一步编辑:如果包含技术细节的答案(感谢!(也可以包括这些差异如何影响通用代码,那就太好了。(是的,如果你正在处理数字,你可能希望确保你的big-n浮点运算尽可能高效(和正确(——但对于被调用几次的通用代码来说,这重要吗?如果代码只使用0.0
并跳过 - 难以维护,这不是更干净吗!-- 浮动后缀?
是的,您应该使用f
后缀。 原因包括:
-
性能。 当你编写
float foo(float x) { return x*3.14; }
时,你强制编译器发出将x转换为双精度的代码,然后进行乘法,然后将结果转换回单。 如果添加f
后缀,则会消除这两种转换。 在许多平台上,每次转换都与乘法本身一样昂贵。 -
性能(续(。 在某些平台上(例如大多数手机(,双精度算术比单精度算法慢得多。 即使忽略转换开销(在 1. 中介绍(,每次强制以 double 计算计算时,都会减慢程序的速度。 这不仅仅是一个"理论"问题。
-
减少您接触错误的风险。 考虑
float x = 1.2; if (x == 1.2) // something;
something
执行的示例? 不,不是,因为 x 保持1.2
四舍五入到float
,但与双精度值1.2
进行比较。 两者并不相等。
我做了一个测试。
我编译了这段代码:
float f1(float x) { return x*3.14; }
float f2(float x) { return x*3.14F; }
将 gcc 4.5.1 用于带有优化 -O2 的 i686
。这是为 f1 生成的汇编代码:
pushl %ebp
movl %esp, %ebp
subl $4, %esp # Allocate 4 bytes on the stack
fldl .LC0 # Load a double-precision floating point constant
fmuls 8(%ebp) # Multiply by parameter
fstps -4(%ebp) # Store single-precision result on the stack
flds -4(%ebp) # Load single-precision result from the stack
leave
ret
这是为 f2 生成的汇编代码:
pushl %ebp
flds .LC2 # Load a single-precision floating point constant
movl %esp, %ebp
fmuls 8(%ebp) # Multiply by parameter
popl %ebp
ret
所以有趣的是,对于 f1,编译器存储值并重新加载它只是为了确保结果被截断为单精度。
如果我们使用 -ffast-math 选项,那么这种差异会显着减少:
pushl %ebp
fldl .LC0 # Load double-precision constant
movl %esp, %ebp
fmuls 8(%ebp) # multiply by parameter
popl %ebp
ret
pushl %ebp
flds .LC2 # Load single-precision constant
movl %esp, %ebp
fmuls 8(%ebp) # multiply by parameter
popl %ebp
ret
但是加载单精度常数或双精度常数之间仍然存在差异。
64 位更新
这些是 gcc 5.2.1 针对 x86-64 和优化 -O2 的结果:
F1:
cvtss2sd %xmm0, %xmm0 # Convert arg to double precision
mulsd .LC0(%rip), %xmm0 # Double-precision multiply
cvtsd2ss %xmm0, %xmm0 # Convert to single-precision
ret
F2:
mulss .LC2(%rip), %xmm0 # Single-precision multiply
ret
使用 -ffast-math,结果是相同的。
我怀疑是这样的:如果您正在使用浮点变量和双精度文字,则整个操作将作为双精度完成,然后转换回浮点数。
如果您使用浮点数文字,从理论上讲,计算将以浮点精度完成,即使某些硬件无论如何都会将其转换为双精度进行计算。
通常,我认为这不会有任何区别,但值得指出3.1415f
和3.1415
(通常(不相等。 上另一方面,您通常不会在float
中进行任何计算无论如何,至少在通常的平台上。 ( double
同样快,如果不快。 关于你应该看到float
的唯一时间是在那里是大型数组,即使这样,所有计算通常也会在double
完成。
有一个区别:如果使用双精度常量并将其与浮点变量相乘,则首先将变量转换为双精度,以双精度完成计算,然后将结果转换为浮点数。虽然精度在这里并不是真正的问题,但这可能会导致混淆。
我个人倾向于使用 f 后缀表示法作为原则问题,并尽可能明显地表明这是一个浮点类型而不是双精度类型。
我的两分钱
摘自C++标准(工作草案(,关于二元运算符的第5节
许多期望算术操作数或 枚举类型导致转换和产生结果类型类似 道路。目的是产生一个通用类型,这也是 结果。这种模式称为通常的算术转换, 定义如下: — 如果任一操作数的作用域为 枚举类型 (7.2(,不执行转换;如果其他 操作数没有相同的类型,表达式格式不正确。— 如果其中一个操作数的类型为长双精度,则应转换另一个操作数 长双倍。— 否则,如果其中一个操作数是双精度数,则另一个操作数是双精度数 应转换为双倍。— 否则,如果任一操作数是浮点数, 另一个应转换为浮动。
还有第 4.8 节
浮点类型的 prvalue 可以转换为 另一种浮点类型。如果源值可以完全 在目标类型中表示,转换的结果是 确切的表示。如果源值位于两个相邻值之间 目标值,则转换结果为 实现定义的任一值的选择。否则, 行为未定义
这样做的结果是,您可以通过以目标类型规定的精度指定常量来避免不必要的转换,前提是您不会因此在计算中失去精度(即,您的操作数在目标类型的精度中完全可表示(。
- 如何在没有函数的情况下编写此代码并使C++更简单?
- 查找定义我的 C/C++ 函数/宏的文件比'grep'更简单的方法
- 将while+if else转为更简单的std::remove_if
- 让类与运算符一起工作更简单的方法
- 多个 if-else 测试的更简单方法
- 为具有多个参数的函数创建 SWIG 类型图的更简单方法?
- 有没有更简单的方法可以从用户那里获取三个数字并按升序打印它们?
- 约束模板参数顺序的更简单方法
- C++ 将整数与硬编码的整数集进行比较的更简单方法
- 有没有一种方法可以在基于枚举的可变参数模板函数之间进行选择,这比将函数包装在结构中更简单
- 一种在没有 root 的情况下加载共享库的更简单方法
- 是否有一种更简单的方法可以使用GDB将C捕获的C结构捕获
- 生活的游戏邻居检查(更简单的方法可能吗?)
- 尝试在链表程序的末尾进行更简单的插入.小问题需要帮助
- 有没有更简单的方法可以做到:if(num1 > num2 && num1 > num3),以获得更大的变量列表进行比较?
- 有没有更简单的方法在 Rcpp 中使用 NumericVector 编写条件语句
- 如果存在条件,是否有更简单的表示形式
- 访问派生属性和给定基指针的方法的更简单方法
- 是否有更简单的方法来定义C 中的for循环中的条件
- 我们是否应该通常使用浮点数文字而不是更简单的双面文字