double类型打印结果一致但系统判定不一致的问题

本文探讨了在计算平面上多个整数点确定的直线数量时,由于浮点数精度限制导致的计算误差。通过示例代码展示了双精度浮点数(double)在处理无限小数时可能出现的不一致,并提出了三种解决方案:字符串转换、差值比较以及避免使用可能存在精度问题的变量。最后,给出了正确的计算方法,即利用集合(set)进行去重,以确保计算直线数量的准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

假设存在两个理论结果一致的公式b和b1:

b和b1的计算方式理论上计算得出的结果是一样的,编译器打印出来肉眼看也是一样的,系统判定不一样原因如下:
即使double有着很高的精度(64位),但是机器对最后一位的处理必定是四舍五入的


遇到无限循环or无限不循环小数这样的计算结果,若将该结果保存在某个double变量中用于后续计算,计算结果的判断可能会出错

那么有以下几种处理方式
(1)将double强制转换为string,保留精度在十进制0.000001
(2)检测差值,当差值小于0.000001时认为一致
(3)推荐的办法:在计算公式中保证不包含某个double型的变量k(该k可能是经过系统末尾四舍五入取值的无限小数)
在这里插入图片描述


        【将下面的代码保存为cpp文件并运行可以看到这种出错的情况】
/*

【问题描述】
在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。
给定平面上2x3个整点{(xv)0≤x<20≤у<3x€Zy€Z},即横坐标是0到1(包含0和1)之间的整数、纵坐标是0到2(包含0和2)之间的整数的点。
这些点一共确定了11条不同的直线。

给定平面上20x21个整点1(xy)0≤x<200≤у<21x€Z,y€Z},即横坐标是0到19(包含0和19)之间的整数、纵坐标是0到20(包含0和20)之间的整数的点。
请问这些点一共确定了多少条不同的直线 ?

*/
# include<iostream>
# include<map>
# include<set>
# include<string>
#define row 5
#define col 3
using namespace std;

int main() {
    map<pair<double, double>, int> m = {};
    int ans = 0;
    for (int x1 = 0; x1 < row; x1++)
        for (int y1 = 0; y1 < col; y1++)
            for (int x2 = 0; x2 < row; x2++)
                for (int y2 = 0; y2 < col; y2++) {
                    if (x1 == x2 || y1 == y2)   continue;
                    
                    double k = 1.0*(y2 - y1) / (x2 - x1)*1.0;
                    double b = (y1 - k * x1);
                    
                    double k1 = (y1 - y2) * 1.0 / (x1 - x2);
                    double b1 = ((x2 * y1 - x1 * y2) * 1.0) / ((x2 - x1) * 1.0);

                    /*
                    存在明显的问题,b和b1的计算方式理论上计算得出的结果是一样的,打印出来肉眼看也是一样的,但是系统判定不一样:
                    即使double有着很高的精度(64位),但是机器对最后一位的处理必定是四舍五入的;
                    如遇到无限循环or无限不循环小数这样的计算结果,若将该结果保存在某个double变量中用于后续计算,计算结果的判断可能会出错。
                    【运行该代码可以看到这种出错的情况】
                    那么有以下两种处理方式:
                    (1)将double强制转换为string,保留精度在十进制0.000001
                    (2)检测差值,当差值小于0.000001时认为一致
                    (3)推荐的办法,在计算公式中保证不包含某个double型的变量k(该k可能是经过系统末尾四舍五入取值的无限小数)
                    */
                  
                    if (b != b1) {
                        printf("点A1:(%d,%d),\t点A2:(%d,%d)\n", x1, y1, x2, y2);
                        printf("b公式计算出的截距:\t十六进制:%x\t浮点型:%lf\n", b,b);
                        printf("b1公式计算出的截距:\t十六进制:%x\t浮点型:%lf\n", b1,b1);
                        string notice = b == b1 ? "b==b1" : "b!=b1";
                        cout << notice << endl;
                        cout << " 将其转换成string再做比较: " << endl;
                        string str = to_string(b);
                        string str1 = to_string(b1);
                        cout << "str_b:\t" << str << endl;
                        cout << "str_b1:\t" << str1 << endl;
                        string note = str == str1 ? "str_b==str_b1" : "str_b!=str_b1";
                        cout << note << endl << endl;

                    }
               
                    if (m[{k, b}] == 0) {
                        m[{k, b}] = 1;
                       //printf("a:(%d,%d),b:(%d,%d) ———— 斜率:%.2f\t截距:%.2f\n\n", x1,y1,x2,y2,k,b);
                        ans++;
                    }
                }

    cout << "由于上述问题导致的结果错误:" << ans + row + col << endl;
    cout << "__________________________________________________________________\n\n\n" << endl;

    /*
     下面是正确的计算方式
    */
    set<pair<double, double> >a;            // 直接使用set去重
        for (int x1 = 0; x1 < row; x1++) {
            for (int y1 = 0; y1 < col; y1++) {
                for (int x2 = 0; x2 < row;x2++) {
                    for (int y2 = 0; y2 < col;y2++){
                        if (x1 == x2 ) {    //避免斜率不存在,也可以在条件中加上 || y1 == y2 ,但结果要加上col
                            continue;
                        }
                        double k = (y1 - y2) * 1.0 / (x1 - x2);
                        double b = ((x2 * y1 - x1 * y2) * 1.0) / ((x2 - x1) * 1.0);     //不在计算中包含double类型的变量
                        a.insert(make_pair(k, b));
                    }
                }
            }
        }
        cout << "线条的真正个数" << a.size() + row << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狱典司

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值