最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - What is the fastest way to count the number of significant digits of a number? - Stack Overflow

programmeradmin3浏览0评论

What is the fastest way to count the number of significant digits of a number?

I have the following function, which works, but is quite slow due to string operations.

/**
 * Count the number of significant digits of a number.
 *
 * For example:
 *   2.34 returns 3
 *   0.0034 returns 2
 *   120.5e+3 returns 4
 *
 * @param {Number} value
 * @return {Number} The number of significant digits
 */
function digits (value) {
  return value
      .toExponential()
      .replace(/e[\+\-0-9]*$/, '')  // remove exponential notation
      .replace( /^0\.?0*|\./, '')    // remove decimal point and leading zeros
      .length
};

Is there a faster way?

Update: here a list of assertions to test correct functioning:

assert.equal(digits(0), 0);
assert.equal(digits(2), 1);
assert.equal(digits(1234), 4);
assert.equal(digits(2.34), 3);
assert.equal(digits(3000), 1);
assert.equal(digits(0.0034), 2);
assert.equal(digits(120.5e50), 4);
assert.equal(digits(1120.5e+50), 5);
assert.equal(digits(120.52e-50), 5);
assert.equal(digits(Math.PI), 16);

My own method failed for digits(0), I fixed that by adding a ? to the second regexp.

What is the fastest way to count the number of significant digits of a number?

I have the following function, which works, but is quite slow due to string operations.

/**
 * Count the number of significant digits of a number.
 *
 * For example:
 *   2.34 returns 3
 *   0.0034 returns 2
 *   120.5e+3 returns 4
 *
 * @param {Number} value
 * @return {Number} The number of significant digits
 */
function digits (value) {
  return value
      .toExponential()
      .replace(/e[\+\-0-9]*$/, '')  // remove exponential notation
      .replace( /^0\.?0*|\./, '')    // remove decimal point and leading zeros
      .length
};

Is there a faster way?

Update: here a list of assertions to test correct functioning:

assert.equal(digits(0), 0);
assert.equal(digits(2), 1);
assert.equal(digits(1234), 4);
assert.equal(digits(2.34), 3);
assert.equal(digits(3000), 1);
assert.equal(digits(0.0034), 2);
assert.equal(digits(120.5e50), 4);
assert.equal(digits(1120.5e+50), 5);
assert.equal(digits(120.52e-50), 5);
assert.equal(digits(Math.PI), 16);

My own method failed for digits(0), I fixed that by adding a ? to the second regexp.

Share Improve this question edited Apr 5, 2014 at 20:20 Jos de Jong asked Apr 5, 2014 at 18:07 Jos de JongJos de Jong 6,8193 gold badges42 silver badges62 bronze badges 15
  • 2 What you're trying to do is something that is fundamentally challenged by the fact that floating-point numbers are represented as binary floating point. – Pointy Commented Apr 5, 2014 at 18:15
  • Perhaps look at ostermiller.org/calc/SignificantFigures.js – bdrx Commented Apr 5, 2014 at 18:21
  • 1 No answer will ever be sufficient unless some bench testing is being used. How do you define fastest? Any one can bring any arbitrary solution and we all can argue all night that it's faster/slower than yours. – Mohammed Joraid Commented Apr 5, 2014 at 18:26
  • 1 Are you working from some sort of paper that describes the technique you're trying to implement? The more I think about it, the more I think that what you're trying to do is inherently extremely difficult due to the combination of the nature of floating point math in general, and the difficulties encountered when interpreting the behavior of binary floating point math after decimal conversion. Even if you do the binary stuff I suggested, things are going to be weird: the value 0.3 is a repeating fraction in binary for example. – Pointy Commented Apr 5, 2014 at 23:23
  • 1 Here is a simple jsbin that explores the issue. Try some simple values in the input box; 18 is interesting. Note that even after some simple operations, things get weird. Sometimes the decimal representation will be "clean", but the binary is a repeating fraction. Sometimes the decimal representation is imprecise. Floating point math is just bizarre and really hard to handle analytically. – Pointy Commented Apr 6, 2014 at 16:31
 |  Show 10 more comments

6 Answers 6

Reset to default 9

Here's a more mathematical way of doing the same operation (which appears to be significantly faster)

JSPerf comparing the three implementations

Accurate for integer n < +-(2^53) per http://ecma262-5.com/ELS5_HTML.htm#Section_8.5
Floats are converted to a string and then coerced to an int (by removing the decimal so similar rules apply)

var log10 = Math.log(10);
function getSignificantDigitCount(n) {
    n = Math.abs(String(n).replace(".", "")); //remove decimal and make positive
    if (n == 0) return 0;
    while (n != 0 && n % 10 == 0) n /= 10; //kill the 0s at the end of n

    return Math.floor(Math.log(n) / log10) + 1; //get number of digits
}

Slight improvement of regular expression

function digits (value) {
  return value
      .toExponential()
      .replace(/^([0-9]+)\.?([0-9]+)?e[\+\-0-9]*$/g, "$1$2")
      .length
};

And yet another approach, that uses string operations and handles some special cases for better performance:

function digits(value) {
    if (value === 0) {
        return 0;
    }
    //create absolute value and
    var t1 = ("" + Math.abs(value));
    //remove decimal point
    var t2 = t1.replace(".","");

    //if number is represented by scientific notation,
    //the places before "e" (minus "-" and ".") are the
    //significant digits. So here we can just return the index
    //"-234.3e+50" -> "2343e+50" -> indexOf("e") === 4
    var i = t2.indexOf("e");
    if (i > -1) {
        return i;
    } 

    //if the original number had a decimal point,
    //trailing zeros are already removed, since irrelevant
    //0.001230000.toString() -> "0.00123" -> "000123"
    if (t2.length < t1.length) {
        // -> remove only leading zeros
        return t2.replace(/^0+/,'').length;
    }

    //if number did not contain decimal point,
    //leading zeros are already removed
    //000123000.toString() -> "123000"
    // -> remove only trailing zeros
    return t2.replace(/0+$/,'').length;
}

You can directly examine the bytes of a floating-point value by using typed arrays. The advantages of doing this are that it's fast, and it doesn't require any math to be done. You can look directly at the bits of the mantissa.

You can start with this:

var n = yourFloatingPointValue; 

var f64 = new Float64Array(1);
var dv = new DataView(f64.buffer);

dv.setFloat64(0, n, false); // false -> big-endian

var bytes = [];
for (var i = 0; i < 8; i++)
  bytes.push(dv.getUint8(i));

Now the bytes array contains integers representing the 8-bit values of the floating point value as it looks in memory. The first byte contains the sign bit in the top bit position, and the first 7 bits of the exponent in the rest. The second byte contains the 5 least-significant bits of the exponent and the first three bits of the mantissa. The rest of the bytes are all mantissa.

Regular string checking. A slight of improvement though.

function digits(value) {
  value = "" + value;
  var res = 0;
  for (var i = 0, len = value.length; i < len; i++){
    if (value[i]==="e")break;
    if (+value[i]>=0)
      res++;
}
  return res;
};

jsperf Benchmark testing result as compared to the OP's and other answers code.


Update

function digits(value) {

  console.log(value);
  value = "" + (+value);

  var res = 0;
  for (var i = 0, len = value.length; i < len; i++) {
  
  if (value[i] === "e") 
    {
    break;
    }
    
    if (+value[i] >= 0)
    {
     res++;
    }     
  }
  console.log(value);
  return res;
}

function check(val1, val2) {

  console.log( val1+"==="+val2 +" = "+ (val1 === val2));
  return val1 === val2;
}


check(digits(0), 1);
check(digits(2), 1);
check(digits(1234), 4);
check(digits("0012003400"), 8);
check(digits("0022.002200"), 6);
check(digits(2.34), 3);
check(digits(3000), 4);
check(digits(0.0034), 2);
check(digits(12003), 5);
check(digits(1.23e+50), 3);
check(digits("1.23e+50"), 3);
check(digits(120.5e51), 4);
check(digits(1120.5e+52), 5);
check(digits(120.52e-53), 5);
check(digits(Math.PI), 16);

There is a faster and indirect way to do it, which is converting it to a string and finding the length of it.

a = 2.303
sig_fig = len(str(a))-len(str(int(a)))-1

The extra -1 is for the "."

发布评论

评论列表(0)

  1. 暂无评论