题 在JavaScript中验证十进制数 - IsNumeric()


在JavaScript中验证十进制数的最简洁,最有效的方法是什么?

奖励积分:

  1. 明晰。解决方案应该干净简单。
  2. 跨平台。

测试用例:

01. IsNumeric('-1')      => true
02. IsNumeric('-1.5')    => true
03. IsNumeric('0')       => true
04. IsNumeric('0.42')    => true
05. IsNumeric('.42')     => true
06. IsNumeric('99,999')  => false
07. IsNumeric('0x89f')   => false
08. IsNumeric('#abcdef') => false
09. IsNumeric('1.2.3')   => false
10. IsNumeric('')        => false
11. IsNumeric('blah')    => false

2156


起源


只有一个注释99,999在法国是一个有效数字,它与英国/美国格式的99.999相同,所以如果你从一个输入形式读取一个字符串,那么99,999可能是真的。 - Re0sless
还可以看看 这篇文章和伟大的评论。 - powtac
十进制逗号是整个欧洲和俄罗斯的标准(英国除外) - Calmarius
jQuery 1.7已经推出了 jQuery.isNumeric 实用功能: api.jquery.com/jQuery.isNumeric - Ates Goral
jQuery.isNumeric 将失败OP的第七个测试案例(IsNumeric('0x89f') => *false*)。但是,我不确定我是否同意这个测试用例。 - Tim Lehner


答案:


@乔尔的回答 非常接近,但在以下情况下会失败:

// Whitespace strings:
IsNumeric(' ')    == true;
IsNumeric('\t\t') == true;
IsNumeric('\n\r') == true;

// Number literals:
IsNumeric(-1)  == false;
IsNumeric(0)   == false;
IsNumeric(1.1) == false;
IsNumeric(8e5) == false;

前段时间我不得不实施一个 IsNumeric 函数,找出变量是否包含数值, 不管它的类型,它可能是一个 String 包含数值(我还必须考虑指数表示法等),a Number 对象,几乎任何东西都可以传递给那个函数,我不能做任何类型的假设,照顾类型强制(例如。 +true == 1; 但 true 不应被视为 "numeric")。

我认为值得分享这一套 +30单元测试 做了很多功能实现,并分享通过我所有测试的那个:

function isNumeric(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
}

附:  isNaN & isFinite的 由于强制转换为数字而导致混乱行为。在ES6中, Number.isNaN & Number.isFinite 会解决这些问题。使用它们时请记住这一点。


更新 : 以下是jQuery现在如何做到(2.2稳定)

isNumeric: function(obj) {
    var realStringObj = obj && obj.toString();
    return !jQuery.isArray(obj) && (realStringObj - parseFloat(realStringObj) + 1) >= 0;
}

更新 : Angular 4.3

export function isNumeric(value: any): boolean {
    return !isNaN(value - parseFloat(value));
}

2760



这个答案是对的。 stackoverflow.com/questions/18082/... 乔尔的回答是错误的。一个错误的答案怎么会比正确的答案收集更多的票? - Arvin
这与我们使用十进制逗号的其他语言环境失败,但添加`n = n.replace(/,/,“。”);'在返回之前修复它。 - Zoltan Lengyel
@RobG,这种行为是故意的, 2e308 > Number.MAX_VALUE 以来 2e308 == Infinity。如果你想要一个返回的函数 true 对于正负无穷大值,请检查功能 第2号 在里面 测试套件。干杯。 - CMS
顺便说一下,现在正在使用单元测试 jQuery项目 - CMS
jQuery现在也是 运用 这个实现。 - RichardTowers


Arrrgh!不要听正则表达式的答案。 RegEx对此非常苛刻,我不只是说性能。用你的正则表达式来制作微妙的,不可能发现错误是如此容易。

如果你不能使用 isNaN(),这应该更好:

function IsNumeric(input)
{
    return (input - 0) == input && (''+input).trim().length > 0;
}

以下是它的工作原理:

(input - 0) 表达式强制JavaScript对输入值进行类型强制;必须首先将其解释为减法运算的数字。如果转换为数字失败,则表达式将导致 NaN。这个 数字 然后将结果与传入的原始值进行比较。由于左侧现在是数字,因此再次使用类型强制。既然来自双方的输入都是从相同的原始值强制转换为相同的类型,那么您会认为它们应该始终相同(始终为真)。但是,有一个特殊的规则说 NaN 绝不等于 NaN,因此无法转换为数字的值(并且只有无法转换为数字的值)将导致false。

检查长度是针对涉及空字符串的特殊情况。另请注意,它会降低到您的0x89f测试,但这是因为在许多环境中,这是一种定义数字文字的好方法。如果要捕获该特定方案,可以添加其他检查。更好的是,如果这是你不使用的原因 isNaN() 然后只是包装你自己的功能 isNaN() 也可以做额外的检查。

综上所述, 如果您想知道某个值是否可以转换为数字,请尝试将其转换为数字。


我回去做了一些研究 为什么 一个空格字符串没有预期的输出,我想我现在得到它:一个空字符串被强制转换 0 而不是 NaN。只需在长度检查之前修剪字符串就可以处理这种情况。

运行单元测试新代码,它只在无限和布尔文字上失败,唯一应该是问题的是你生成代码(真的,谁会输入文字并检查它是否是数字?你应该 知道),这将是一些奇怪的代码生成。

但是,再一次, 使用它的唯一原因是,如果由于某种原因你必须避免使用isNaN()。


315



更好的是,如果你只需要考虑0x89f特殊情况就是在isNaN()周围包装一个IsNumeric()函数,然后只有当isNaN()返回false时才进行特殊检查。 - Joel Coehoorn
这在空白字符串上失败,例如 IsNumeric(' '), IsNumeric('\n\t')等全部归还 true - Crescent Fresh
它也将失败 Number 文字 IsNumeric(5) == false; 检查我发布的单元测试集,这个函数是数字 16 在测试套件上。 stackoverflow.com/questions/18082/... - CMS
我不敢相信在没有使用正则表达式警告之后没有人指出使用正则表达式(替换)...当然,空格替换比数字解析更简单,但它仍然绝对“icky”。 - Patrick M
@Oriol这是一个很大的问题......在那个日期之后没有发布任何安全修复程序,远离XP应该是一个优先事项。 - Joel Coehoorn


这种方式似乎运作良好:

function IsNumeric(input){
    var RE = /^-{0,1}\d*\.{0,1}\d+$/;
    return (RE.test(input));
}

并测试它:

// alert(TestIsNumeric());

function TestIsNumeric(){
    var results = ''
    results += (IsNumeric('-1')?"Pass":"Fail") + ": IsNumeric('-1') => true\n";
    results += (IsNumeric('-1.5')?"Pass":"Fail") + ": IsNumeric('-1.5') => true\n";
    results += (IsNumeric('0')?"Pass":"Fail") + ": IsNumeric('0') => true\n";
    results += (IsNumeric('0.42')?"Pass":"Fail") + ": IsNumeric('0.42') => true\n";
    results += (IsNumeric('.42')?"Pass":"Fail") + ": IsNumeric('.42') => true\n";
    results += (!IsNumeric('99,999')?"Pass":"Fail") + ": IsNumeric('99,999') => false\n";
    results += (!IsNumeric('0x89f')?"Pass":"Fail") + ": IsNumeric('0x89f') => false\n";
    results += (!IsNumeric('#abcdef')?"Pass":"Fail") + ": IsNumeric('#abcdef') => false\n";
    results += (!IsNumeric('1.2.3')?"Pass":"Fail") + ": IsNumeric('1.2.3') => false\n";
    results += (!IsNumeric('')?"Pass":"Fail") + ": IsNumeric('') => false\n";
    results += (!IsNumeric('blah')?"Pass":"Fail") + ": IsNumeric('blah') => false\n";

    return results;
}

我借用了那个正则表达式 http://www.codetoad.com/javascript/isnumeric.asp。说明:

/^ match beginning of string
-{0,1} optional negative sign
\d* optional digits
\.{0,1} optional decimal point
\d+ at least one digit
$/ match end of string

58



//还应该添加到你的测试结果+ =(!IsNumeric(' - ')?“Pass”:“Fail”)+“:IsNumeric(' - ')=> false \ n”;结果+ =(!IsNumeric('01')?“Pass”:“Fail”)+“:IsNumeric('01')=> false \ n”;结果+ =(!IsNumeric(' - 01')?“Pass”:“Fail”)+“:IsNumeric(' - 01')=> false \ n”;结果+ =(!IsNumeric('000')?“Pass”:“Fail”)+“:IsNumeric('000')=> false \ n”; - Dan
这是做什么的?/ ^ - {0,1} \ d * \。{0,1} \ d + $ / - call-me
可以将“{0,1}”替换为“?”,因此您的正则表达式将如下所示:/^-?\d*\.?\d+$/? - Cloud


雅虎UI 用这个:

isNumber: function(o) {
    return typeof o === 'number' && isFinite(o);
}

45



这更多地检查变量类型而不是数字的内容。它也会因创建的数字而失败 new Number(1)。 - alex
正如亚历克斯所说,这实际上并没有回答所提出的问题,因为如果o =“1001”,这将失败。 - Case


function IsNumeric(num) {
     return (num >=0 || num < 0);
}

这适用于0x23类型数字。


44



IsNumeric(''), IsNumeric(' '), IsNumeric(true), IsNumeric(false), IsNumeric(null) 返回 true 代替 false。 - Oriol
我改进了这个: stackoverflow.com/a/20712631/1985601 - daniel1426


接受的答案没有通过你的测试#7,我想这是因为你改变了主意。所以这是对已接受的答案的回应,我遇到了问题。

在某些项目中,我需要验证一些数据并尽可能确定它是一个可以在数学运算中使用的javascript数值。

jQuery和其他一些javascript库已经包含了这样一个函数,通常称为 isNumeric。还有一个 在stackoverflow上发布 已被广泛接受作为答案,与上述图书馆正在使用的相同的一般例程。

function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

首先,如果参数是长度为1的数组,则上面的代码将返回true,并且该单个元素是上述逻辑认为是数字的类型。在我看来,如果它是一个数组,那么它不是数字。

为了缓解这个问题,我添加了一个检查来从逻辑中对数组进行折扣

function isNumber(n) {
  return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n);
}

当然,你也可以使用 Array.isArray,jquery $.isArray 或原型 Object.isArray 代替 Object.prototype.toString.call(n) !== '[object Array]'

我的第二个问题是负十六进制整数文字字符串(“-0xA” - > -10)未计入数字。但是,正十六进制整数文字字符串(“0xA” - > 10)被视为数字。 我需要两个都是有效的数字。

然后我修改了逻辑以考虑到这一点。

function isNumber(n) {
  return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}

如果你担心每次调用函数时都会创建正则表达式,那么你可以在一个闭包中重写它,像这样的东西

var isNumber = (function () {
  var rx = /^-/;

  return function (n) {
      return Object.prototype.toString.call(n) !== '[object Array]' &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(rx, ''));
  };
}());

然后我接受了CMS +30个测试用例 并克隆了 在jsfiddle上测试 添加了我的额外测试用例和上述解决方案。

它可能无法取代广泛接受/使用过的答案,但如果这更像是您希望的isNumeric函数的结果,那么希望这会有所帮助。

编辑: 正如所指出的那样 BERGI,还有其他可能被视为数字的对象,白名单比黑名单更好。考虑到这一点,我会添加标准。

我希望我的isNumeric函数只考虑数字或字符串

考虑到这一点,最好使用它

function isNumber(n) {
  return (Object.prototype.toString.call(n) === '[object Number]' || Object.prototype.toString.call(n) === '[object String]') &&!isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
}

测试解决方案

var testHelper = function() {

  var testSuite = function() {
    test("Integer Literals", function() {
      ok(isNumber("-10"), "Negative integer string");
      ok(isNumber("0"), "Zero string");
      ok(isNumber("5"), "Positive integer string");
      ok(isNumber(-16), "Negative integer number");
      ok(isNumber(0), "Zero integer number");
      ok(isNumber(32), "Positive integer number");
      ok(isNumber("040"), "Octal integer literal string");
      ok(isNumber(0144), "Octal integer literal");
      ok(isNumber("-040"), "Negative Octal integer literal string");
      ok(isNumber(-0144), "Negative Octal integer literal");
      ok(isNumber("0xFF"), "Hexadecimal integer literal string");
      ok(isNumber(0xFFF), "Hexadecimal integer literal");
      ok(isNumber("-0xFF"), "Negative Hexadecimal integer literal string");
      ok(isNumber(-0xFFF), "Negative Hexadecimal integer literal");
    });

    test("Foating-Point Literals", function() {
      ok(isNumber("-1.6"), "Negative floating point string");
      ok(isNumber("4.536"), "Positive floating point string");
      ok(isNumber(-2.6), "Negative floating point number");
      ok(isNumber(3.1415), "Positive floating point number");
      ok(isNumber(8e5), "Exponential notation");
      ok(isNumber("123e-2"), "Exponential notation string");
    });

    test("Non-Numeric values", function() {
      equals(isNumber(""), false, "Empty string");
      equals(isNumber("        "), false, "Whitespace characters string");
      equals(isNumber("\t\t"), false, "Tab characters string");
      equals(isNumber("abcdefghijklm1234567890"), false, "Alphanumeric character string");
      equals(isNumber("xabcdefx"), false, "Non-numeric character string");
      equals(isNumber(true), false, "Boolean true literal");
      equals(isNumber(false), false, "Boolean false literal");
      equals(isNumber("bcfed5.2"), false, "Number with preceding non-numeric characters");
      equals(isNumber("7.2acdgs"), false, "Number with trailling non-numeric characters");
      equals(isNumber(undefined), false, "Undefined value");
      equals(isNumber(null), false, "Null value");
      equals(isNumber(NaN), false, "NaN value");
      equals(isNumber(Infinity), false, "Infinity primitive");
      equals(isNumber(Number.POSITIVE_INFINITY), false, "Positive Infinity");
      equals(isNumber(Number.NEGATIVE_INFINITY), false, "Negative Infinity");
      equals(isNumber(new Date(2009, 1, 1)), false, "Date object");
      equals(isNumber(new Object()), false, "Empty object");
      equals(isNumber(function() {}), false, "Instance of a function");
      equals(isNumber([]), false, "Empty Array");
      equals(isNumber(["-10"]), false, "Array Negative integer string");
      equals(isNumber(["0"]), false, "Array Zero string");
      equals(isNumber(["5"]), false, "Array Positive integer string");
      equals(isNumber([-16]), false, "Array Negative integer number");
      equals(isNumber([0]), false, "Array Zero integer number");
      equals(isNumber([32]), false, "Array Positive integer number");
      equals(isNumber(["040"]), false, "Array Octal integer literal string");
      equals(isNumber([0144]), false, "Array Octal integer literal");
      equals(isNumber(["-040"]), false, "Array Negative Octal integer literal string");
      equals(isNumber([-0144]), false, "Array Negative Octal integer literal");
      equals(isNumber(["0xFF"]), false, "Array Hexadecimal integer literal string");
      equals(isNumber([0xFFF]), false, "Array Hexadecimal integer literal");
      equals(isNumber(["-0xFF"]), false, "Array Negative Hexadecimal integer literal string");
      equals(isNumber([-0xFFF]), false, "Array Negative Hexadecimal integer literal");
      equals(isNumber([1, 2]), false, "Array with more than 1 Positive interger number");
      equals(isNumber([-1, -2]), false, "Array with more than 1 Negative interger number");
    });
  }

  var functionsToTest = [

    function(n) {
      return !isNaN(parseFloat(n)) && isFinite(n);
    },

    function(n) {
      return !isNaN(n) && !isNaN(parseFloat(n));
    },

    function(n) {
      return !isNaN((n));
    },

    function(n) {
      return !isNaN(parseFloat(n));
    },

    function(n) {
      return typeof(n) != "boolean" && !isNaN(n);
    },

    function(n) {
      return parseFloat(n) === Number(n);
    },

    function(n) {
      return parseInt(n) === Number(n);
    },

    function(n) {
      return !isNaN(Number(String(n)));
    },

    function(n) {
      return !isNaN(+('' + n));
    },

    function(n) {
      return (+n) == n;
    },

    function(n) {
      return n && /^-?\d+(\.\d+)?$/.test(n + '');
    },

    function(n) {
      return isFinite(Number(String(n)));
    },

    function(n) {
      return isFinite(String(n));
    },

    function(n) {
      return !isNaN(n) && !isNaN(parseFloat(n)) && isFinite(n);
    },

    function(n) {
      return parseFloat(n) == n;
    },

    function(n) {
      return (n - 0) == n && n.length > 0;
    },

    function(n) {
      return typeof n === 'number' && isFinite(n);
    },

    function(n) {
      return !Array.isArray(n) && !isNaN(parseFloat(n)) && isFinite(n.toString().replace(/^-/, ''));
    }

  ];


  // Examines the functionsToTest array, extracts the return statement of each function
  // and fills the toTest select element.
  var fillToTestSelect = function() {
    for (var i = 0; i < functionsToTest.length; i++) {
      var f = functionsToTest[i].toString();
      var option = /[\s\S]*return ([\s\S]*);/.exec(f)[1];
      $("#toTest").append('<option value="' + i + '">' + (i + 1) + '. ' + option + '</option>');
    }
  }

  var performTest = function(functionNumber) {
    reset(); // Reset previous test
    $("#tests").html(""); //Clean test results
    isNumber = functionsToTest[functionNumber]; // Override the isNumber global function with the one to test
    testSuite(); // Run the test

    // Get test results
    var totalFail = 0;
    var totalPass = 0;
    $("b.fail").each(function() {
      totalFail += Number($(this).html());
    });
    $("b.pass").each(function() {
      totalPass += Number($(this).html());
    });
    $("#testresult").html(totalFail + " of " + (totalFail + totalPass) + " test failed.");

    $("#banner").attr("class", "").addClass(totalFail > 0 ? "fail" : "pass");
  }

  return {
    performTest: performTest,
    fillToTestSelect: fillToTestSelect,
    testSuite: testSuite
  };
}();


$(document).ready(function() {
  testHelper.fillToTestSelect();
  testHelper.performTest(0);

  $("#toTest").change(function() {
    testHelper.performTest($(this).children(":selected").val());
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="https://rawgit.com/Xotic750/testrunner-old/master/testrunner.js" type="text/javascript"></script>
<link href="https://rawgit.com/Xotic750/testrunner-old/master/testrunner.css" rel="stylesheet" type="text/css">
<h1>isNumber Test Cases</h1>

<h2 id="banner" class="pass"></h2>

<h2 id="userAgent">Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11</h2>

<div id="currentFunction"></div>

<div id="selectFunction">
  <label for="toTest" style="font-weight:bold; font-size:Large;">Select function to test:</label>
  <select id="toTest" name="toTest">
  </select>
</div>

<div id="testCode"></div>

<ol id="tests">
  <li class="pass">
    <strong>Integer Literals <b style="color:black;">(0, 10, 10)</b></strong>

    <ol style="display: none;">
      <li class="pass">Negative integer string</li>

      <li class="pass">Zero string</li>

      <li class="pass">Positive integer string</li>

      <li class="pass">Negative integer number</li>

      <li class="pass">Zero integer number</li>

      <li class="pass">Positive integer number</li>

      <li class="pass">Octal integer literal string</li>

      <li class="pass">Octal integer literal</li>

      <li class="pass">Hexadecimal integer literal string</li>

      <li class="pass">Hexadecimal integer literal</li>
    </ol>
  </li>

  <li class="pass">
    <strong>Foating-Point Literals <b style="color:black;">(0, 6, 6)</b></strong>

    <ol style="display: none;">
      <li class="pass">Negative floating point string</li>

      <li class="pass">Positive floating point string</li>

      <li class="pass">Negative floating point number</li>

      <li class="pass">Positive floating point number</li>

      <li class="pass">Exponential notation</li>

      <li class="pass">Exponential notation string</li>
    </ol>
  </li>

  <li class="pass">
    <strong>Non-Numeric values <b style="color:black;">(0, 18, 18)</b></strong>

    <ol style="display: none;">
      <li class="pass">Empty string: false</li>

      <li class="pass">Whitespace characters string: false</li>

      <li class="pass">Tab characters string: false</li>

      <li class="pass">Alphanumeric character string: false</li>

      <li class="pass">Non-numeric character string: false</li>

      <li class="pass">Boolean true literal: false</li>

      <li class="pass">Boolean false literal: false</li>

      <li class="pass">Number with preceding non-numeric characters: false</li>

      <li class="pass">Number with trailling non-numeric characters: false</li>

      <li class="pass">Undefined value: false</li>

      <li class="pass">Null value: false</li>

      <li class="pass">NaN value: false</li>

      <li class="pass">Infinity primitive: false</li>

      <li class="pass">Positive Infinity: false</li>

      <li class="pass">Negative Infinity: false</li>

      <li class="pass">Date object: false</li>

      <li class="pass">Empty object: false</li>

      <li class="pass">Instance of a function: false</li>
    </ol>
  </li>
</ol>

<div id="main">
  This page contains tests for a set of isNumber functions. To see them, take a look at the source.
</div>

<div>
  <p class="result">Tests completed in 0 milliseconds.
    <br>0 tests of 0 failed.</p>
</div>


37



这是我认为屏蔽功能最强的;最后一个。所接受的答案可能占所有案例的99.99%,但这一案件可能有100%的a)案件,开销很小。 - Samuel
你忘记了“99,999”Foating-Point文字。这是欧洲所有英国的有效号码 - Andrii Horda
它没有被遗忘,它不是我认为Javascript数字意义上的数字,OP也说 IsNumeric('99,999') => false - Xotic750


是的,内置的 isNaN(object) 将比任何正则表达式解析快得多,因为它是内置和编译的,而不是动态解释。

虽然结果与你想要的有些不同(尝试一下):

                                              // IS NUMERIC
document.write(!isNaN('-1') + "<br />");      // true
document.write(!isNaN('-1.5') + "<br />");    // true
document.write(!isNaN('0') + "<br />");       // true
document.write(!isNaN('0.42') + "<br />");    // true
document.write(!isNaN('.42') + "<br />");     // true
document.write(!isNaN('99,999') + "<br />");  // false
document.write(!isNaN('0x89f') + "<br />");   // true
document.write(!isNaN('#abcdef') + "<br />"); // false
document.write(!isNaN('1.2.3') + "<br />");   // false
document.write(!isNaN('') + "<br />");        // true
document.write(!isNaN('blah') + "<br />");    // false

30





从jQuery 1.7开始,你可以使用 jQuery.isNumeric()

$.isNumeric('-1');      // true
$.isNumeric('-1.5');    // true
$.isNumeric('0');       // true
$.isNumeric('0.42');    // true
$.isNumeric('.42');     // true
$.isNumeric('0x89f');   // true (valid hexa number)
$.isNumeric('99,999');  // false
$.isNumeric('#abcdef'); // false
$.isNumeric('1.2.3');   // false
$.isNumeric('');        // false
$.isNumeric('blah');    // false

请注意,不像你说的那样, 0x89f 是有效数字(hexa)


14



OP希望有效 十进制 数字,所以jQuery ISNUMERIC 不合适。对于非常大的数字,它也失败了。 - RobG


使用该功能 isNaN。我相信如果你测试 !isNaN(yourstringhere) 它适用于任何这些情况。


14



注意:!isNaN(null)== true,因为Number(null)== 0 - Jonathan Lonowski
if(!(x == null || isNaN(x)))alert(“isNumeric”); //但是这个解决方案接受0x40所以它仍然不是操作系统想要的。 - some
注意isNaN(“Infinity”)=== false,这可能也不是你想要的(但在现实生活中也不会发生)。 - Erik Hesselink


它可以在没有RegExp的情况下完成

function IsNumeric(data){
    return parseFloat(data)==data;
}

10



不应该=== - powtac
如果我们使用==,即使对于以字符串形式呈现的数字,它也会返回true。因此,对于“==”,“42”将被计为有效数字,并且在===的情况下将被视为无效 - Aquatic
这在“-0。”,“ - 0”,“。0”和“0”上返回true。 - Janus Troelsen