"x = -x" 和 "x *= -1" 在取负值时是否存在功能差异?
Is there a functional difference between "x = -x" and "x *= -1" when negating values?
代码示例:
float f = 123.456;
int i = 12345;
// negate by changing sign
i = -i;
f = -f;
// negate by multiplying with -1
i *= -1;
f *= -1.0f;
除了美学之外,还有什么事实可以说明人们应该更喜欢一种方式而不是另一种方式吗?
对我来说乘法似乎有点不必要。但我更经常看到乘法风格,并且想听听除了偏好之外是否有充分的理由。
根据我对“fastest-way-to-negate-a-number”的回答
速度
similar question 的答案表明:
- 你应该忘掉速度,选择你觉得最易读的习语。
- 几乎所有编译器都会为
a = -a
、a *= -1
等任何内容生成等效的最佳代码(可能是一条指令)。
- 但是尽管 MSVS 2012(优化?)对每条指令使用一条指令,
= -a
需要 1 个周期,*= -1
需要 3 个周期。
- 任何让它更快的尝试都会大大降低它的可读性,并且很容易让它变慢。
- 如果您需要优化,您应该从分析生成的代码和性能开始。
副作用和冗余
然而 *= -1
惯用语有一个实际的优势:你只需要写一次左边,并且只计算一次。当 LHS 较长、复杂或昂贵或可能有副作用时,这是相关的:
(valid ? a : b)[prime_after(i++)] *= -1;
*look_up (input) *= -1; // Where look_up may have side-effects
parity[state][(unsigned int)getc(stdin)] *= -1;
variable_with_a_long_explanatory_name *= -1;
这种冗余的另一个好处是,当寻找错误时,可以确定这个数字确实被取反了原位,并且没有细微的差别两个表达式。
一旦采用了一种成语,人们往往会在其他情况下坚持使用它,因此坚持*= -1
是可以理解的。
建议使用最能解释代码的内容,除非使用的是旧平台或编译器。在那种情况下,使用否定。
以下是一些不明显的区别:
int
非 2 的补码格式:
在过去,0
和 -1
的乘积可能导致 0
,而 0
的否定可能导致 -0
。当然0
和-0
有相同的值,只是符号不同。并非所有非 2 的补码机都在这方面工作。
浮点数:
速度:智能编译器将为 f *= -1.0f
和 f *= -f
创建高效且可能相同的代码。然而,一个较小的编译器可能无法识别这种等价性并且执行一个比另一个慢。
舍入:否定不需要调用任何舍入,但乘法,即使乘以 1.0,也可能涉及舍入。这发生在两种情况下:当变量具有更高的精度(在现代 C 中允许,FLT_EVAL_METHOD
)和在使用 base-16 而不是 base-2 执行 FP 的机器(更像遗物)中。在后一种情况下,精度波动(例如 IBM Floating Point)和简单的乘法将呈现一个舍入乘积。现在这种行为并不常见。
也存在对同一值具有多种表示形式的 FP 格式,但乘以 1 将是 return 首选格式,而取反只会翻转符号位。结果相同 value,但 FP 编号的位模式不同。
代码示例:
float f = 123.456;
int i = 12345;
// negate by changing sign
i = -i;
f = -f;
// negate by multiplying with -1
i *= -1;
f *= -1.0f;
除了美学之外,还有什么事实可以说明人们应该更喜欢一种方式而不是另一种方式吗?
对我来说乘法似乎有点不必要。但我更经常看到乘法风格,并且想听听除了偏好之外是否有充分的理由。
根据我对“fastest-way-to-negate-a-number”的回答
速度
similar question 的答案表明:
- 你应该忘掉速度,选择你觉得最易读的习语。
- 几乎所有编译器都会为
a = -a
、a *= -1
等任何内容生成等效的最佳代码(可能是一条指令)。- 但是尽管 MSVS 2012(优化?)对每条指令使用一条指令,
= -a
需要 1 个周期,*= -1
需要 3 个周期。
- 但是尽管 MSVS 2012(优化?)对每条指令使用一条指令,
- 任何让它更快的尝试都会大大降低它的可读性,并且很容易让它变慢。
- 如果您需要优化,您应该从分析生成的代码和性能开始。
副作用和冗余
然而 *= -1
惯用语有一个实际的优势:你只需要写一次左边,并且只计算一次。当 LHS 较长、复杂或昂贵或可能有副作用时,这是相关的:
(valid ? a : b)[prime_after(i++)] *= -1;
*look_up (input) *= -1; // Where look_up may have side-effects
parity[state][(unsigned int)getc(stdin)] *= -1;
variable_with_a_long_explanatory_name *= -1;
这种冗余的另一个好处是,当寻找错误时,可以确定这个数字确实被取反了原位,并且没有细微的差别两个表达式。
一旦采用了一种成语,人们往往会在其他情况下坚持使用它,因此坚持*= -1
是可以理解的。
建议使用最能解释代码的内容,除非使用的是旧平台或编译器。在那种情况下,使用否定。
以下是一些不明显的区别:
int
非 2 的补码格式:
在过去,0
和 -1
的乘积可能导致 0
,而 0
的否定可能导致 -0
。当然0
和-0
有相同的值,只是符号不同。并非所有非 2 的补码机都在这方面工作。
浮点数:
速度:智能编译器将为 f *= -1.0f
和 f *= -f
创建高效且可能相同的代码。然而,一个较小的编译器可能无法识别这种等价性并且执行一个比另一个慢。
舍入:否定不需要调用任何舍入,但乘法,即使乘以 1.0,也可能涉及舍入。这发生在两种情况下:当变量具有更高的精度(在现代 C 中允许,FLT_EVAL_METHOD
)和在使用 base-16 而不是 base-2 执行 FP 的机器(更像遗物)中。在后一种情况下,精度波动(例如 IBM Floating Point)和简单的乘法将呈现一个舍入乘积。现在这种行为并不常见。
也存在对同一值具有多种表示形式的 FP 格式,但乘以 1 将是 return 首选格式,而取反只会翻转符号位。结果相同 value,但 FP 编号的位模式不同。