Help us understand the problem. What is going on with this article?

JavaScriptでの小数点の計算の誤差について

More than 3 years have passed since last update.

はじめに

4日目は、スマートテック・ベンチャーズの本山(@k_moto)が担当します。

今回はJavaScriptの小数点の計算について。
一見iOSと関係なさそうですが、
ブリッジすることや、ハイブリッドアプリはJSで書かれているので、
豆知識として覚えておいていただければと思います。

JavaScriptで小数点を計算するとズレる

JavaScriptでは IEEE 754(浮動小数点数演算標準) という規格に沿った実装がされています。
そのため、かなり微妙な誤差ではあるのですが、
計算結果にずれが出てしまいます。
詳しくは JavaScript IEEE754 などのキーワードでGoogle先生に聞いてみてください。

例えば、
以下のような場合、少数第14位が1多く
resultの値は 70.60000000000001 となってしまいます。

value1 = 80.7;
value2 = 10.1;

result = value1 - value2;

誤差を修正する方法ですが、
基本的には整数値にしてから計算することで解決します。
計算する値が小数の場合は問題なのであって、
整数にしてしまえば特に誤差はないようです。
整数値にする方法は以下のパターンです。

①10^Nで整数にする

単純に10の倍数を掛けて、位をずらしてしまえば良いという考えです。
上の例を修正する場合はこんな感じ。

value1 = 80.7;
value2 = 10.1;

result = ((value1 *10) - (value2 * 10)) / 10;

それぞれ10を掛けて、807-101 を行った後に10で割れば、
計算結果は正しく 70.6 になります。
ただし、このパターンではうまくいかない場合があります。
最終的に四捨五入するなど、
ある程度の数値の丸めが許されているのであれば、
この対処方法でも問題はないでしょう。

うまくいかない例はこちら

value1 = 20.42;
value2 = 10.1;

result = ((value1 *100) - (value2 * 100)) / 100;

普通に 20.42 - 10.1 を計算すると 10.32 になります。
JavaScriptの誤差をなくそうと、100を掛けると
20.42 * 100 = 2042.0000000000002 となってしまい、
result には 10.320000000000002 という残念な数値が入ってしまいます。

整数値に直す時点で、小数点の計算をしてしまっているので、
結果誤差が残ってしまうというのが、このパターンの問題点です。

②数値を文字列に変換して小数点を取り除く

①でうまくいかないのは、整数値に変換するときに計算をしているからなので、
文字列操作で整数にしてしまいます。

まず、小数点の位置をさぐるメソッドを作ります。

function getDotPosition(value){

 // 数値のままだと操作できないので文字列化する
 var strVal = String(value);
 var dotPosition = 0;

  // 小数点が存在するか確認
  if(strVal.lastIndexOf('.') === -1){
    // 小数点があったら位置を取得
    dotPosition = (strVal.length-1) - strVal.lastIndexOf('.');
  }

  return dotPosition;
}

次に実際に計算するメソッドを作ります。

function calcSubtract(value1,value2){

 // それぞれの小数点の位置を取得
 var dotPosition1 = getDotPosition(value1);
 var dotPosition2 = getDotPosition(value2);

 // 位置の値が大きい方(小数点以下の位が多い方)の位置を取得
 var max = Math.max(dotPosition1,dotPosition2);

 // 大きい方に小数の桁を合わせて文字列化、
 // 小数点を除いて整数の値にする
 var intValue1 = parseInt((value1.toFixed(max) + '').replace('.', ''));
 var intValue2 = parseInt((value2.toFixed(max) + '').replace('.', ''));

 // 10^N の値を計算
 var power = Math.pow(10,max);

 // 整数値で引き算した後に10^Nで割る
 return (intValue1-intValue2) / power;

}

整数値で計算した後に、
ずらした小数分を10のN乗で割ることで元の小数に戻してあげれば、
小数点で計算するタイミングがないためずれないという考え方です。

③ライブラリを使う

BigDecimal.jsを使えば良いとのことですが、
使用したことがないので紹介だけにとどめておきます。

https://github.com/dtrebbien/BigDecimal.js/

まとめ

JavaScriptで小数点の計算をするとなかなか面倒なので、
四捨五入など、丸めても問題ないか仕様を詰めた方が良いかもしれません。
もし他の言語で代替できるならそちらで計算してしまうのもありです。

ついでに、
JavaScript等の実装結果をすぐ見たい場合、以下のサイトがおすすめです。
https://jsfiddle.net/
HTMLやCSSもかけるブラウザ上のエディタ的なサイトです。
ささっと実行結果を確認したい時に使ってます。

最後に

スマートテック・ベンチャーズでは、未経験だけどiOSの開発をやりたい!という人を募集しています。
Advent Calendarのスマートテック・ベンチャーズページに会社およびWantedlyのURLをのせていますので、興味のある方は是非ご覧ください。
http://qiita.com/advent-calendar/2016/stv

明日は@okuderapさんです。
お楽しみに!

k_moto
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした