LoginSignup
16
10

More than 5 years have passed since last update.

JavaScriptのfloatで正確な比較を行う

Last updated at Posted at 2017-10-30

TLにこんなツイートが回ってきました

Yep. JavaScriptに限らず浮動小数点数の丸め誤差の問題はあります。
しかし、仕様通りですが、便利かと言えば便利ではありませんね?

こんにちはAST

こんなときは、ASTで変換してやればいいです。
Babelで仕様とか色々大切なものを歪めてあげましょう。

akameco/babel-plugin-float-equal: Babel plugin for float equal

$ npm install --save-dev babel-plugin-float-equal 
{
  plugins: ["float-equal"]
}
// before
0.1 + 0.2 === 0.3
// => false


// after
0.1 + 0.2 === 0.3
// => true

Babelを使って、===で比較されていた場合、問答無用で以下のコードへ変換してやります。
Number.EPSILONは2.220446049250313e-16です。
いわゆる許容する誤差です。

0.1 + 0.2 === 0.3

           

typeof (0.1 + 0.2) === 'number' && typeof 0.3 === 'number'
  ? Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON
  : 0.1 + 0.2 === 0.3

変数

もちろん変数に対応してなくては使い道がないですね。

var a = 0.1
var b = 0.2
a + b === 0.3
// => true

How to create Babel Plugin?

以下がプラグインの全コードです。
BinaryExpressionのときのオペレーターが===のときに、左右のnodeをtemplate関数を呼んで変換したものをpath.replaceWithを使って入れ替えてあげます。
単純ですね。

export default ({ template } /*: {template: Function} */) => {
  const builder = template(`
  (typeof LEFT === 'number' && typeof RIGHT === 'number') ? Math.abs(LEFT - RIGHT) < Number.EPSILON : LEFT === RIGHT
  `)

  return {
    name: 'float-equal',
    visitor: {
      BinaryExpression(path /*: Object */) {
        if (path.get('operator').node !== '===') {
          return
        }

        const { node: left } = path.get('left')
        const { node: right } = path.get('right')

        const ast = builder({ LEFT: left, RIGHT: right })

        path.replaceWith(ast)
        path.skip()
      }
    }
  }
}

おわりに

プロダクションで使ってもいいですか?

いいえ、プロダクションで使ってはいけません。
まあ、危険を犯すのが好きならば、どうぞご自由に。

akameco/babel-plugin-float-equal: Babel plugin for float equal

16
10
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
10