我们知道我们可以使用按位运算符来划分任意两个数字。例如:
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并发布了我的答案。但是,我正在寻找一种更快的方法。
答案 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
的起始值是divisor
和dividend >= 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的幂,我们会尽可能接近dividend
,2^(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的限制。
这些应该没问题,只要你用它们的任何方法来解释它。好的,我的意思是坏的,如果你不小心,可能会导致不确定的行为。但这是用实际值而不是异常来表示结果的明确方法。