显示双精度浮点数与它们的比较

时间:2016-02-24 00:52:36

标签: c++

序言

我正在研究一种开发用于不了解浮点运算的人的系统。因此,浮点数的比较实现不会暴露给使用该系统的人。目前,浮点数的比较如下所示(由于遗留原因,此无法更改):

// If either number is not finite, do default comparison
if (!IsFinite(num1) || !IsFinite(num2)) {
    output = (num1 == num2);
} else {
    // Get exponents of both numbers to determine epsilon for comparison
    tmp = (OSINT32*)&num1+1;
    exp1 = (((*tmp)>>20)& 0x07ff) - 1023;
    tmp = (OSINT32*)&num2+1;
    exp2 = (((*tmp)>>20)& 0x07ff) - 1023;

    // Check if exponent is the same
    if (exp1 != exp2) {
        output = false;
    } else {
        // Calculate epsilon based on the magic number 47 (presumably calculated experimentally)?
        epsilon = pow(2.0,exp1-47);
        output = (fabs(num2-num1) <= eps);
    }
}

关键在于,我们根据数字的指数计算epsilon,以阻止界面用户做出浮点比较错误。 大注:这适用于那些不是软件程序员的人,所以当他们pow(sqrt(2), 2) == 2时,他们不会感到惊讶。也许这不是最好的主意,但就像我说的那样,它无法改变。

问题

我们无法确定如何向用户显示数字。在过去,他们只是将数字显示为15位有效数字。但是这会导致以下类型的问题:

>> SHOW 4.1 MOD 1
>>    0.099999999999999996
>> SHOW (4.1 MOD 1) == 0.1
>>    TRUE

由于生成的epsilon,比较称这是正确的。但是这个号码的打印让人感到困惑,0.099999999999999996 = 0.1怎么样? 我们需要一种方法来显示数字,使其代表与其相比的数字为真的最短有效位数。因此对于0.099999999999999996,这将是0.1,对于0.569999999992724327,它将是0.569999999992725 。

这可能吗?

4 个答案:

答案 0 :(得分:3)

您可以计算(num - pow(2.0, exp - 47))(num + pow(2.0, exp - 47)),将两者都转换为字符串并搜索范围之间的最小小数。

double的确切值是mantissa * pow(2.0, exp - 51),其整数值为mantissa,因此如果您添加/减去pow(2.0, exp - 47),则将尾数更改为2^4,这应该是完全可以表示没有舍入错误(除非在尾部下溢/溢出的角落情况下,即如果它是二进制<= pow(2,4)>= pow(2, 53) - pow(2,4)。你可能想要检查这些*)。

然后你有两个字符串,搜索数字不同的第一个位置并将其剪掉。虽然有很多舍入情况,特别是当你不只是想要在范围内的正确数字时,但数字接近输入数字(但可能不需要)。例如,如果你得到"1.23""1.24",你甚至可能想输出“1.235”。

这也表明你的例子是错误的。 0.569999999992724327的epsilon是(达到最大精度)0.000000000000003552713678800500929355621337890625。范围为0.5699999999927207738892320776358246803283691406250.569999999992727879316589678637683391571044921875,如果您希望进行舍入,则会在0.569999999992725(或0.569999999992723)处被截断。

更容易实现的大锤方法是将其输出到最大精度,减少一位数,将其转换回双倍,检查它是否正确比较。然后继续切割,直到比较失败。 (可以通过二分搜索进行改进)

*它们应该仍然可以完全表示,但是你的比较方法会表现得非常奇怪。考虑num1 == 1num2 == 1 - pow(2.0, -53) = 0.99999999999999988897769753748434595763683319091796875。差异0.00000000000000011102230246251565404236316680908203125低于你的epsilon 0.000000000000003552713678800500929355621337890625,但比较会说它们有所不同,因为它们有不同的指数

答案 1 :(得分:1)

是的,这是可能的。

double a=fmod(4.1,1);
cerr<<std::setprecision(0)<<a<<"\n";
cerr<<std::setprecision(10)<<a<<"\n";
cerr<<std::setprecision(20)<<a<<"\n";

产生

0.1
0.1
0.099999999999999644729

我认为你只需要确定哪种级别的显示精度与你的epsilon值相对应。

答案 2 :(得分:0)

  

我们需要一种方法来显示数字,使其代表最短的数字   一个数字与其相比的有效位数   TRUE。

你不能用蛮力的方式做到这一点吗?

float num = 0.09999999;
for (int precision = 0; precision < MAX_PRECISION; ++precision) {
    std::stringstream str;
    float tmp = 0;
    str << std::fixed << std::setprecision(precision) << num;
    str >> tmp;
    if (num == tmp) {
        std::cout << std::fixed << std::setprecision(precision) << num;
        break;
    }
}

答案 3 :(得分:-1)

考虑到您指定的限制,无法避免让用户感到困惑。首先,0.0999999999999996447比较等于0.1,而0.1000000000000003664比较等于0.1,但0.0999999999999996447不比较等于0.1000000000000003664。另一方面,2.00000000000001421比较等于2.0,但1.999999999999999778不等于2.0,即使它比2.00000000000001421更接近2.0。

享受。