C++中的双类型变量乘法会导致值不准确
double type variable multiplication in C++ results in inaccurate value
我在做字符串到双精度转换练习时发现了这种情况(例如stdlib.h中的atof)。我想把字符串"625"(表示双精度的小数部分)放在双精度变量0.625中。奇怪的是,当我把它作为练习的一部分时,它导致了不准确的结果0.6250000000000000011或类似的结果。然而,当我把它单独放在一个地方时,它运行得很好,就像下面的代码:
int main(int argc, char **argv) {
string str = "625";
double mask = 0.1;
double frac = 0.0;
for( int i = 0; i < static_cast<int>(str.length()); ++i ) {
frac += (str[i] - '0')*mask;
mask *= 0.1;
}
cout << frac << endl;
}
上面的代码给出了准确的结果(0.625)。但下面的代码给出的结果不准确(0.6250000000000000011):
string PrintDecimal(string input) {
long int_part = 0;
double frac_part = 0.0;
bool is_positive;
size_t found;
string ret_str;
found = input.find('-');
if( found == string::npos ) {
is_positive = true;
}
else {
is_positive = false;
input.erase(found, 1);
}
found = input.find('.');
if( found == string::npos ) {
int mask = 1;
char app_char;
for( int i = static_cast<int>(input.length()-1); i > -1; --i ) {
int_part += (input[i] - '0')*mask;
mask *= 10;
}
while( int_part != 0 ) {
app_char = (int_part % 2 == 0) ? '0' : '1';
ret_str.push_back(app_char);
int_part /= 2;
}
if( is_positive == false ) {
ret_str.append("-");
}
reverse(ret_str.begin(), ret_str.end());
}
else {
char app_char;
long mask_int = 1;
double mask_frac = 0.1;
string int_part_str = input.substr(0, found);
//string frac_part_str = input.substr(found+1, input.length()-found-1);
string frac_part_str = "0.";
frac_part_str.append(input.substr(found+1, input.length()-found-1));
for( int i = static_cast<int>(int_part_str.length()-1); i > -1; --i ) {
int_part += (int_part_str[i] - '0')*mask_int;
mask_int *= 10;
}
//This converting causes 6*0.1 = 0.6000000000009
/*
for( int i = 0; i < static_cast<int>(frac_part_str.length()); ++i ) {
frac_part += (frac_part_str[i] - '0')*mask_frac;
mask_frac *= 0.1;
}
*/
frac_part = atof(frac_part_str.c_str()); //This works well.
while( int_part != 0 ) {
app_char = (int_part % 2 == 0) ? '0' : '1';
ret_str.push_back(app_char);
int_part /= 2;
}
if( is_positive == false ) {
ret_str.append("-");
}
reverse(ret_str.begin(), ret_str.end());
ret_str.push_back('.');
found = ret_str.find('.');
while( frac_part != 0.0 ) {
if( ret_str.length() - found > 64 ) {
cerr << "Can't express accurately." << endl;
return "Error";
}
frac_part *= 2;
if( frac_part >= 1.0 ) {
ret_str.push_back('1');
frac_part -= 1;
}
else {
ret_str.push_back('0');
}
}
}
cout << ret_str << endl;
return ret_str;
}
我使用的编译器版本是gcc版本4.2.1(Apple股份有限公司版本5666)(点3)。请注意,代码中的注释部分导致了问题。我请求你提出解决这个问题的办法。谢谢
事实上,第一个也不准确,如果我们以更高的精度打印结果,我们就会得到
0.62500000000000011102
以及,将中间结果打印为全精度
Prelude Text.FShow.RealFloat> mapM_ print $ scanl (+) (FD 0) $ zipWith (*) (iterate (*0.1) 0.1) [6,2,5]
0.0
0.600000000000000088817841970012523233890533447265625
0.62000000000000010658141036401502788066864013671875
0.62500000000000011102230246251565404236316680908203125
为了获得尽可能准确的结果,您必须使用更复杂的算法,例如将字符串解析为有理数并从中进行转换。
一个快速的部分解决方案是解析分数部分以产生numerator / (10^k)
,
double denominator = 1.0;
uint64_t numerator = 0;
for(i = f0; i < len; ++i) { // f0 index of first digit after decimal point
numerator = 10*numerator + (str[i] - '0');
denominator *= 10;
}
double fractional_part = numerator / denominator;
10的幂(具有非负指数)可以在一段时间内精确地表示为double
s(对于指数<=22,假设64位IEEE754 double
s),并且如果分数部分不太长,分子也可以精确地表示。那么,由于必要的四舍五入,即最后的除法,只有一点会产生不精确的结果,并且结果(应该是)与精确的数学结果最接近的可表示数。(另一个不精确的点是将分数部分添加到积分部分。)
对于积分部分不太大、分数部分足够短的输入,上面的方法会产生很好的结果,但对于长分数部分,这是非常错误的。
正确解析和显示浮点数是一项复杂的业务。
您需要设置精度,以便能够输出浮点值并加倍到您选择的精度:
请尝试使用setprecision()
一点也不奇怪:double是二进制浮点表示。
这意味着它将无法正确地对某些十进制数字进行建模。
如果这对你来说没有意义,请阅读这篇文章。
如果要对小数进行建模,可以尝试使用十进制浮点表示法。
因此,如果您可以访问C++11编译器,如gcc 4.6.1,您可以使用std::decimal::decial64来准确地表示小数。如果没有,你可以使用这个库。
- 是否允许类类型的 std::function 成员变量(不完整类型)?
- 为什么我得到变量"Node"不是类型名称错误
- C++ 类型类的变量声明不命名类型?
- 为什么类型变量;不调用默认 CTR
- 如何对对象数组进行排序,而不考虑对象变量类型
- TBB Parallel_用于计数,增量变量不准确
- 不能将变量'test'声明为抽象类型 'OurStack<int>'
- 是否有任何类型特征控制成员类型(不是成员变量)
- 模板固有性 - 变量不命名类型
- 类型枚举的变量不是类型名称
- 不能将变量"t"声明为抽象类型"AddSalariedEmployee"
- 不能将变量声明为抽象类型
- C++ 显示字符串变量"<不完整类型>的调试窗口
- 不命名类型错误(在变量上)c++
- 不能将变量“”声明为抽象类型“”
- 为什么在链表中使用指向结构的指针而不是常规结构类型变量呢
- 不能将变量'x'声明为抽象类型 'Queue<int>'
- 应用于不完整类型的变量的成员访问表达式
- 为什么 c++ 指针 * 与声明的变量相关联,而不是类型
- C++中的双类型变量乘法会导致值不准确