C++中的重要数字

Significant figures in C++

本文关键字:数字 C++      更新时间:2023-10-16

我编写了一个程序,用于计算序列中的值,所有值都是特别长的双精度值。我想打印这些值,每个值显示15个有效数字。下面的一些代码说明了我遇到的问题:

#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
    double x = 0.12345678901234567890;
    double y = 1.12345678901234567890;
    cout << setprecision(15) << fixed << x << "t" << y << "n";
    return 0;
}

由于只显示setprecision的尾随零,所以我添加了fixed,就像我在这个网站上的其他答案中看到的那样。然而,现在我似乎只有15位小数,对于不是0的值,这不是我想要的。你可以从上面的输出中看到这一点:

0.123456789012346       1.123456789012346

第一个数字有15个sig,但第二个数字有16个。我能做些什么来解决这个问题?

编辑:有人特别要求我使用setprecision,所以我无法尝试cout.recision.

您可以简单地使用scientific(注意14而不是15):

std::cout << std::scientific << std::setprecision(14) << -0.123456789012345678 << std::endl;
std::cout << std::scientific << std::setprecision(14) << -1.234567890123456789 << std::endl;
-1.23456789012346e-01
-1.23456789012346e+00

或者你可以使用一个功能:

#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include <sstream>
enum vis_opt { scientific, decimal, decimal_relaxed };
std::string figures(double x, int nfig, vis_opt vo=decimal) {
    std::stringstream str;
    str << std::setprecision(nfig-1) << std::scientific << x;
    std::string s = str.str();
    if ( vo == scientific )
        return s;
    else {
        std::stringstream out;
        std::size_t pos;
        int ileft = std::stoi(s,&pos);
        std::string dec = s.substr(pos + 1, nfig - 1);
        int e = std::stoi(s.substr(pos + nfig + 1));
        if ( e < 0 ) {
            std::string zeroes(-1-e,'0');
            if ( ileft < 0 ) 
                out << "-0." << zeroes << -ileft << dec;
            else
                out << "0." << zeroes << ileft << dec;
        } else if ( e == 0) {
            out << ileft << '.' << dec;
        } else if ( e < ( nfig - 1) ) {
            out << ileft << dec.substr(0,e) << '.' << dec.substr(e);
        } else if ( e == ( nfig - 1) ) {
            out << ileft << dec;
        } else {
            if ( vo == decimal_relaxed) {
                out << s;
            } else {
                out << ileft << dec << std::string(e - nfig + 1,'0');
            }
        }
        return out.str();   
    }
}
int main() {
    std::vector<double> test_cases = {
        -123456789012345,
        -12.34567890123456789,
        -0.1234567890123456789,
        -0.0001234,
        0,
        0.0001234,
        0.1234567890123456789,
        12.34567890123456789,
        1.234567890123456789,
        12345678901234,
        123456789012345,
        1234567890123456789.0,
    };

    for ( auto i : test_cases) {
        std::cout << std::setw(22) << std::right << figures(i,15,scientific);
        std::cout << std::setw(22) << std::right << figures(i,15) << std::endl;
    }
    return 0;
}

我的输出是:

 -1.23456789012345e+14      -123456789012345
 -1.23456789012346e+01     -12.3456789012346
 -1.23456789012346e-01    -0.123456789012346
 -1.23400000000000e-04 -0.000123400000000000
  0.00000000000000e+00      0.00000000000000
  1.23400000000000e-04  0.000123400000000000
  1.23456789012346e-01     0.123456789012346
  1.23456789012346e+01      12.3456789012346
  1.23456789012346e+00      1.23456789012346
  1.23456789012340e+13      12345678901234.0
  1.23456789012345e+14       123456789012345
  1.23456789012346e+18   1234567890123460000

我发现,仅计算整数有效数字,然后将浮动有效数字设置为X - <integer sig figs>:,就取得了一些成功

编辑

为了解决Bob的评论,我将解释更多的边缘案例。我对代码进行了一些重构,以调整基于前导和尾随零的字段精度。对于非常小的值(如std::numeric_limits<double>::epsilon:

int AdjustPrecision(int desiredPrecision, double _in)
{
    // case of all zeros
    if (_in == 0.0)
        return desiredPrecision;
        
    
    // handle leading zeros before decimal place
    size_t truncated = static_cast<size_t>(_in);
    
    while(truncated != 0)
    {
        truncated /= 10;
        --desiredPrecision;
    }
    
    // handle trailing zeros after decimal place
    _in *= 10;
    while(static_cast<size_t>(_in) == 0)
    {
        _in *= 10;
        ++desiredPrecision;
    }
        
    return desiredPrecision;
}

更多测试:

double a = 0.000123456789012345;
double b = 123456789012345;
double x = 0.12345678901234567890;
double y = 1.12345678901234567890;
double z = 11.12345678901234567890;

std::cout.setf( std::ios::fixed, std:: ios::floatfield);
std::cout << "a: " << std::setprecision(AdjustPrecision(15, a)) << a << std::endl;
std::cout << "b: " << std::setprecision(AdjustPrecision(15, b)) << b << std::endl;
std::cout << "x " << std::setprecision(AdjustPrecision(15, x))  << x << std::endl; 
std::cout << "y " << std::setprecision(AdjustPrecision(15, y))  << y << std::endl; 
std::cout << "z: " << std::setprecision(AdjustPrecision(15, z)) << z << std::endl;

输出:

a: 0.000123456789012345
b: 123456789012345
x 0.123456789012346
y 1.12345678901235
z: 11.1234567890123

现场演示

int GetIntegerSigFigs(double _in)
{
    int toReturn = 0;
    int truncated = static_cast<int>(_in);
    while(truncated != 0)
    {
        truncated /= 10;
        ++toReturn;
    }
    return toReturn;
}

(我确信我遗漏了一些边缘案例)

然后使用它:

double x = 0.12345678901234567890;
double y = 1.12345678901234567890;
std::cout << td::setprecision(15-GetIntegerSigFigs(x))  << x 
<< "t" << std::setprecision(15-GetIntegerSigFigs(y)) << y << "n";

打印:

0.123456789012346 1.12345678901235

实时演示