为什么存储在变量中的值比原来的答案小一?
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
存储在任何浮点变量(float
、double
、long 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);
或(明确说明从 double
到 int
的转换发生的位置)
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)
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
存储在任何浮点变量(float
、double
、long 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);
或(明确说明从 double
到 int
的转换发生的位置)
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)