JS の toFixed() メソッドの丸め精度の問題の詳細な説明

JS の toFixed() メソッドの丸め精度の問題の詳細な説明

落とし穴

最近、仕事で商品の割引価格を計算すると、いつも1セントの価格差が出てしまいます。お金が絡む問題はより敏感です。調査した結果、最終的にJSのネイティブtoFixedメソッドの問題であることが判明しました。

まあ、これはどういうルールですか? 。 。 (⊙お⊙)

充填方法

問題を急いで調べないでください。問題を見つけたので、まずはバグを修正してください。ネイティブ メソッドが機能しない場合は、自分で書いてください。数分しかかかりません、ハハハ!

/**
 * 小数点以下の桁数を保持し、自動的にゼロを埋め、切り上げます* @param num: 値* @param digit: 小数点以下の桁数* @returns 文字列
 */ 
関数 myFixed(数値, 桁数) {
  if(Object.is(parseFloat(num), NaN)) {
    return console.log(`受信値: ${num} は数値ではありません`);
  }
  num = parseFloat(num);
  (Math.round((num + Number.EPSILON) * Math.pow(10, digit)) / Math.pow(10, digit)).toFixed(digit) を返します。
}

何の穴ですか?

さて、バグが修正されたので、toFixed の秘密を探ってみましょう。

えーっと…まずは、えーっと…百度で、百度プログラミングエンジニアを対象に検索してみましょう。案の定、たくさんの結果が出てきます。典型的な問題のようです。

少し理解してみると、toFixed メソッドで使用される丸めは、私たちが理解している文字通りの丸めではないことがわかりました。 toFixed メソッドは、「偶数への丸め」と呼ばれる奇妙な方法を使用します。これは銀行家のアルゴリズムとしても知られています。これはどういう意味ですか?

完全な文: 「最も近い 5 に切り上げます。5 の後の数が 0 でない場合は、1 を加えます。5 の後の数が 0 の場合は、それが奇数か偶数かを検討します。5 の前の数が偶数の場合は、それを破棄します。5 の前の数が奇数の場合は、1 を加えます。」

一般的な意味は、捨てられた数字の値が ≤4 の場合は捨て、≥6 の場合は加算し、=5 の場合は 5 の後の数字に応じて決定し、5 の後に 0 以外の数字がある場合は 5 を 1 に丸め、5 の後に有効な数字がない場合は、2 つのケースに分けられます。5 の前の数字が偶数の場合は 5 を丸めて加算せず、5 の前の数字が奇数の場合は 5 を 1 に丸めます。

このルールに従って、ブラウザでさらにいくつかのテストを実行しましたが、まだ適切ではないと感じました。

// 5 の前の数字は偶数なので、切り捨てられませんか?
console.log(1.00000065.toFixed(7)); // 1.0000007 エラー console.log(1.000000065.toFixed(8)); // 1.00000007 エラー // 5 の前の数字は奇数なので、1 増加しませんか?
console.log(1.00000015.toFixed(7)); // 1.0000001 エラー console.log(1.000000015.toFixed(8)); // 1.00000001 エラー

これはなぜでしょうか?本当に混乱します。 。 。 (︶︿︶)

さらに調査を進めた結果、ようやく結果が得られました。では、このメソッドの ECMAScript 仕様の定義を見てみましょう。仕様に戻るのが最も信頼できる方法である場合もあります。

上の図はtoFixedメソッド全体の定義ですが、翻訳版です。多少の違いはありますが、大きな違いはありません。上のリンクをクリックして元のテキストを表示することもできます。図の赤いボックス部分に主に焦点を当て、数式を使用して破棄された値を計算します。

次の 2 つの例を取り上げ、テストしてみましょう。

console.log(1.0000005.toFixed(6)); // 1.000001 正解 console.log(1.00000005.toFixed(7)); // 1.0000000 間違い

まず、赤いボックスの条件によれば、x<10^21、1.0000005、1.00000005 はどちらも 10^21 未満なので、式 n / 10^ - x を直接使用できます。

まず、x=1.0000005 を式に代入して状況を確認しましょう。

// n1と仮定
var n1 = 1000000;
var x = 1.0000005;
var f = 6;
console.log((n1 / Math.pow(10, f) - x)); // -5.00000000069889e-7

// n2と仮定
var n2 = 1000001;
var x = 1.0000005;
var f = 6;
console.log((n2 / Math.pow(10, f) - x)); // 4.9999999998478444e-7

結果から、n1=1000001 の場合、結果は 0 に最も近い値であることがわかります。

console.log(1.0000005.toFixed(6)); // 1.000001が正しい

式に x=1.00000005 を代入してもう一度試してみましょう。

// n1と仮定
var n1 = 10000000;
var x = 1.00000005;
var f = 7;
console.log((n1 / Math.pow(10,f) - x)); // -4.99999999918171056e-8

// n2と仮定
var n2 = 10000001;
var x = 1.00000005;
var f = 7;
console.log((n2 / Math.pow(10,f) - x)); // 5.000000014021566e-8

結果から、n2=10000001 の場合、結果は 0 に最も近い値であることがわかります。

console.log(1.00000005.toFixed(7)); // 1.0000000 エラー

ああ...ここに来て、自分が大きな穴を掘ってしまったことに今気づきました。なぜこんなにたくさんのゼロを使わなければならないのでしょうか。数えると目が回ります。 。 。

一般的に、上記の例は、仕様で定義されている式を使用して結果を計算する方法を説明するだけです。仕様を理解できる場合は、直接代入しても問題ありません。

要約する

JS の toFixed() メソッドの丸め精度問題についての記事はこれで終わりです。JS toFixed() の丸め精度問題についての詳細は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JavaScript 正規表現における括弧の落とし穴の一覧
  • JS で tofixed と round を使用してデータの丸めを処理する場合の違い
  • JS はデータの丸め処理を行います (tofixed と round の違いの詳細な説明)
  • JavaScript で toFixed() 丸めを使用する方法の詳細な説明
  • js 正規表現の簡単な検証方法
  • jsは正規表現を使用して年、月、日の例をフィルタリングします
  • JavaScript 正規表現の説明
  • jmeter でインターフェース関連付けを実装する 2 つの方法 (正規表現抽出器と json 抽出器)
  • nest.js で正規表現を使用して検証を正しく設定する方法
  • jJavaScript における toFixed() と正規表現の落とし穴

<<:  ウェブページの表の分割線を削除する方法

>>:  MySQL全文インデックスの原理と欠点

推薦する

ウェブページに埋め込まれた Flash と IE、FF、Maxthon の互換性の問題

いろいろ苦労した後、インターネットで検索したり、以前の会社のプロジェクトを探したり、他の人のプロジェ...

Vue2/vue3 ルーティング権限管理方法の例

1. Vueルーティングの権限制御には一般的に2つの方法がありますa. ルーティングメタ情報(メタ)...

CSS クロスブラウザ スタイルのバグのデバッグについて

まず最初に、適切なブラウザを選択します。私が Chrome を選択したのは、その強力なデバッグ ツー...

JavaScriptの擬似配列と配列の使い方と違い

擬似配列と配列JavaScript では、5 つのプリミティブ データ型を除き、関数を含め、その他す...

CMDコマンドを使用してMySqlデータベースを操作する方法の詳細な説明

まず、mysqlサービスを開始および停止します ネットストップmysql ネットスタートMySQL ...

Vue はシェイク機能を実装します (ios13.3 以降と互換性があります)

最近、shake.jsを使用して、shakeに似た機能を作成しました。ただし、shake機能はios...

JavaScript ループトラバーサルの 24 種類のメソッドをすべてご存知ですか?

目次序文1. 配列走査法1. 各() 2. マップ() 3. 〜のために4. フィルター() 5. ...

uniappのグローバル変数実装の詳細な説明

序文この記事では、uniapp グローバル変数の実装方法をいくつかまとめています。詳細な知識は、uV...

プロジェクトに必須の 8 つの JavaScript コード スニペット

目次1. ファイル拡張子を取得する2. コンテンツをクリップボードにコピーする3. スリープ時間は何...

CentOS 7.2 は uniapp プロジェクトを展開するための nginx Web サーバーを構築します

Pantherは新人としてスタートし、今もまだ新人ですが、人々から学び、学んだことを時々皆さんと共有...

LinuxのCPU負荷とCPU使用率の詳細な説明

CPU 負荷と CPU 使用率これらは両方とも、ある程度、マシンの忙しさを反映できます。 CPU 使...

はじめに: HTML の基本的なタグと属性の簡単な紹介

HTML はタグと属性で構成されており、これらを組み合わせてブラウザにページの表示方法を指示します。...

テーブルを作成するための MySQL SQL ステートメントの詳細な概要

mysql テーブル作成 SQL ステートメントMySQL テーブルを作成するための一般的な SQL...

Vueナンバープレート入力コンポーネントの使い方の詳しい説明

参考までに、シンプルなナンバープレート入力コンポーネント(vue)です。具体的な内容は次のとおりです...

MySQL サービスを完全に削除する方法 (レジストリをクリーンアップする)

序文あるプロジェクトの実行可能ファイルをインストールすると、MySQL 自体をインストールできるよう...