36

丸め関数を探しているわけではないことに注意することが重要です。任意の数値の簡略化された 10 進数表現で小数点以下の桁数を返す関数を探しています。つまり、次のようになります。

decimalPlaces(5555.0);     //=> 0
decimalPlaces(5555);       //=> 0
decimalPlaces(555.5);      //=> 1
decimalPlaces(555.50);     //=> 1
decimalPlaces(0.0000005);  //=> 7
decimalPlaces(5e-7);       //=> 7
decimalPlaces(0.00000055); //=> 8
decimalPlaces(5.5e-7);     //=> 8

私の最初の本能は、文字列表現を使用することでした: split on '.'、次に on 'e-'、そして次のように計算します (例は冗長です):

function decimalPlaces(number) {
  var parts = number.toString().split('.', 2),
    integerPart = parts[0],
    decimalPart = parts[1],
    exponentPart;

  if (integerPart.charAt(0) === '-') {
    integerPart = integerPart.substring(1);
  }

  if (decimalPart !== undefined) {
    parts = decimalPart.split('e-', 2);
    decimalPart = parts[0];
  }
  else {
    parts = integerPart.split('e-', 2);
    integerPart = parts[0];
  }
  exponentPart = parts[1];

  if (exponentPart !== undefined) {
    return integerPart.length +
      (decimalPart !== undefined ? decimalPart.length : 0) - 1 +
      parseInt(exponentPart);
  }
  else {
    return decimalPart !== undefined ? decimalPart.length : 0;
  }
}

上記の例では、この関数は機能します。ただし、考えられるすべての値をテストするまで満足できないので、バストアウトしましたNumber.MIN_VALUE

Number.MIN_VALUE;                      //=> 5e-324
decimalPlaces(Number.MIN_VALUE);       //=> 324

Number.MIN_VALUE * 100;                //=> 4.94e-322
decimalPlaces(Number.MIN_VALUE * 100); //=> 324

5e-324 * 10これは最初は合理的に見えましたが、二重に考えてみると、そうあるべきだと気づきました5e-323。そして、それは私を襲った:私は非常に小さな数の量子化の影響を扱っています。格納前に数値が量子化されるだけではありません。さらに、2 進数で格納された一部の数値は 10 進数表現が不当に長いため、10 進数表現が切り捨てられます。これは、文字列表現を使用して真の小数精度を取得できないことを意味するため、私にとっては残念です。

だから私はあなたに来ました、StackOverflowコミュニティ。数値の真の小数点以下の精度を得る信頼できる方法を知っている人はいますか?

この関数の目的は、float を簡略化された分数に変換する別の関数で使用することです (つまり、互いに素な整数分子と非ゼロの自然分母を返します)。この外側の関数で唯一欠けている部分は、float の小数点以下の桁数を決定する信頼できる方法であるため、適切な 10 の累乗を掛けることができます。

4

10 に答える 10

23

歴史的なメモ: 以下のコメント スレッドは、最初と 2 番目の実装を参照している可能性があります。バグのある実装でリードすると混乱が生じたため、2017 年 9 月に順序を入れ替えました。

"0.1e-100"101にマップするものが必要な場合は、次のようなものを試すことができます

function decimalPlaces(n) {
  // Make sure it is a number and use the builtin number -> string.
  var s = "" + (+n);
  // Pull out the fraction and the exponent.
  var match = /(?:\.(\d+))?(?:[eE]([+\-]?\d+))?$/.exec(s);
  // NaN or Infinity or integer.
  // We arbitrarily decide that Infinity is integral.
  if (!match) { return 0; }
  // Count the number of digits in the fraction and subtract the
  // exponent to simulate moving the decimal point left by exponent places.
  // 1.234e+2 has 1 fraction digit and '234'.length -  2 == 1
  // 1.234e-2 has 5 fraction digit and '234'.length - -2 == 5
  return Math.max(
      0,  // lower limit.
      (match[1] == '0' ? 0 : (match[1] || '').length)  // fraction length
      - (match[2] || 0));  // exponent
}

仕様によると、組み込みの数値 - >文字列変換に基づくソリューションは、指数を超えて 21 桁までしか正確ではありません。

9.8.1 Number 型に適用される ToString

  1. それ以外の場合、n、k、および s は、k ≥ 1、10k−1 ≤ s < 10k、s × 10n−k の Number 値が m、k が可能な限り小さいような整数とします。k は s の 10 進数表現の桁数であること、s は 10 で割り切れないこと、および s の最下位桁がこれらの基準によって一意に決定されるとは限らないことに注意してください。
  2. k ≤ n ≤ 21 の場合、s の 10 進数表現の k 桁 (順番に先行ゼロなし) と、それに続く n−k 回の文字 '0' で構成される文字列を返します。
  3. 0 < n ≤ 21 の場合、s の 10 進数表現の最上位 n 桁、小数点 '.'、s の 10 進数表現の残りの k-n 桁で構成される文字列を返します。
  4. −6 < n ≤ 0 の場合、文字「0」、小数点「.」、文字「0」の −n 回の出現、その後の 10 進数表現の k 桁で構成される文字列を返します。秒。

歴史的なメモ: 以下の実装には問題があります。コメント スレッドのコンテキストとしてここに残します。

の定義に基づいてNumber.prototype.toFixed、次のように動作するように見えますが、double 値の IEEE-754 表現により、特定の数値が誤った結果を生成します。たとえば、decimalPlaces(0.123)を返し20ます。

function decimalPlaces(number) {
  // toFixed produces a fixed representation accurate to 20 decimal places
  // without an exponent.
  // The ^-?\d*\. strips off any sign, integer portion, and decimal point
  // leaving only the decimal fraction.
  // The 0+$ strips off any trailing zeroes.
  return ((+number).toFixed(20)).replace(/^-?\d*\.?|0+$/g, '').length;
}

// The OP's examples:
console.log(decimalPlaces(5555.0));  // 0
console.log(decimalPlaces(5555));  // 0
console.log(decimalPlaces(555.5));  // 1
console.log(decimalPlaces(555.50));  // 1
console.log(decimalPlaces(0.0000005));  // 7
console.log(decimalPlaces(5e-7));  // 7
console.log(decimalPlaces(0.00000055));  // 8
console.log(decimalPlaces(5e-8));  // 8
console.log(decimalPlaces(0.123));  // 20 (!)

于 2012-03-02T20:13:38.223 に答える
11

私は、浮動小数点数に 10 のべき乗を掛けると整数になるという事実に基づいた解決策を使用します。

たとえば、3.14 * 10 ^ 2 を掛けると、314 (整数) が得られます。指数は、浮動小数点数が持つ小数の数を表します。

ですから、浮動小数点数を 10 のべき乗で徐々に乗算すると、最終的に解に到達すると考えました。

let decimalPlaces = function () {
   function isInt(n) {
      return typeof n === 'number' && 
             parseFloat(n) == parseInt(n, 10) && !isNaN(n);
   }
   return function (n) {
      const a = Math.abs(n);
      let c = a, count = 1;
      while (!isInt(c) && isFinite(c)) {
         c = a * Math.pow(10, count++);
      }
      return count - 1;
   };
}();

for (const x of [
  0.0028, 0.0029, 0.0408,
  0, 1.0, 1.00, 0.123, 1e-3,
  3.14, 2.e-3, 2.e-14, -3.14e-21,
  5555.0, 5555, 555.5, 555.50, 0.0000005, 5e-7, 0.00000055, 5e-8,
  0.000006, 0.0000007,
  0.123, 0.121, 0.1215
]) console.log(x, '->', decimalPlaces(x));

于 2013-12-02T17:56:46.150 に答える
3

これは よりも小さい数に対して機能しますe-17:

function decimalPlaces(n){
    var a;
    return (a=(n.toString().charAt(0)=='-'?n-1:n+1).toString().replace(/^-?[0-9]+\.?([0-9]+)$/,'$1').length)>=1?a:0;
}
于 2012-03-02T20:08:03.870 に答える
1

格納前に数値が量子化されるだけではありません。さらに、2 進数で格納された一部の数値は 10 進数表現が不当に長いため、10 進数表現が切り捨てられます。

JavaScript は、IEEE-754倍精度 (64 ビット) 形式を使用して数値を表します。私が理解しているように、これにより 53 ビットの精度、つまり 15 から 16 桁の 10 進数が得られます。

したがって、桁数が多い数値については、近似値を取得するだけです。このスレッドで言及されているものを含め、より正確に大きな数を処理するためのライブラリがいくつかあります。

于 2012-03-02T20:29:44.990 に答える