如何检测基数10十进制是否可以在基数2中精确表示

时间:2009-01-26 20:25:13

标签: c++

作为数值库测试的一部分,我需要选择可以在基数2中精确表示的10位十进制数。如何在C ++中检测基数为10的十进制数是否可以在基数2中精确表示?

我的第一个猜测如下:

bool canBeRepresentedInBase2(const double &pNumberInBase10)
{
    //check if a number in base 10 can be represented exactly in base 2
    //reference: http://en.wikipedia.org/wiki/Binary_numeral_system
    bool funcResult = false;

    int nbOfDoublings = 16*3;
    double doubledNumber = pNumberInBase10;
    for (int i = 0; i < nbOfDoublings ; i++)
    {
        doubledNumber = 2*doubledNumber;
        double intPart;
        double fracPart = modf(doubledNumber/2, &intPart);
        if (fracPart == 0) //number can be represented exactly in base 2
        {
            funcResult = true;
            break;
        }
    }
    return funcResult;
}

我用以下值测试了这个函数:-1.0 / 4.0,0.0,0.1,0.2,0.205,1.0 / 3.0,7.0 / 8.0,1.0,256.0 / 255.0,1.02,99.005。对于-1.0 / 4.0,0.0,7.0 / 8.0,1.0,99.005,它返回true,这是正确的。

有更好的想法吗?

12 个答案:

答案 0 :(得分:5)

我认为你所寻找的是一个数字,它有一个小数部分,它是2的负幂序列的总和(又称:2的2次幂)。我相信这应该始终能够在IEEE浮点数/双打中完全表示。

例如:

0.375 =(1/4 + 1/8)应具有精确的表示。

如果你想生成这些。您可以尝试这样做:

#include <iostream>
#include <cstdlib>

int main() {
    srand(time(0));
    double value = 0.0;
    for(int i = 1; i < 256; i *= 2) {
        // doesn't matter, some random probability of including this
        // fraction in our sequence..
        if((rand() % 3) == 0) {
            value += (1.0 / static_cast<double>(i));        
        }
    }
    std::cout << value << std::endl;
}
编辑:我相信你的功能有一个破碎的界面。如果你有这个会更好:

bool canBeRepresentedExactly(int numerator, int denominator);

因为并非所有分数都具有精确的表示形式,但是当你把它推到一个双精度表中时,你已经选择了二进制表示法......打败了测试的目的。

答案 1 :(得分:4)

如果你正在检查它是否是二进制文件,它将始终返回true。如果您的方法采用double作为参数,则该数字已经以二进制表示(double是二进制类型,通常为64位)。看看你的代码,我认为你实际上是在尝试查看它是否可以完全表示为整数,在这种情况下,为什么你不能只是转换为int,然后回到double并且与原始相比。存储在double中的任何整数都在int可表示的范围内,应该是精确的IIRC,因为64位double有53位尾数(我假设32位为int)。这意味着如果它们相等,则为整数。

答案 2 :(得分:2)

如果您传入的是double,那么根据定义,它已经用二进制表示,如果没有,那么您已经失去了准确性。

也许尝试将分数的分子和分母传递给函数。然后你没有失去准确性,可以检查你是否能想出答案的二进制表示,与你传入的分数相同。

答案 3 :(得分:1)

正如rmeador所指出的那样,接受双倍可能不是一个好主意,因为这个数字已被转换为双数,可能与您试图检查的数字近似。

因此,以非常抽象的方式,您应该将支票分成整数和小数。整数不应该太大,以至于尾数不能表示所有整数,(例如,9007199254740993不能用64位fp正确表示) 在精神上,小数点可能更容易一些,因为如果小数点后面的任何内容(例如xxx.yyy中的yyy)包含除2以外的任何因子,浮点重复以尝试表示它。这就是为什么1/3不能用基数10 =基数(2 * 5)的有限数字表示的原因...见Recurring Decimal

编辑:正如评论所指出的,如果十进制数的因子不是1/2,那么这就是数学上正确的说法......

答案 4 :(得分:1)

正如其他人所提到的,你的方法没有按你的意思行事,因为你传递了一个表示为(二进制)双精度的数字。如果您传递的数字是integer/2^48形式,则该方法实际检测到。对于(1+2^-50)(二进制)和259/255之类的数字,这应该会失败。

如果你真的想测试一个数字是否可以通过有限二进制字符串完全表示,你必须以精确的形式传递一个数字。

答案 5 :(得分:1)

你问过C ++,但也许这个算法会有所帮助。我使用“EE”来表示“完全可以表达为浮动。”

以要测试的数字的十进制表示开头。删除任何尾随零(即,0.123450000变为0.12345)。

1)如果数字不是整数,检查最右边的数字是否为5.如果不是,则停止 - 数字不是EE。 2)将数字乘以2.如果结果是整数,则停止 - 数字为EE。否则,请返回步骤1.

我没有这方面的严格证据,而是“温暖的模糊”。启动计算器并输入您最喜欢的2的分数幂,如0.0000152587890625。将它添加到自己几十次(我只是点击“+”一次然后“=”多次)。如果小数点右边有任何非零数字,则最后一位数字始终为5.

答案 6 :(得分:1)

你不能传递IN双精度因为它已经失去了精度。您应该能够使用Double的toString()方法来检查这一点。 (Java中的例子)

public static Boolean canBeRepresentedInBase2(String thenumber)
{
    // Reuturns true of the parsed Double did not loose precision.
    // Only works for numbers that are not converted into scientific notation by toString.
    return thenumber.equals(Double.parseDouble(thenumber).toString())
}

答案 7 :(得分:0)

甚至更容易:

return pNumber == floor(pNumber);

另一方面,如果你有一些奇怪的小数表示(分子分母对,或带有小数的字符串,或其他东西),你真的想知道这个值是否可以精确地表示为double,这有点难。

但是你需要一个不同的参数......

答案 8 :(得分:0)

我认为这不是他要求的......我认为他正在寻找一种解决方案,告诉他一个数字是否可以二进制形式完全表示。例如,33.3 ..这是一个数字不能用二进制表示,因为它将永远存在,所以根据你的FPU设置,它将被表示为类似“33.333333333333336”。所以,看起来他的方法可以完成这项工作。我不知道有什么更好的办法。 \

答案 9 :(得分:0)

给定一个数r,它可以用基数2中的有限精度精确表示,如果r可以写成r = m / 2 ^ n,其中m,n是整数,n> = 0。

例如,1/7没有有限的二进制表达式,也不能用基数2中的有限表达式写入1/6和1/10。

但是1/4 + 1/32 + 1/1024,在基数中有一个有限的表达式。

PS:一般来说,你可以在基数b中用有限数字表示一个数字r iff r = m / b ^ n其中m,n是整数,n> = 0。

PPS:几乎所有人都曾说过使用双倍作为输入是一个坏主意,因为你正在失去精确度,你最终会得到一个不同的数字。

答案 10 :(得分:0)

无视使用双重的一般批评...... 对于一般有限小数,您可以使用以下算法确定它是否具有二进制的有限表示:

  1. 提取小数 f 的小数部分。
  2. 确定 f x 10 b = c ,其中 b 和< em> c 是整数。
  3. 确定2 d &gt; = 10 b ,其中 d 是整数。
  4. 如果 c x 2 b / 10 b 是一个整数,那么十进制在二进制中有一个有限的表示。否则,它没有。
  5. 你可以将它概括为任何两个基础。

答案 11 :(得分:0)

这是C#中的代码,它可以工作。因为它与Decimal数据一起使用 - 在使用double的原始代码中没有出现固有的舍入错误。 (使用基数10而不是基数2的C#存储中的小数 - 这是双重的)

static bool canBeRepresentedInBase2(decimal pNumberInBase10)
{
    //check if a number in base 10 can be represented exactly in base 2
    //reference: http://en.wikipedia.org/wiki/Binary_numeral_system
    bool funcResult = false;

    int nbOfDoublings = 16*3;
    decimal doubledNumber = pNumberInBase10;
    for (int i = 0; i < nbOfDoublings ; i++)
    {
        doubledNumber = 2*doubledNumber;
        decimal intPart;
        decimal fracPart = ModF(doubledNumber/2, out intPart);
        if (fracPart == 0) //number can be represented exactly in base 2
        {
                funcResult = true;
                break;
        }
    }
    return funcResult;
}

static decimal ModF(decimal number, out decimal intPart)
{
    intPart = Math.Floor(number);
    decimal fractional = number - (intPart);
    return fractional;
}

使用以下代码进行测试(其中WL执行Console.WritelLine - SnippetCompiler) WL(canBeRepresentedInBase2(-1.0M / 4.0M)); //真正 WL(canBeRepresentedInBase2(0.0M)); //真正 WL(canBeRepresentedInBase2(0.1M)); //假 WL(canBeRepresentedInBase2(0.2M)); //假 WL(canBeRepresentedInBase2(0.205M)); //假 WL(canBeRepresentedInBase2(1.0M / 3.0M)); //假 WL(canBeRepresentedInBase2(7.0M / 8.0M)); //真正 WL(canBeRepresentedInBase2(1.0M)); //真正 WL(canBeRepresentedInBase2(256.0M / 255.0M)); //假 WL(canBeRepresentedInBase2(1.02M)); //假 WL(canBeRepresentedInBase2(99.005M)); //假 WL(canBeRepresentedInBase2(2.53M)); //假