TLにこんなツイートが回ってきました
正確な実数計算をやらされるJavaScriptくん #擬竜戯画 pic.twitter.com/ipE56C2YbV
— RAO(らお) (@RIORAO) October 26, 2017
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
君のJavaScriptはそうなのか。僕のJavaScriptは計算出来るのだけど https://t.co/XSvTE4h8tu pic.twitter.com/6V5HskHExj
— あかめ@無職.js (@akameco) October 29, 2017
インチキ臭くて草
— wreulicke (@wreulicke) October 29, 2017