JavaScript中的Number.sign()

108

想知道有没有任何非平凡的方法来找到数字的符号 (signum函数)?
可能有比显而易见的方法更短、更快、更优雅的解决方案。

var sign = number > 0 ? 1 : number < 0 ? -1 : 0;

简短回答!

使用这个方法,您就能安全且快速地操作(来源:moz)。

if (!Math.sign) Math.sign = function(x) { return ((x > 0) - (x < 0)) || +x; };

您可能需要查看性能和类型转换比较的fiddle

很长一段时间已经过去了。下面的内容主要是出于历史原因。


结果

目前我们有以下解决方案:


1. 显而易见且快速

function sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }

1.1. 修改自kbec - 去掉一次类型转换,更高性能,更短 [最快]

function sign(x) { return x ? x < 0 ? -1 : 1 : 0; }

注意: sign("0") -> 1


2. 简洁、短小但不太快的 [最慢]

function sign(x) { return x && x / Math.abs(x); }

注意:sign(+-Infinity) -> NaN, sign("0") -> NaN

由于在JS中Infinity是一个合法的数字,因此这个解决方案似乎并不完全正确。


3. 这种方法... 但非常缓慢[最慢]

function sign(x) { return (x > 0) - (x < 0); }

4. 使用位移
快速,但sign(-Infinity) -> 0

function sign(x) { return (x >> 31) + (x > 0 ? 1 : 0); }

5. 安全类型的 [超级快速]

看起来浏览器(尤其是 Chrome 的 V8 引擎)会进行一些神奇的优化,这种解决方案比其他方案表现更好,甚至比 (1.1) 还要快,尽管它包含了两个额外的操作并且从逻辑上讲不可能更快。

function sign(x) {
    return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN;
}

工具

欢迎提出改进建议!


[离题] 接受的答案

  • Andrey Tarantsov - 艺术加分100,但不幸的是比明显的方法慢了大约5倍

  • Frédéric Hamidi - 目前为止最被赞同的答案,有点酷,但我认为这绝对不是正确的做法。另外,它不能正确处理无穷大的数字,你知道的,这也是数字。

  • kbec - 是对明显解决方案的改进。不算革命性,但综合考虑,我认为这种方法是最好的。请投他一票 :)


3
有时候,“0”是一个特殊情况。 - disfated
2
@disfated,哪一个?当然,如果你运行“测试一切”版本,Safe会拒绝测试特殊值,所以速度会更快!尝试运行only integers测试。此外,JSPerf只是在执行它的工作,这不是喜欢或不喜欢的问题。 :) - Alba Mendez
1
哇,我的方法很慢!感谢测试;我从来没有想到过。有趣的是,在 ACM ICPC 的早期,我们一直在使用 (x>0)-(x<0) 这种方式,因为它是 _最快的解决方案_(内联汇编被规则禁止),而且显然是正确的。 - Andrey Tarantsov
2
根据jsperf测试结果显示,typeof x === "number" 对性能有一些神奇的影响。请多运行几次,特别是在FF、Opera和IE上进行测试以明确结果。 - disfated
4
为了完整性,我添加了一个新的测试 http://jsperf.com/signs/7 用于 Math.sign() (0===0,不像“安全模式”那样快),该方法出现在 Firefox25 中并将在 Chrome 中推出。 - Alex K.
显示剩余13条评论
16个回答

0

非常类似于Martijn的答案

function sgn(x) {
    isNaN(x) ? NaN : (x === 0 ? x : (x < 0 ? -1 : 1));
}

我认为这种方式更易读。此外,它还可以理解那些可以被解释为数字的东西;例如,当输入 '-5' 时,它会返回 -1


0

我刚想问同样的问题,但在写完之前找到了解决方案,看到这个问题已经存在,但没有看到这个解决方案。

(n >> 31) + (n > 0)

通过添加三元运算符似乎更快 (n >> 31) + (n>0?1:0)


非常好。你的代码似乎比(1)快了很多。 (n>0?1:0) 由于没有类型转换,所以更快。唯一令人失望的是 sign(-Infinity) 的结果是 0。测试已更新。 - disfated

0

这是一个紧凑版本:

let sign=x=>2*(x>=0)-1
//Tests
console.log(sign(0)); //1
console.log(sign(6)); //1
console.log(sign(Infinity)); //1
console.log(sign(-6)); //-1
console.log(sign(-Infinity)); //-1
console.log(sign("foo")); //-1

如果您想处理 NaN 和其他边缘情况,请使用下面的代码(虽然它会更长):
let sign=x=>isNaN(x)?NaN:2*(x>=0)-1
//Tests
console.log(sign(0)); //1
console.log(sign(6)); //1
console.log(sign(Infinity)); //1
console.log(sign(-6)); //-1
console.log(sign(-Infinity)); //-1
console.log(sign("foo")); //NaN

如果你想让sign(0)也返回0:
let sign=x=>isNaN(x)?NaN:(x?2*(x>=0)-1:0)
//Tests
console.log(sign(0)); //0
console.log(sign(6)); //1
console.log(sign(Infinity)); //1
console.log(sign(-6)); //-1
console.log(sign(-Infinity)); //-1
console.log(sign("foo")); //NaN

对于任何不是数字的输入,输出不应该是 NaN 吗? - nviens

0
你可以对数字进行位移并检查最高有效位(MSB)。如果 MSB 是 1,则该数字为负数。如果是 0,则该数字为正数(或 0)。

@ NullUserException 我可能仍然有错,但从我的阅读中,“所有位运算符的操作数都以大端顺序和二进制补码格式转换为带符号的32位整数。” 来自 MDN - Brombomb
这仍然似乎是很多工作;你仍然需要将1和0转换为-1和1,并且还要处理0。如果OP只想要那个,那么更容易的方法就是使用var sign = number < 0 : 1 : 0 - NullUserException
@davin 所有数字都保证可以使用该位掩码吗?我输入了“-5e32”它就出错了。 - NullUserException
@NullUserExceptionఠ_ఠ,这里讨论的是当应用 ToInt32 标准时,保持相同符号的数字。如果你阅读了那里(第9.5节),会发现有一个模数影响着数字的值,因为32位整数的范围小于js Number类型的范围。所以对于那些值或无穷大的值,它将不起作用。尽管如此,我仍然喜欢这个答案。 - davin
@davin 你的建议似乎对于介于-1和0之间的负数无效。 - starwed
显示剩余2条评论

0
我知道的方法如下:

Math.sign(n)

var s = Math.sign(n)

这是原生函数,但由于函数调用的开销而最慢。然而,它可以处理“NaN”,而下面的其他函数可能只会假定为0(即Math.sign('abc')为NaN)。

((n>0) - (n<0))

var s = ((n>0) - (n<0));

在这种情况下,仅左侧或右侧可以基于符号为1。这将导致1-0(1),0-1(-1)或0-0(0)。

在Chrome中,这个速度似乎与下面的下一个速度相当。

(n>>31)|(!!n)

var s = (n>>31)|(!!n);

使用“符号传播右移”。基本上,将其向右移31位会丢弃除符号位以外的所有位。如果符号被设置,则结果为-1,否则为0。在|右侧,它通过将值转换为布尔值(0或1 [顺便说一句:非数字字符串,如!!'abc',在这种情况下变为0,而不是NaN])来测试正数,然后使用按位OR运算符组合位。

这似乎是跨浏览器的最佳平均性能(至少在Chrome和Firefox中表现最佳),但并非在所有浏览器中都是最快的。由于某种原因,在IE中三元运算符更快。

n?n<0?-1:1:0

var s = n?n<0?-1:1:0;

由于某种原因,在IE中最快。

jsPerf

执行的测试:https://jsperf.com/get-sign-from-value


0

IE 11 不支持 Math.sign。我正在将最佳答案与 Math.sign 答案结合起来:

Math.sign = Math.sign || function(number){
    var sign = number ? ( (number <0) ? -1 : 1) : 0;
    return sign;
};

现在,可以直接使用Math.sign。


1
你促使我更新了我的问题。自那以后已经过去了8年。我还将我的jsfiddle更新到了es6和window.performance api。但是我更喜欢Mozilla的版本作为polyfill,因为它匹配了Math.sign的类型强制转换。性能现在不是太重要了。 - disfated

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