使用按位运算符除以0(除以0的模拟)

时间:2015-03-20 15:26:20

标签: java bitwise-operators

我们知道我们可以使用按位运算符来划分任意两个数字。例如:

int result = 10 >> 1; //reult would be 5 as it's like dividing 10 by 2^1

我们有没有机会使用位操作将数字除以0?

编辑1:如果我改写我的问题,我想实际将数字除以零并打破我的机器。我该怎么做?

编辑2:让我们暂时忘掉Java。无论使用何种编程语言,机器是否可以将数字除以0?

编辑3:由于实际上不可能这样做,有没有办法可以使用接近0的非常小的数字来模拟这个?

另一个编辑:有些人提到CPU硬件会阻止除以0.我同意,没有直接的方法可以做到。让我们看一下这段代码:

i = 1;
while(0 * i != 10){
    i++;
}

我们假设i的最大值没有上限。在这种情况下,没有编译器错误,CPU也不会抵制这一点。现在,我希望我的机器能够找到与0相乘的数字给出一个结果(显然永远不会发生)或者尝试死亡。

所以,因为有办法做到这一点。如何通过直接操作位来实现这一点?

最终编辑:如何在不使用按位运算符的情况下在Java中执行二进制除法? (对不起,这纯粹与标题相矛盾。)

注意:我尝试用0模拟divison并发布了我的答案。但是,我正在寻找一种更快的方法。

5 个答案:

答案 0 :(得分:6)

如果您想要的是比 division by repeated subtraction (您发布的)更快的分割方法,并且当您尝试除以零时无限期运行,则可以实现自己的版本 Goldschmidt division ,并且当除数为零时不会抛出错误。

算法的工作原理如下:

1. Create an estimate for the factor f
2. Multiply both the dividend and the divisor by f
3. If the divisor is close enough to 1
    Return the dividend
4. Else
    Go back to step 1

通常,我们需要在开始之前缩小被除数和除数,以便0 < divisor < 1得到满足。在这种情况下,由于我们要除以零,所以不需要这一步。我们还需要选择一个任意精度,超出该精度我们认为结果足够好。

代码没有检查divisor == 0,就像这样:

static double goldschmidt(double dividend, double divisor) {
    double epsilon = 0.0000001;
    while (Math.abs(1.0 - divisor) > epsilon) {
        double f = 2.0 - divisor;
        dividend *= f;
        divisor *= f;
    }
    return dividend;
}

这比通过重复减法方法的除法快得多,因为它以二次方式而不是线性方式收敛到结果。当除以零时,它并不重要,因为两种方法都不会收敛。但是,如果你尝试除以一小部分,例如10^(-9),你可以清楚地看到差异。

如果您不希望代码无限期地运行,但是在除以0时返回Infinity,则可以将其修改为在dividend到达{{}时停止1}}:

Infinity

如果static double goldschmidt(double dividend, double divisor) { double epsilon = 0.0000001; while (Math.abs(1.0 - divisor) > 0.0000001 && !Double.isInfinite(dividend)) { double f = 2.0 - divisor; dividend *= f; divisor *= f; } return dividend; } dividend的起始值是divisordividend >= 1.0,那么最多会得到divisor == 0.0 Infinity次迭代。这是因为最糟糕的情况是2^10,你需要将它乘以1024(dividend == 1)1024次才能到f = 2.0 - 0.0,这大于Double.MAX_VALUE

Goldschmidt部门在AMD Athlon CPU中实施。如果您想了解一些较低级别的详细信息,可以查看以下文章: Floating Point Division and Square Root Algorithms and Implementation in the AMD-K7 TM Microprocessor

修改

发表评论:

请注意,您发布的Restoring Division方法的代码会迭代2048(2^1024)次。我将代码中2^11的值降低到1024,因此我们可以比较执行相同迭代次数的两种方法。

我用n运行了两次实现100000次,这是Goldschmidt最糟糕的情况,并测量了这样的运行时间:

dividend == 1

Goldschmidt部门的运行时间约为290毫秒,代码的运行时间约为23000毫秒(23秒)。因此,在此测试中,此实现大约 80x 。这是预期的,因为在一种情况下,我们正在进行long begin = System.nanoTime(); for (int i = 0; i < 100000; i++) { goldschmidt(1.0, 0.0); // changed this for restoringDivision(1) to test your code } long end = System.nanoTime(); System.out.println(TimeUnit.NANOSECONDS.toMillis(end - begin) + "ms"); 次乘法,而在另一种情况下,我们正在使用double

您的实施的优势在于,由于您使用的是BigInteger,因此您可以将结果设置为BigInteger支持的结果,而此处的结果仅受BigInteger的限制。< / p>

在实践中,当除以零时,Goldschmidt除法在每次迭代时将股息加倍,相当于左移,直到达到最大可能值。所以使用Double.MAX_VALUE的等价物将是:

BigInteger

函数static BigInteger divideByZero(int dividend) { return BigInteger.valueOf(dividend) .shiftLeft(Integer.MAX_VALUE - 1 - ceilLog2(dividend)); } static int ceilLog2(int n) { return (int) Math.ceil(Math.log(n) / Math.log(2)); } 是必需的,因此ceilLog2()不会导致溢出。根据您分配的内存量,这可能会导致shiftLeft()异常。所以这里有一个折衷方案:

  • 您可以让分割模拟运行得非常快,但结果上限为java.lang.OutOfMemoryError: Java heap space

  • 你可以得到与Double.MAX_VALUE一样大的结果,但是可能需要太多的记忆和时间来达到这个限制。

编辑2:

解决您的新评论:

  

请注意,您的更新代码中没有发生任何分组。它只是试图找到最大可能的BigInteger

首先,让我们来看看Goldschmidt部门在2^(Integer.MAX_VALUE - 1)时退化为左移:

divisor == 0

因子static double goldschmidt(double dividend, double divisor) { double epsilon = 0.0000001; while (Math.abs(1.0 - 0.0) > 0.0000001 && !Double.isInfinite(dividend)) { double f = 2.0 - 0.0; dividend *= f; divisor = 0.0 * f; } return dividend; } 将始终等于f,并且第一个2.0条件将始终为真。因此,如果我们消除冗余:

while

假设static double goldschmidt(double dividend, 0) { while (!Double.isInfinite(dividend)) { dividend *= 2.0; } return dividend; } dividend,我们可以使用向左移位进行相同的乘法运算:

Integer

如果我们可以达到的最大值是static int goldschmidt(int dividend) { while (...) { dividend = dividend << 1; } return dividend; } ,我们需要循环2^n次。 n时,这相当于:

dividend == 1

static int goldschmidt(int dividend) { return 1 << n; } 时,我们需要减去dividend > 1以防止溢出:

ceil(log2(dividend))

因此显示Goldschmidt分部相当于static int goldschmidt(int dividend) { return dividend << (n - ceil(log2(dividend)); } 时的左移。

  

然而,将位向左移位会将右侧的位填充为0.尝试使用小的红利运行此操作并左移(一次或两次以检查结果)。这件事永远不会到达divisor == 0

现在我们已经看到2^(Integer.MAX_VALUE - 1)左移相当于n的乘法,让我们看看2^n版本是如何工作的。考虑以下示例,显示如果有足够的可用内存并且被除数是2的幂,我们将到达BigInteger

2^(Integer.MAX_VALUE - 1)

dividend = 1

BigInteger.valueOf(dividend).shiftLeft(Integer.MAX_VALUE - 1 - ceilLog2(dividend)) = BigInteger.valueOf(1).shiftLeft(Integer.MAX_VALUE - 1 - 0) = 1 * 2^(Integer.MAX_VALUE - 1) = 2^(Integer.MAX_VALUE - 1)

dividend = 1024

如果BigInteger.valueOf(dividend).shiftLeft(Integer.MAX_VALUE - 1 - ceilLog2(dividend)) = BigInteger.valueOf(1024).shiftLeft(Integer.MAX_VALUE - 1 - 10) = 1024 * 2^(Integer.MAX_VALUE - 1) = 2^10 * 2^(Integer.MAX_VALUE - 1 - 10) = 2^(Integer.MAX_VALUE - 1) 不是2的幂,我们会尽可能接近dividend2^(Integer.MAX_VALUE - 1)

答案 1 :(得分:2)

您的要求是不可能的。

除以0在数学上是不可能的。这个概念不存在,所以没有办法模拟它。

如果您实际上尝试进行限制操作(除以0+0-),那么仍无法使用按位进行操作,因为它只允许您除以2的幂

这里使用按位运算的例子仅除以2的幂

10 >> 1 = 5

查看您发布的评论,如果您想要的只是在用户尝试除以0时退出程序,则只需验证它:

if(dividant == 0)
    System.exit(/*Enter here the exit code*/);

这样你就可以避免ArithmeticException。


在与您交换了几条评论之后,您似乎想要做的就是将操作系统崩溃除以0。

不幸的是,据我所知,任何可以在计算机上编写的语言都可以通过0来处理除法。

想到一个简单的计算器,你支付1美元,尝试除以0,它甚至不会崩溃,它只会抛出一个错误消息。无论如何,这可能在处理器级别上得到验证。


修改

对您的问题进行多次修改/评论后,您似乎正在尝试检索 Infinity 除以0+0-,该子句为0。

你可以通过双重/浮动分割实现这一点。

System.out.println(1.0f / 0.0f);//prints infinity
System.out.println(1.0f / -0.0f);//prints -Infinity
System.out.println(1.0d / 0.0d);//prints infinity
System.out.println(1.0d / -0.0d);//prints -Infinity

请注意,即使你写了0.0,这个值也不是真的等于0,它只是非常接近它。

答案 2 :(得分:1)

不,没有,因为你只能使用右移来除以2的幂。

答案 3 :(得分:1)

模拟无符号整数除法的一种方法(无论使用除数)是通过重复减法除法:

BigInteger result = new BigInteger("0");
int divisor = 0;
int dividend = 2;

while(dividend >=  divisor){

    dividend = dividend - divisor;              
    result = result.add(BigInteger.ONE);            

}

第二种方法是使用Restoring Division算法(Thanks @harold),这比第一种算法快:

int num = 10;
BigInteger den = new BigInteger("0");
BigInteger p = new BigInteger(new Integer(num).toString());
int n = 2048; //Can be changed to find the biggest possible number (i.e. upto 2^2147483647 - 1). Currently it shows 2^2048 - 1 as output 
den = den.shiftLeft(n);

BigInteger q = new BigInteger("0");

for(int i = n; i > 0; i -= 1){
    q = q.shiftLeft(1);
    p = p.multiply(new BigInteger("2"));
    p = p.subtract(den);

    if(p.compareTo(new BigInteger("0")) == 1 
        || p.compareTo(new BigInteger("0")) == 0){
        q = q.add(new BigInteger("1"));
    } else {
        p = p.add(den);
    }
}

System.out.println(q);

答案 4 :(得分:0)

正如其他人所说,你不能在数学上除以0。

但是如果你想要方法除以0,你可以使用Double中的一些常量。例如,你可以有一个方法

public static double divide(double a, double b){
    return b == 0 ? Double.NaN : a/b;
}

public static double posLimitDivide(double a, double b){
    if(a == 0 && b == 0)
        return Double.NaN;
    return b == 0 ? (a > 0 ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY) : a/b;

这将返回x接近+ b的a / x的限制。

这些应该没问题,只要你用它们的任何方法来解释它。好的,我的意思是坏的,如果你不小心,可能会导致不确定的行为。但这是用实际值而不是异常来表示结果的明确方法。