定期的に「 COBOL はひどい」と聞くので「 COBOL はそういう事に使うのではない」という事を記録しておきます。
Update (8/10)
「COBOLだけではない!」とコメントをいただきまして、旧タイトル「小数点を扱うならCOBOLが圧倒的」から変更し、記事の内容も変更しています。
(夜に追記) C# と bc が加わりました。 ReXX も 0.3 から BCD で処理しているらしいですが、再確認をしておきたいと思います。
このエントリーは 0.1 + 0.2 = 0.3 になる言語を探すのが目的ではありません
冒頭でも書いたように「COBOL は (モダンなプログラム言語名に比べて) ひどい」とよく言われます。
「FIZZBUZZ も満足に書けないプログラム言語なんて(自主規制)」という事を言われたなら、COBOLの立場から言えば「0.1 + 0.2 = 0.3
も満足に計算できないプログラム言語なんて」と言いたくもなるでしょう。
要するに 目的外の事に使おうとすると、大変な思いをするから道具は選びましょう というのが主旨です。
「~を使えばできるよ」「~とすればできるよ」という話もありますが、現行のプログラム言語に この程度の事に対する解方があるのは当たり前 なのです。
(テクニカル的にはとても面白いんですが!!!)
その点において 1983年産まれの Ada が BCD を採用していて計算ができるという事実はとても興味深いです。では、なぜ Ada ではなく COBOL だったのか。そこから何が学べるのか?という過去からの学びを得て、同じ過ちをしないようにすることも大切だと思います。
TL;DR
COBOL は小数点処理が圧倒的に強い。だから金利などを取り扱う金融系で使われるづけるのです。
小数点処理を軽視する者はプログラムを書くこと無かれ。
BigDecimal 等の外部ライブラリなど使わずプログラム言語の標準仕様/機能のみで 0.1 + 0.2
が 0.3
となるのは COBOL だけです 小数点を含む数値を BCD で扱う言語となります。
今のところ判明しているのは
です。
問題の再確認
一般的に言われる少数点を持つ数値を演算すると誤差が生じるケースがあります。
0.1 + 0.2
が 0.3
ではなく 0.30000000000000004
となる話です。
理由はhttp://blog.tsukaby.net/archives/1706にまとめられていますのでご一読を。
上記ブログでも記載されていますが、小数点の取り扱いを BCD (二進化十進表現) として扱うことで、丸め誤差を回避する方法があります。
この BCD を言語仕様として実装していたのが COBOL だけだと思っていたのですが、なんと MSX-Basic や Ada も扱えるという事がわかりました。
論よりコード
Ubuntu 18.04/Linux(amd64) 上に各ランタイムをインストールして確認しました
https://paiza.io/ja でも確認することができます(同様の結果でした)
COBOL
# cobc (OpenCOBOL) 1.1.0
IDENTIFICATION DIVISION.
PROGRAM-ID. float-checker.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 A1 PIC 9(1)V9(17) VALUE 0.1.
01 A2 PIC 9(1)V9(17) VALUE 0.2.
01 WR PIC 9(1)V9(30).
PROCEDURE DIVISION.
COMPUTE WR = A1 + A2.
IF 0.3 = WR THEN
DISPLAY "TRUE"
ELSE
DISPLAY "FALSE"
END-IF.
STOP RUN.
# ==> TRUE
Ada
※ コメントから引用しました
with Ada.Text_IO; use Ada.Text_IO;
procedure Hogera is
type F is delta 0.00000000000000001 range 0.0 .. 9.99999999999999999;
a1 : constant F := 0.1;
a2 : constant F := 0.2;
wr : F;
begin
wr := a1 + a2;
if F(0.3) = wr then
Put_Line ("TRUE");
else
Put_Line ("FALSE");
end if;
end Hogera;
-- => TRUE
MSX-Basic
C#
標準に Decimal 型があります
こう見ると COBOL の次は C# なのかもしれない。
※ コメントから引用しました
using System;
public class Hogera
{
public static void Main()
{
const decimal a1 = 0.1m;
const decimal a2 = 0.2m;
decimal wr = a1 + a2;
if (0.3m == wr) {
System.Console.WriteLine("TRUE");
} else {
System.Console.WriteLine("FALSE");
}
}
}
bc コマンド
a1 = 0.1;
a2 = 0.2;
wr = a1 + a2;
if (0.3 == wr) {
"TRUE";
}
if (0.3 != wr) {
"FALSE";
}
コメントをご覧ください。
他のプログラム言語の結果
では他のプログラム言語ではどうなのか、調べてみました。
※ダサいコードなのは COBOL に合わせたからです、ごめんなさい
Ruby
# ruby 2.5.1p57
a1 = 0.1
a2 = 0.2
wr = a1 + a2
if 0.3 == wr
print "TRUE"
else
print "FALSE"
end
# ==> FALSE
Python3
# Python 3.6.5
a1 = 0.1
a2 = 0.2
wr = a1 + a2
if 0.3 == wr:
print("TRUE")
else:
print("FALSE")
# ==> FALSE
Go
// go1.10.1
package main
import "fmt"
func main() {
var a1 float64 = 0.1
var a2 float64 = 0.2
var wr float64
wr = a1 + a2
if 0.3 == wr {
fmt.Println("TRUE")
} else {
fmt.Println("FALSE")
}
}
//==> FALSE
Javascript
// V8 6.7.288.46
var a1 = 0.1;
var a2 = 0.2;
var wr = a1 + a2;
if (0.3 == wr) {
console.log("TRUE");
} else {
console.log("FALSE");
}
//==> FALSE
Java
// openjdk 10.0.1
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
double a1 = 0.1;
double a2 = 0.2;
double wr = a1 + a2;
if (0.3 == wr) {
System.out.println("TRUE");
} else {
System.out.println("FALSE");
}
}
}
//==> FALSE
まとめ
要するに FIZZBUZZ 書くプログラム言語じゃないんだよ。
BigDecimal 使えばいいじゃん
はい、今はそうですね。今はね。
Rational 使えばいいじゃん
その通りです。使えるものは使いましょう
あとがき
COBOLコードがsyntax highlightされない悲しみ。
そしてdisるときはこのくらい書いてもらいたいものです。
コメントをいただいて (8/10 追記)
書いてみると知見が集まってありがたい!
そして Ada コードがsyntax highlightされない悲しみ。。。
EoT