如何确定一个数字是正数还是负数?

57

在面试中,我被问到如何确定一个数字是正数还是负数。规则是我们不能使用关系运算符,如<>,也不能使用内置的Java函数(比如substringindexOfcharAtstartsWith),不能使用正则表达式或API。

我对此进行了一些研究,下面给出了代码,但它只适用于整数类型。 但是他们要求我编写一个通用代码,可以处理floatdoublelong

 // This might not be better way!!

 S.O.P ((( number >> 31 ) & 1) == 1 ? "- ve number " : "+ve number );

你有什么想法吗?


3
如果有一种方法可以将[某个东西]转换成一个二进制数组,你可以查看最高位来确定这个数字是正数还是负数... 出于好奇,这样的技能会对你有什么帮助?虽然我还没有工作过,但是...他们会从你手中夺走基本运算符似乎有点奇怪:P - Warty
147
愚蠢而牵强的面试问题 - Mitch Wheat
4
“像这样的技能会如何帮助你?”这个问题是因为,在拥有5年J2EE经验后(我曾被面试Java职位,但却没有预料到他们会问这个问题:(),由于我的计算机科学背景,我感到无法给出解决方案而感到遗憾。 - Dead Programmer
2
@Stephen C:我理解原因。谷歌、微软等公司多年前就意识到这些不是“正确”的面试问题...... - Mitch Wheat
8
然而,面试官的问题不够精确。引用:「我们不应该使用条件运算符」。这个「==」也是一个条件运算符,因此根本无法回答这个问题。括号中的两个运算符(<,>)根本不能作为一个合适的解释。面试官必须使用明确的措辞来表达他的意思,比如说:「关于条件运算符,你只能使用 == 运算符」。调查不精确的问题是程序员生活中真正需要的技能。 - OlimilOops
显示剩余17条评论
32个回答

67
整数情况很容易。双精度情况更棘手,直到你想起无穷大这个概念。
注意:如果你认为双精度常量是“API的一部分”,那么你可以用溢出表达式来替代它们,比如 1E308 * 2
int sign(int i) {
    if (i == 0) return 0;
    if (i >> 31 != 0) return -1;
    return +1;
}
int sign(long i) {
    if (i == 0) return 0;
    if (i >> 63 != 0) return -1;
    return +1;
}
int sign(double f) {
    if (f != f) throw new IllegalArgumentException("NaN");
    if (f == 0) return 0;
    f *= Double.POSITIVE_INFINITY;
    if (f == Double.POSITIVE_INFINITY) return +1;
    if (f == Double.NEGATIVE_INFINITY) return -1;

    //this should never be reached, but I've been wrong before...
    throw new IllegalArgumentException("Unfathomed double");
}

7
双重解决方案非常巧妙/精妙。 - Ivan
2
根据之前对其他答案的评论,我们可以说Double.POSITIVE_INFINITY是API的一部分。我很喜欢它。可以通过(double)转换使其完全通用。 - Chris Cudmore
1
@chris 有一个注释说,命名的双精度常量可以被字面表达式替换。除非必要,否则我不会牺牲清晰度。 - Craig Gidney
1
非常有帮助,谢谢。这告诉我不应该使用“>0”和“<0”。 - jjz
1
@Nom1fan https://en.wikipedia.org/wiki/NaN#与NaN的比较 - Craig Gidney
显示剩余7条评论

36
以下方法非常糟糕,如果在任何工作中使用都会被解雇... 这取决于您是否遇到堆栈溢出异常[或Java称之为其他名称]... 它仅适用于不像疯狂偏离0的正数。 负数没有问题,因为您会溢出到正数,最终遇到堆栈溢出异常[这将返回false或“是,它是负数”]。
Boolean isPositive<T>(T a)
{
  if(a == 0) return true;
  else
  {
    try
    {
      return isPositive(a-1);
    }catch(StackOverflowException e)
    {
      return false; //It went way down there and eventually went kaboom
    }
  }
}

27
那个让我笑了。 :D - Rekin
6
谢谢,但这就像吹一个炸弹并知道结果一样。 - Dead Programmer
7
如果数字是1.5,那么它不会直接得出0,你也应该考虑到0.5-0.5,否则这个想法很好 :) - jmj
1
@Suresh S,他们说你的代码必须“高效”吗?; @org.life.java:grah,你抓住我了 :P - Warty
1
它对0.0001不起作用,对吧?任何正数且非整数的数字都是... - Jason Goemaat
显示剩余6条评论

17

除了[0..2],这将适用于所有内容。

boolean isPositive = (n % (n - 1)) * n == n;

你可以像这样制作一个更好的解决方案(适用于除 [0..1] 外的所有情况)

boolean isPositive = ((n % (n - 0.5)) * n) / 0.5 == n;

通过将0.5变成类似于2^m(m为整数)的形式,您可以获得更好的精度:

boolean isPositive = ((n % (n - 0.03125)) * n) / 0.03125 == n;

1
很棒的解决方案!对于0/1,你可以将其转换为整数,如果等于0或1,则返回true(或类似的内容)。 - Beep beep
"==不是一个条件运算符吗?" - MSpeed
@billynomates:是的。然而,看着这些答案,即使是那些无效的答案,如果你不能使用它,你实际上没有解决方案。毕竟,它并没有明确说明是问题的一部分。 - nanda

8
你可以像这样做:

你可以这样做:

((long) (num * 1E308 * 1E308) >> 63) == 0 ? "+ve" : "-ve"

这里的主要想法是我们将其转换为long类型并检查最高有效位的值。由于在将double/float转换为long时,介于-1和0之间的数将舍入为零,因此我们通过乘以大的doubles来使负的float/double小于-1。需要两次乘法是因为存在subnormals(不过它不需要那么大)。


3
如果将一个过大的双精度数转换为long类型,根据Java语言规范,将会得到long类型所能表示的最大值(负数同理)。无论如何,如果这个数在long类型范围内,我们基本上都相当于将它乘以无穷大(除了0)。 - Nabb
既然您快速回答了次正常数的问题,我考虑接受您的答案。 - Dead Programmer

6

这个怎么样?

return ((num + "").charAt(0) == '-');

糟糕。我手头没有Java编译器,虽然在C#中可以这样做,但以为在这里也可能有效。 - Beep beep
这将是一个聪明的替代toString,并且它遵循指南。虽然我不确定Java是否允许这种语法,但C#和JavaScript会允许。我喜欢这个逻辑。 - Warty
1
@Peter,不过charAt被认为是Java API的一部分吗? - Warty
1
使用indexOf很简单,但是(num+"").indexOf("-") > 1无法使用。 - Dead Programmer
@SureshSankar 我觉得你的意思是 (num+"").indexOf("-") == 0; 因为如果字符串中不存在该字符,它会返回-1,如果该字符不在字符串的开头,则返回大于0的值。 - Ape-inago
显示剩余4条评论

3
// Returns 0 if positive, nonzero if negative
public long sign(long value) {
    return value & 0x8000000000000000L;
}

调用方式:

long val1 = ...;
double val2 = ...;
float val3 = ...;
int val4 = ...;

sign((long) valN);

将double / float / integer转换为long时,应保留符号,如果不保留实际值...

1
没有 API 的 Double.doubleToLongBits,为什么要与 0x8000000000000000L 进行 AND 操作?这个数字有什么重要意义吗? - Dead Programmer
谢谢,我认为这个答案很接近,我正在使用1E08数字测试您的代码。我会告诉您结果的。 - Dead Programmer
2
你在方法定义中使用了void并返回了某个东西。 - Dead Programmer

3

你说

我们不应该使用条件运算符

但这是一个诡计性的要求,因为 == 也是一个条件运算符。 ? :whilefor循环中也内置了一个条件运算符。所以几乎所有人都未能提供符合所有要求的答案。

唯一构建没有条件运算符的解决方案的方法是使用查找表,而不是其他几个人的解决方案,这些解决方案可以归结为在遇到条件之前为0/1或字符。

以下是我认为可能适用于查找表的答案:

  • Nabb
  • Steven Schlansker
  • Dennis Cheung
  • Gary Rowe

2

这个解决方案使用模数。是的,它也适用于0.5(测试在主方法中,见下文)。

public class Num {

    public static int sign(long x) {
        if (x == 0L || x == 1L) return (int) x;
        return x == Long.MIN_VALUE || x % (x - 1L) == x ? -1 : 1;
    }

    public static int sign(double x) {
        if (x != x) throw new IllegalArgumentException("NaN");
        if (x == 0.d || x == 1.d) return (int) x;
        if (x == Double.POSITIVE_INFINITY) return 1;
        if (x == Double.NEGATIVE_INFINITY) return -1;
        return x % (x - 1.d) == x ? -1 : 1;
    }

    public static int sign(int x) {
        return Num.sign((long)x);
    }

    public static int sign(float x) {
        return Num.sign((double)x);
    }

    public static void main(String args[]) {

        System.out.println(Num.sign(Integer.MAX_VALUE)); // 1
        System.out.println(Num.sign(1)); // 1
        System.out.println(Num.sign(0)); // 0
        System.out.println(Num.sign(-1)); // -1
        System.out.println(Num.sign(Integer.MIN_VALUE)); // -1

        System.out.println(Num.sign(Long.MAX_VALUE)); // 1
        System.out.println(Num.sign(1L)); // 1
        System.out.println(Num.sign(0L)); // 0
        System.out.println(Num.sign(-1L)); // -1
        System.out.println(Num.sign(Long.MIN_VALUE)); // -1

        System.out.println(Num.sign(Double.POSITIVE_INFINITY)); // 1
        System.out.println(Num.sign(Double.MAX_VALUE)); // 1
        System.out.println(Num.sign(0.5d)); // 1
        System.out.println(Num.sign(0.d)); // 0
        System.out.println(Num.sign(-0.5d)); // -1
        System.out.println(Num.sign(Double.MIN_VALUE)); // -1
        System.out.println(Num.sign(Double.NEGATIVE_INFINITY)); // -1

        System.out.println(Num.sign(Float.POSITIVE_INFINITY)); // 1
        System.out.println(Num.sign(Float.MAX_VALUE)); // 1
        System.out.println(Num.sign(0.5f)); // 1
        System.out.println(Num.sign(0.f)); // 0
        System.out.println(Num.sign(-0.5f)); // -1
        System.out.println(Num.sign(Float.MIN_VALUE)); // -1
        System.out.println(Num.sign(Float.NEGATIVE_INFINITY)); // -1
        System.out.println(Num.sign(Float.NaN)); // Throws an exception

    }
}

2

这段代码涵盖了所有情况和类型:

public static boolean isNegative(Number number) {
    return (Double.doubleToLongBits(number.doubleValue()) & Long.MIN_VALUE) == Long.MIN_VALUE;
}

该方法接受任何包装类(IntegerLongFloatDouble),并且由于自动装箱,可以接受任何原始数值类型(intlongfloatdouble)。该方法会检查高位是否设置了符号位。对于所有类型来说,符号位都是高位。如果设置了符号位,则返回true,否则返回false
当传递以下任何内容时,它将返回true
  • 任何负的int/Integer
  • 任何负的long/Long
  • 任何负的float/Float
  • 任何负的double/Double
  • Double.NEGATIVE_INFINITY
  • Float.NEGATIVE_INFINITY
否则返回false。请注意保留HTML标签。

非常好的解决方案,访问符号位的方法正是我正在寻找的! - ShellFish

1

尚未经过测试,但是可以说明我的想法:

boolean IsNegative<T>(T v) {
  return (v & ((T)-1));
}

我认为应该是 (v & ((v) - 1))。 - Mitch Wheat
我在这方面不太了解Java;但是为了使某些东西“通用”,您可以使用Java泛型-模板化参数,因此它应该适用于各种数字宽度;然后,您想要查看高位是否设置(不知道双精度或浮点数中哪个位可靠地给出符号,但可以计算出来- Java是否支持通用特化?)。 因此,您将-1作为适当宽度,并将其视为高位标志(在java iirc中没有sizeof())。 经过深思熟虑,我认为这段代码必须更加丑陋才能正常工作。 - Will
2
不错的想法,但我认为它会导致编译错误。你不能将其转换为类型参数。 - Stephen C
3
不行。Java泛型不支持基本类型,算术和位运算符不适用于包装器类型,自动拆箱再次无法以通用方式工作。 - Michael Borgwardt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接