为什么存储在变量中的值比原来的答案小一?

Why the value storing in the variable is one less than the original answer?

d = 92.345; //it is of double type
scnt = 92; //it is of int type
//cout<<scnt;
int d1 =  (d-scnt)*10;
int d2 =  ((d-scnt)*100)-(d1*10);
int d3 =  ((d-scnt)*1000) - ((d1*100)+d2*10);//same equation if we print in cout gives actual answer
bool f1, f2;
cout<<"original value : "<<((d-scnt)*1000) - ((d1*100)+d2*10)<<endl;
cout<<"d3 : "<<d3<<endl;

如果您尝试 运行 上面的代码片段,您会注意到存储在 d3 变量中的值比实际答案小 1。 我期望存储在 d3 中的值应该是 5,但它是 4。 如果我将 d3 的数据类型设为双精度,那么它工作正常。但是进一步的代码需要 d3 的数据类型为 int ,所以我无法更改它。如果我在第 6 行计算后将 d3 简单地递增 1 是否安全?

我猜您正试图从双精度数中获取 int 变量中的每个小数。 如果您看到此代码逐步显示的结果:

double d = 92.345;
int scnt = 92;
int d1 =  (d-scnt)*10;
cout << d1 << endl;
cout << (d-scnt)*10 << endl;
int d2 =  ((d-scnt)*100)-(d1*10);
cout << d2 << endl;
cout << ((d-scnt)*100)-(d1*10) << endl;
int d3 =  ((d-scnt)*1000) - ((d1*100)+d2*10);
cout << d3 << endl;
cout << ((d-scnt)*1000) - ((d1*100)+d2*10) << endl;

所以打印出来的结果是:

d1 = 3
calculated d1 = 3.45
d2 = 4
calculated d2 = 4.5
d3 = 4
calculated d3 = 5

所以,你在 int 中得到一个 4 而在 cout 中得到一个 5 是因为,92.345 不是一个有效的双精度可表示数字,实际上是一个 92.344999999999999 所以,通过每次整数乘法你失去了精度,但是 cout 将这些数字视为双精度数,因此 cout 将这些数字四舍五入。

在低级别上,所有操作完全相同,除了存储或显示部分,这是唯一的区别。

  mov DWORD PTR [rbp-24], eax //Int saving

  mov edi, OFFSET FLAT:std::cout //Cout displaying
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(double)
  mov esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
  mov rdi, rax
  call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))

要了解更多,请执行以下操作:

if (((d - scnt) * 1000) - ((d1 * 100) + d2 * 10) == ((d - scnt) * 100) - (d1 * 10)) {
        cout << "lol" << endl;
    }

"lol" 不会被打印出来。希望对您有所帮助。

问题的起因是浮点类型的基础 属性。查看 here 了解更多信息。

在您的特定情况下,92.345 的值无法用浮点类型精确表示。分数 1/3 不能用有限的小数位数(以 10 为底)表示(无限循环项)。浮点也会产生同样的效果,只是浮点值是二进制的(基数为 2)。试图用二进制表示 0.345(即作为两个负幂的总和)需要无限多的二进制数字。

这样做的结果是当文字 92.345 存储在任何浮点变量(floatdoublelong double 类型)中时,将有一个不完全等于 92.345 的值。

实际值取决于浮点变量在您的系统上的表示方式,但据推测,在您的系统上实际值略小于 92.345。

这会影响你的计算(假设d是浮点型)。

int d1 =  (d-scnt)*10;        
int d2 =  ((d-scnt)*100)-(d1*10);
int d3 =  ((d-scnt)*1000) - ((d1*100)+d2*10);

对于浮点计算,会出现舍入误差。计算 (d-scnt)*10 将产生一个略小于 3.45 的值,这意味着 d1 将具有一个值 3。同样,d2 将得到 4 的值。问题(在此示例中)出现在计算 d3 中,因为 (d-scnt)*1000 将给出一个略小于 345 的浮点值,而减去 ((d1*100)+d2*10) 将给出一个略小于 345 的浮点值比 5。转换为 int 向零舍入,因此 d3 将以 4 的值结束,如您所述。

d3 增加到 "fix" 这个问题是一个非常糟糕的主意,因为浮点舍入可以采用任何一种方式。您会发现 d 的其他值,它们以另一种方式舍入(double 的值略大于您的预期)。还有一些值,您的代码将根据这些值产生您期望的行为,而无需修改。

有多种方法可以解决这个问题。

一种方法是将值打印为具有所需位数的字符串。这可以使用 std::ostringstream(来自标准头文件 <sstream>)轻松完成。这会将 92.345 转换为字符串 "92.345"。从那里,您可以从字符串中提取数字。

我更喜欢的另一种方法是编写您的计算以正确考虑浮点舍入。在 C++11 及更高版本中,您可以使用 <cmath> 中的 round(),如下所示。

int d1 =  round((d-scnt)*10);
int d2 =  round((d-scnt)*100)-(d1*10);
int d3 =  round((d-scnt)*1000) - ((d1*100)+d2*10);

或(明确说明从 doubleint 的转换发生的位置)

int d1 =  static_cast<int>(round((d-scnt)*10));
int d2 =  static_cast<int>(round((d-scnt)*100)) -(d1*10);
int d3 =  static_cast<int>(round((d-scnt)*1000)) - ((d1*100)+d2*10);

在C++11之前(没有提供round()),round(x)的效果可以大致模拟为floor(x + 0.5)