CppCoreGuidelines ES.46 避免那些导致精度损失的变窄、截断数值类型转换
20 April 2023
“Avoid lossy (narrowing, truncating) arithmetic conversions”
坏例子
变窄类型转换操作
double d = 7.9; int i = d; // bad: narrowing: i becomes 7 cout << d << ", " << i << endl; i = (int) d; // bad: we're going to claim this is still not explicit enough cout << d << ", " << i << endl;
7.9, 7 7.9, 7
void f(int x, long y, double d) { char c1 = x; // bad: narrowing char c2 = y; // bad: narrowing char c3 = d; // bad: narrowing cout << c1 << ", " << c2 << ", " << c3; } int main() { f(10099, 1000000099, 9999990.999999); return EXIT_SUCCESS; }
s, c, v
注意
指南支持库 GSL 提供narrow_cast
操作和 narrow
操作。如果允许数值丢失,那么可以用narrow_cast
;如果认为数值丢失是一种错误,那么用 narrow
操作,如果类型转换的过程中数值丢失,它会抛出narrow_error
。
double d = 7.9; double d2 = 8.0; int i{}; i = gsl::narrow_cast<int>(d); // OK (you asked for it): narrowing: i becomes 7 cout << "gsl::narrow_cast<int>(" << d << ") = " << i << endl; try { i = gsl::narrow<int>(d2); cout << "gsl::narrow<int>(" << fixed << setprecision(1) << d2 << ") = " << i << endl; i = gsl::narrow<int>(d); // OK: throws narrowing_error } catch (gsl::narrowing_error& e) { cout << e.what(); }
gsl::narrow_cast<int>(7.9) = 7 gsl::narrow<int>(8.0) = 8 std::exception
负数浮点类型也可以用 gsl 类型转换操作:
double d = -7.9; unsigned u = 0; u = d; // bad: narrowing cout << u << endl; u = gsl::narrow_cast<unsigned>(d); // OK (you asked for it): u // becomes 4294967289 cout << "gsl::narrow_cast<int>(" << d << ") = " << u << endl; try { u = gsl::narrow<unsigned>(d); // OK: throws narrowing_error } catch (gsl::narrowing_error& e) { cout << e.what(); }
4294967289 gsl::narrow_cast<int>(-7.9) = 4294967289 std::exception
注意
此规则不适用于转换到布尔型的某些特殊操作:
if (ptr) do_something(*ptr); // OK: ptr is used as a condition bool b = ptr; // bad: narrowing
强化
好的分析工具能检测到所有窄转换操作。然而,把所有窄转换都打上标记,可能会导致太多误报。
建议
标记所有从浮点型到整型的类型转换。也包括浮点到 char,双精度型到 int。
标记所有 long
到 char
的类型转换。
函数参数出现窄类型转换也很值得怀疑!