2

我想在java中实现一个函数来获取一个数字的绝对值:如果它是正数则不做任何事情,如果它是负数,则转换为正数。

我只想使用位操作而不是数字比较器来做到这一点。

请帮忙

4

5 回答 5

8

好吧,否定:

-n

与二进制补码相同:

~n + 1

问题在于,如果值 < 0,您只想否定。您可以通过使用逻辑移位来查看是否设置了 MSB:

n >>> 31

补码与全 1 的 XOR 相同,类似于(对于 4 位整数):

~1010 == 1010 ^ 1111

我们可以通过算术右移得到一个掩码:

n >> 31

绝对值 说:

  • 如果 n < 0,则取反(取补并加 1)
  • 否则,什么也不做

因此,将它们放在一起我们可以执行以下操作:

static int abs(int n) {
    return (n ^ (n >> 31)) + (n >>> 31);
}

计算:

  • 如果 n < 0,则将其与所有 1 进行异或并加 1
  • 否则,将其与所有 0 进行异或并添加 0

我不确定是否有一种简单的方法可以在不添加的情况下做到这一点。加法涉及任意数量的进位,即使是简单的增量。

例如 2 + 1 没有进位:

10 + 1 == 11

但是 47 + 1 有 4 个进位:

101111 + 1 == 110000

使用按位/位移位进行加法和进位基本上只是一个循环展开且毫无意义。

(编辑!)

傻傻的,这里有一个增量和进位:

static int abs(int n) {
    int s = n >>> 31;
    n ^= n >> 31;

    int c;
    do {
        c = (n & s) << 1;
        n ^= s;
    } while((s = c) != 0);

    return n;
}

它的工作方式是翻转第一位,然后继续翻转直到找到 0。所以工作就是展开循环。循环体可以用一个有点荒谬的复合单线表示。

static int abs(int n) {
    int s = n >>> 31;
    n ^= n >> 31;

    int c = (n & s) << 1;
    c = ((n ^= s) & (s = c)) << 1; // repeat this line 30 more times
    n ^= s;

    return n;
}

所以有一个只使用按位和位移的绝对值。

这些并不比 Math.abs 快。Math.abs 只是返回n < 0 ? -n : n微不足道的。相比之下,实际上循环展开完全糟糕。我猜只是好奇。这是我的基准:

数学.abs:4.627323150634766ns
移位/异或/添加绝对值:6.729459762573242ns
循环绝对值:12.028789520263672ns
展开 abs:32.47122764587402ns
位黑客 abs:6.380939483642578ns

(bit hacks abs 是这里显示的非专利技术,它与我的想法基本相同,只是有点难以理解。)

于 2014-02-21T02:35:47.530 回答
2

您可以通过取逻辑否定来将二进制恭维数变为正数或负数

i = ~i; // i equals not i

您可以使用该Math.max()功能始终获得正面

public static int abs(int i) {
    return Math.max(i,~i);
}
于 2014-02-21T02:11:01.840 回答
0

这取决于您使用的号码类型。对于 int,使用

int sign = i >> 31;

这得到符号位,正数为 0,负数为 1。对于其他基元类型,将 31 替换为基元使用的位数减 1。

然后,您可以在 if 语句中使用该符号。

if (sign == 1)
    i = ~i + 1;
于 2014-02-21T02:08:33.070 回答
0

我想你会发现这个小曲就是你要找的:

int abs(int v) {
    int mask = v >> Integer.SIZE - 1;

    return v + mask ^ mask;
}

它基于Bit Twiddling Hacks绝对值方程,不使用比较操作。如果您不允许使用加法,那么(v ^ mask) - mask是一种替代方法。这个函数的价值是相当值得怀疑的;因为它几乎与 Math.abs 的实现一样清晰,而且速度稍快(至少在 i7 上):

  1. v + mask ^ mask: 2.0844380704220384 abs/ns
  2. (v ^ mask) - mask: 2.0819764093030244 abs/ns
  3. Math.abs: 2.2636355843860656 abs/ns

这是一个测试,证明它可以在整个整数范围内工作(测试在 Java 7 update 51 下的 i7 处理器上运行不到 2 分钟):

package test;

import org.hamcrest.core.Is;
import org.junit.Assert;
import org.junit.Test;

public class AbsTest {

    @Test
    public void test() {
        long processedCount = 0L;
        long numberOfIntegers = 1L << Integer.SIZE; //4294967296L
        int value;

        for (value = Integer.MIN_VALUE; processedCount < numberOfIntegers; value++) {
            Assert.assertEquals((long) abs(value), (long) StrictMath.abs(value));
            if (processedCount % 1_000_000L == 0L) {
                System.out.print(".");
            }
            processedCount++;
        }
        System.out.println();
        Assert.assertThat(processedCount, Is.is(numberOfIntegers));
        Assert.assertThat(value - 1, Is.is(Integer.MAX_VALUE));
    }

    private static int abs(int v) {
        int mask = v >> Integer.SIZE - 1;

        return v + mask ^ mask;
    }

}
于 2014-02-21T02:56:50.340 回答
0

这个问题可以分解为两个简单的步骤:

1. 如果 >= 0 则只返回数字。

2. 如果小于0(即负数),则翻转表示该数为负数的第一位。这可以通过带有 -1 和数字的 XOR 操作轻松完成;然后只需添加 +1 来处理偏移量(有符号整数从 -1 而不是 0 开始)。

public static int absolute(int a) {
    if (a >= 0) {
        return a;
    } else  {
        return (a ^ -1) + 1;
    }
}
于 2015-06-24T08:35:48.287 回答