如何使用位操作在java中获取数字的绝对值

Posted

技术标签:

【中文标题】如何使用位操作在java中获取数字的绝对值【英文标题】:how to get absolute value of a number in java using bit manipulation 【发布时间】:2014-02-21 02:03:13 【问题描述】:

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

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

请帮忙

【问题讨论】:

【参考方案1】:

一个否定:

-n

与二进制补码相同:

~n + 1

问题在于,如果值

n >>> 31

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

~1010 == 1010 ^ 1111

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

n >> 31

绝对值说:

如果n 否则,什么也不做

所以把它放在一起我们可以做以下事情:

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

计算:

如果 n 否则,将其与全 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 &lt; 0 ? -n : n,这是微不足道的。相比之下,实际上循环展开完全糟糕。我猜只是好奇。这是我的基准:

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

(bit hacks abs 是非专利的shown here,这与我的想法基本相同,只是有点难以理解。)

【讨论】:

不错的解决方案。我的仍然需要一个比较。 谢谢。我想没有面具是没有办法的 为了傻笑,我添加了一个没有添加的版本。 请注意Math.abs(Integer.MIN_VALUE) 的结果是-2147483648【参考方案2】:

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

i = ~i; // i equals not i

您可以使用Math.max() 函数始终获得肯定

public static int abs(int i) 
    return Math.max(i,~i);

【讨论】:

【参考方案3】:

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

int sign = i >> 31;

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

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

if (sign == 1)
    i = ~i + 1;

【讨论】:

我想出了第一部分,只是我想知道i = -i的按位替代方案@ 真的没有。你可以做 i = ~i + 1,但这仍然不是完全按位。【参考方案4】:

我想你会发现这首小曲正是你要找的:

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

    return v + mask ^ mask;

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

    v + mask ^ mask: 2.0844380704220384 abs/ns (v ^ mask) - mask: 2.0819764093030244 abs/ns 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;
    


【讨论】:

【参考方案5】:

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

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;
    

【讨论】:

以上是关于如何使用位操作在java中获取数字的绝对值的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 64 位浏览器和 64 位 java 插件在 64 位 Linux 上获取 32 位 JRE 路径

java - 如何添加 - 每个字符串数字的第三位数字[重复]

如何在合理的时间内将绝对庞大的数字转换为字符串?

如何使用内在函数对 double 执行绝对值? [复制]

java如何获取文件的相对路径啊?

如何使用按位运算获取整数的第N位?