Snap!(BYOB 4.0)で四則演算を固定小数点計算で扱う方法について

Snap!における四則演算のブロックは浮動小数点による計算のため,例えば0.1+0.2と0.3は等しくならない。


また,変数を0から0.1ずつ増やして10回繰り返した場合の値も1とは一致しない。


これらは初等教育などでSnap!を利用する場合に(Scratchでも同様であるが)気をつける必要がある。このページでは,Snap!の四則演算のブロックの計算部分などを,javascriptで固定小数点を扱うために真鍋氏によりGithubで公開されているライブラリーJSDecimalを用いて変更して,四則演算を浮動小数点計算から固定小数点計算に変更する方法をのべる。

GitHub - hiroshi-manabe/JSDecimal: A JavaScript implementation of decimal type.
なお,JSDecimalはMITライセンスにより公開されている。

四則演算を固定小数点で扱う。

Snap!のサイトでJSDecimalを利用するには,lib/decimal.jsの内容をjavascriptのブロックに入れて実行すれば良い。

最初から固定小数点を利用可能なSnap!を使うには以下の様にローカルにSnap!のソースをダウンロードする。

  1. Snap!のソースファイルをGitHub - jmoenig/Snap--Build-Your-Own-Blocks: a visual programming language inspired by Scratchよりダウンロードする。
  2. zipを適当な場所に展開する。
  3. 展開したフォルダに上記のdecimal.jsを入れる。
  4. 展開したフォルダ中のsnap.htmlに次の行を加える。場所は「FileSaver.min.js」の行の後でよい。(使用をやめる場合はこの行を削除するだけでよい。)
    	<script type="text/javascript" src="decimal.js"></script>
    
  5. フォルダ内のindex.html(あるいは直接snap.html)をブラウザで開く。

次に四則演算のブロックを固定小数点で計算するブロックに変更する。四則演算の計算は,Snap!では,threads.jsに次のように定義されている。

// Process math primtives

Process.prototype.reportSum = function (a, b) {
    return +a + (+b);
};

Process.prototype.reportDifference = function (a, b) {
    return +a - +b;
};

Process.prototype.reportProduct = function (a, b) {
    return +a * +b;
};

Process.prototype.reportQuotient = function (a, b) {
    return +a / +b;
};

そこでこれらを固定小数点で計算するように次のように変更して,javascriptブロックを作成する。
Process.prototype.reportSum = function (a, b) {
    return Decimal(+a).add(+b) ;
};

Process.prototype.reportDifference = function (a, b) {
    return Decimal(+a).sub(+b);
};

Process.prototype.reportProduct = function (a, b) {
    return Decimal(+a).mul(+b);
};

Process.prototype.reportQuotient = function (a, b) {
    return Decimal(+a).div(+b);
};

これを利用して0.1+0.2=0.3となるかチェックすると次のように「はい」を返し,和を固定小数点で計算したことがわかる。

同様に,「・・・を・・・ずつ変える」ブロックは,同じthreads.jsで

VariableFrame.prototype.changeVar = function (name, delta, sender) {
    // change the specified variable if it exists
    // else throw an error, because variables need to be
    // declared explicitly (e.g. through a "script variables" block,
    // before they can be accessed.
    // if the found frame is inherited by the sender sprite
    // shadow it (create an explicit one for the sender)
    // before changing the value ("create-on-write")

    var frame = this.find(name),
        value,
        newValue;
    if (frame) {
        value = parseFloat(frame.vars[name].value);
        newValue = isNaN(value) ? delta : value + parseFloat(delta);
        if (sender instanceof SpriteMorph &&
                (frame.owner instanceof SpriteMorph) &&
                (sender !== frame.owner)) {
            sender.shadowVar(name, newValue);
        } else {
            frame.vars[name].value = newValue;
        }

    }
};

と定義されているので,これを
VariableFrame.prototype.changeVar = function (name, delta, sender) {
    var frame = this.find(name),
        value,
        newValue;
    if (frame) {
        value = parseFloat(frame.vars[name].value);
        newValue = isNaN(value) ? delta : Decimal(value).add(parseFloat(delta));
        if (sender instanceof SpriteMorph &&
                (frame.owner instanceof SpriteMorph) &&
                (sender !== frame.owner)) {
            sender.shadowVar(name, newValue);
        } else {
            frame.vars[name].value = newValue;
        }

    }
};

として,ブロックを作成する。


実行してみると,確かに固定小数点で計算していることがわかる。


もともとの浮動小数点計算の内容をもつブロックを作成しておくと,固定小数点と浮動小数点の計算を随時切り替えることが可能になる。


このページの内容は,JSPS科研費26560089の助成を受けた研究の過程で得られたものです。