JavaScript:掃き出し法による逆行列の計算

今回は、掃き出し法(Gaussian elimination method, row reduction algorithm)を使って、正方行列の逆行列を計算するプログラムを紹介します。 但し、線形方程式を解くだけなら、こちらの記事 C++ vs Python vs JavaScript vs Julia のGaussの消去法を使ったほうが、逆行列を使って x=A-1b を計算するよりも高速に計算できます。 尚、下記のプログラムは、こちらの paiza.IO で試せます。

inverse.js


class Matrix {
  constructor(matrix) {
    this.n = matrix.length;
    this.matrix = matrix;
    this.invers = Array(this.n).fill().map(() => Array(this.n).fill(0));
  }
  inverse() {
    for (let i = 0; i < this.n; i++) {
      for (let j = 0; j < this.n; j++) {
        this.invers[i][j] = (i != j) ? 0 : 1;
      }
    }
    for (let i = 0; i < this.n; i++) {
      const scalar = 1 / this.matrix[i][i];
      for (let j = 0; j < this.n; j++) {
        this.matrix[i][j] *= scalar;
        this.invers[i][j] *= scalar;
      }
      for (let j = 0; j < this.n; j++) {
        if (i != j) {
          const scalar = this.matrix[j][i];
          for (let k = 0; k < this.n; k++) {
            this.matrix[j][k] -= scalar * this.matrix[i][k];
            this.invers[j][k] -= scalar * this.invers[i][k];
          }
        }
      }
    }
    return this.invers;
  }
}
const matrix = new Matrix([[1, -2, 8, -5, 3], [-8, 0, 6, 4, -1], [2, 9, 0, 5, -7], [3, -2, -7, 1, 5], [-8, 6, 4, -9, 0]]);
console.log(matrix.inverse());
 

 

〈結果〉


[[0.07274085710454226, -0.06044177091515161, 0.04536485373283013,  0.007777926780206557, -0.04120768597099561],
 [0.06390927029253435,  0.02933006379432559, 0.11649648460698483,  0.130615529033123350,  0.05676353953140865],
 [0.10151535470028161,  0.03969424701622636, 0.03486656832506370, -0.004157167761834557, -0.01984712350811318],
 [0.02306557596888830,  0.09092128201689689, 0.05283626123105807,  0.078315676545527680, -0.04546064100844844],
 [0.11942757524090500,  0.08538477748615875, 0.05762562501197342,  0.226096285369451560,  0.02873618268549206]]
 

 

Bun vs Node.js

今回は、最近注目のBunNode.jsの処理速度をGaussの消去法を使って比較しました。 より正確に言うと、それらに搭載されているJavaScriptエンジンのJavaScriptCoreとV8の比較です。 結果は表1のようになりました。Bun(JavaScriptCore)は、Node.js(V8)よりも約1.5倍高速でした。 尚、ベンチマークの計測で使用したプログラム(gauss.cpp, gauss.js)は、こちらの記事 C++ vs Python vs JavaScript vs Julia にあります。

 

表1.計算時間(gauss.cpp, gauss.js)
プログラミング言語 計算時間 C++を1としたときの計算時間
C++(Clang) 0.508967 sec 1.000
Bun(JavaScriptCore 0.668377 sec 1.313
Node.js(V8) 0.998315 sec 1.961

 

JavaScript数値計算をするならBun(JavaScriptCore)で決まり!

 

C++ vs Python vs JavaScript vs Julia

 

今回は、Gaussの消去法を使って、C++PythonJavaScript、Juliaの計算時間を計測しました。
Pythonに関しては、JITコンパイラであるNumba無しと有りでどうなるかを評価しました。
また、Numbaで@numba.jitを付けた関数を2回以上呼び出した場合、1回目と2回目以降では計算速度が異なります。2回目以降はcacheから関数を呼び出すため高速になるので、これについても評価しました。

〈テスト環境〉
・CPU:Intel Celeron Processor 1005M(1.9GHz)
・OS:Lubuntu 20.04.4
Python 3.8.10
・Numpy 1.21.1
・Numba 0.55.1
・llvmlite 0.38.0

結果は表1のようになりました。
PythonはNumba無しからNumba有りにすると、1回目の計算では350倍、2回目の計算では570倍高速化しました。
また、JavaScript(V8)が意外と健闘しました。Windows 8.1ではJuliaよりもわずかに速かったです。V8もNumbaと同様に2回目以降の計算を高速化してくれると嬉しいのですが。。。
Juliaに関してはC++と同レベルだと思っていただけにちょっと残念な結果に終わりましたが、ベクトルや行列の演算ができるので、今後AIの分野での利用が拡大するのではないかと思います。

注)Pythonでクラスの中の関数(メソッド)に@numba.jitを付けるとWarningが表示され高速化できなかったです。
注)Juliaの計算では2次元配列 Array{Float64, 2}:a[i, j] ではなく、配列の配列 Vector{Vector{Float64}}:a[i][j] を用いています。2次元配列を用いた場合、計算時間が6.888756secとなり、8倍以上遅くなりました。
?)gauss.pyのプログラム(Numba有り)で、set(a, b)solve(a, b)set(n, a, b)solve(n, a, b) にすると十数%遅くなりました。
また、#n = len(a)# を外しても十数%遅くなりました。どうしてこんなに遅くなるのでしょう?

表1.計算時間(Gaussの消去法)
プログラミング言語 計算時間 C++を1としたときの計算時間
C++(Clang) 0.508967 sec      1.000          
Python(Numba無し1回目の計算) 377.815687 sec      742.319          
Python(Numba無し2回目の計算) 378.716451 sec      744.088          
Python(Numba有り1回目の計算) 1.074311 sec      2.111          
Python(Numba有り2回目の計算) 0.655199 sec      1.287          
JavaScript(Node.js:V8) 0.998315 sec      1.961          
Julia 0.796610 sec      1.565          

 

Gaussの消去法は連立一次方程式の解法の一つです。今回のプログラムではピボットを選択しない方法を採用しているので、対角要素に0がある場合は使えません。 プログラムでは、係数行列は対角要素より下の要素は0、それ以外の要素は1、非同次項は全ての要素を1に設定しています。
     a x = b
     a:係数行列(n×nの正方行列)
     b:非同次項(n次元の列ベクトル)
     x:求める解(n次元の列ベクトル)

今回、計算時間を計測するに当たって用いたプログラムは以下のようになります。

gauss.cpp(Clang

/*
$ sudo apt update
$ sudo apt install clang -y
$ clang++ gauss.cpp -o gauss -O3
$ ./gauss
*/

#include <chrono>
#include <cstdio>

class Gauss {
  static const int n = 1000;
  double a[n][n] = {{}};
  double b[n] = {};
public:
  auto set(double a, double b) -> void {
    for (int i = 0; i < n; i++) {
      for (int j = i; j < n; j++) {
        this->a[i][j] = a;
      }
      this->b[i] = b;
    }
  }
  auto solve() -> void {
    for (int k = 0; k < n - 1; k++) {
      for (int i = k + 1; i < n; i++) {
        const double s = a[i][k] / a[k][k];
        for (int j = k + 1; j < n; j++) {
          a[i][j] -= s * a[k][j];
        }
        b[i] -= s * b[k];
      }
    }
    for (int i = n - 1; i >= 0; i--) {
      for (int j = i + 1; j < n; j++) {
        b[i] -= a[i][j] * b[j];
      }
      b[i] /= a[i][i];
    }
  }
  auto print() -> void {
    for (int i = 0; i < n; i++) {
      printf("x[%d] = %.10E\n", i, b[i]);
    }
  }
};

auto main() -> int {
  const auto gauss = new Gauss;
  gauss->set(1, 1);
  const auto start = std::chrono::system_clock::now();
  gauss->solve();
  const auto stop = std::chrono::system_clock::now();
  gauss->print();
  printf("%.6f%s\n", 0.000001 * std::chrono::duration_cast<std::chrono::microseconds>(stop - start).count(), "sec");
  delete gauss;
}
 

 

下記のNumba有り(JIT有り)のプログラムで@numba.jitコメントアウトすれば、Numba無し(JIT無し)のプログラムになります。
また、@numba.jit(cache=True)の方を有効にすると、2回目のプログラムの実行から1回目以降の計算をcacheから呼び出して高速化します。

gauss.py(Python×Numba

"""
$ sudo apt update
$ sudo apt install python3 python3-pip -y
$ pip3 install numba==0.55.1 numpy==1.21.1
$ python3 gauss.py
"""
import numba
import numpy as np
import time

@numba.jit
#@numba.jit(cache=True)
def set(a, b, a0, b0):
  #n = len(a)
  for i in range(n):
    for j in range(i, n):
      a[i][j] = a0
    b[i] = b0

@numba.jit
#@numba.jit(cache=True)
def solve(a, b):
  #n = len(a)
  for k in range(n - 1):
    for i in range(k + 1, n):
      s = a[i][k] / a[k][k]
      for j in range(k + 1, n):
        a[i][j] -= s * a[k][j]
      b[i] -= s * b[k]
  for i in range(n - 1, - 1, - 1):
    for j in range(i + 1, n):
      b[i] -= a[i][j] * b[j]
    b[i] /= a[i][i]

def printf(b):
  for i in range(len(b)):
    print(f"x[{i}] = {b[i]:.10E}")

#1回目の計算
n = 1000
a = np.zeros((n, n))
b = np.zeros(n)
set(a, b, 1, 1)
start = time.time()
solve(a, b)
stop = time.time()
#printf(b)
print(f"{(stop - start):.6f}sec")

#2回目の計算
n = 1000
a = np.zeros((n, n))
b = np.zeros(n)
set(a, b, 1, 1)
start = time.time()
solve(a, b)
stop = time.time()
#printf(b)
print(f"{(stop - start):.6f}sec")
 

 

gauss.js(Node.js, Bun

/*
$ sudo apt update
$ sudo apt install npm -y
$ sudo npm install n -g
$ sudo n stable
$ node gauss.js
*/
/*
$ sudo apt update
$ sudo apt install curl -y
$ curl https://bun.sh/install | bash
$ export PATH=~/.bun/bin:$PATH
$ bun gauss.js
*/

"use strict";
const performance = require("perf_hooks").performance;

class Gauss {
  constructor(n) {
    this.a = Array(n).fill().map(() => new Float64Array(n).fill(0));
    this.b = new Float64Array(n).fill(0);
  }
  set(a, b) {
    const n = this.a.length;
    for (let i = 0; i < n; i++) {
      for (let j = i; j < n; j++) {
        this.a[i][j] = a;
      }
      this.b[i] = b;
    }
  }
  solve() {
    const n = this.a.length;
    for (let k = 0; k < n - 1; k++) {
      for (let i = k + 1; i < n; i++) {
        const s = this.a[i][k] / this.a[k][k];
        for (let j = k + 1; j < n; j++) {
          this.a[i][j] -= s * this.a[k][j];
        }
        this.b[i] -= s * this.b[k];
      }
    }
    for (let i = n - 1; i >= 0; i--) {
      for (let j = i + 1; j < n; j++) {
        this.b[i] -= this.a[i][j] * this.b[j];
      }
      this.b[i] /= this.a[i][i];
    }
  }
  print() {
    for (const i in this.b) {
      console.log(`x[${i}] = ${this.b[i].toExponential(10)}`);
    }
  }
}

const gauss = new Gauss(1000);
gauss.set(1, 1);
const start = performance.now();
gauss.solve();
const stop = performance.now();
gauss.print();
console.log(`${(0.001 * (stop - start)).toFixed(6)}sec`);
 

 

Juliaにはclassがないのですが、structを使ってオブジェクト指向風に書いてみました。 上記のJavaScriptのプログラムと比較すると、オブジェクトobjに付随するメソッドmの呼び出しが、構造体strを引数に持つ関数fの呼び出しに変わったのがわかると思います。
   obj.m(args) → f(str, args)

gauss.jl(Julia

#=
$ sudo apt update
$ sudo apt install julia -y
$ julia gauss.jl
=#

using Printf

struct Gauss
  a::Vector{Vector{Float64}}
  b::Vector{Float64}
end

function Gauss(n::Int)
  a = [zeros(Float64, n) for i = 1 : n]
  b = zeros(Float64, n)
  Gauss(a, b)
end

function set!(gauss::Gauss, a::Float64, b::Float64)
  n = length(gauss.a)
  for i = 1 : n
    for j = i : n
      gauss.a[i][j] = a
    end
    gauss.b[i] = b
  end
end

function solve!(gauss::Gauss)
  n = length(gauss.a)
  for k = 1 : n - 1
    for i = k + 1 : n
      s = gauss.a[i][k] / gauss.a[k][k]
      for j = k + 1 : n
        gauss.a[i][j] -= s * gauss.a[k][j]
      end
      gauss.b[i] -= s * gauss.b[k]
    end
  end
  for i = n : - 1 : 1
    for j = i + 1 : n
      gauss.b[i] -= gauss.a[i][j] * gauss.b[j]
    end
    gauss.b[i] /= gauss.a[i][i]
  end
end

function print(gauss::Gauss)
  for i = 1 : length(gauss.b)
    @printf("x[%d] = %.10E\n", i, gauss.b[i])
  end
end

const gauss = Gauss(1000)
set!(gauss, 1.0, 1.0)
@time begin
  solve!(gauss)
end
#print(gauss)
 

注)Vector{Float64}Array{Float64, 1}と同じです。
注)Juliaでは引数に渡された変数を変更する関数(破壊的な関数)には関数名の最後に!を付けるそうですが、付けなくてもエラーにはなりません。

 

処理系のインストールと実行

今回は、Lubuntu 20.04.4 LTS(Focal Fossa)でテストしました。Lubuntuは、ダウンロードサイトからISOファイルをダウンロードし、ISOファイルを開き、その中身を全てフォーマット済みのUSBメモリにコピーして、F12キーを連打しながら再起動(途中でUSBメモリを挿入したドライブを選択)すれば使えるようになります。 この状態でソフトウェアのインストールが可能です。但し、ディスクではなくメモリにインストールされるだけなので、シャットダウンすれば消えてしまいます。

 

〈Clangのインストールと実行〉


$ sudo apt update
$ sudo apt install clang -y
$ clang++ gauss.cpp -o gauss -O3
$ ./gauss

 

PythonとNumbaのインストールと実行〉


$ sudo apt update
$ sudo apt install python3 python3-pip -y
$ pip3 install numba==0.55.1 numpy==1.21.1
$ python3 gauss.py

注)Numpyはバージョンによってインストール時にエラーになることがあります。numpy-1.21.1はOKでした。

 

〈Node.jsのインストールと実行〉


$ sudo apt update
$ sudo apt install npm -y
$ sudo npm install n -g
$ sudo n stable
$ node gauss.js

 

〈Bunのインストールと実行〉


$ sudo apt update
$ sudo apt install curl -y
$ curl https://bun.sh/install | bash
$ export PATH=~/.bun/bin:$PATH
$ bun gauss.js

 

〈Juliaのインストールと実行〉


$ sudo apt update
$ sudo apt install julia -y
$ julia gauss.jl

 

参考記事

 

JavaScriptで字句解析

関数の再帰呼び出しを使った簡易の字句解析器です。対応している字句は下記のようになります。

〈対応している字句〉


- 空白文字
- セパレータ(,.:;(){}[])
- 演算子(=<>!&|+\-*/%)※2回連続もOK:+=, +>, %&
- 数値(12, 34.56, 78.)
- 文字列("abcde")
- 識別子(先頭は英字:x1, abc123)
上記以外はエラーになります。

 

lexer.js


function getSpace(string) {
  if (string === "") return "";
  if (string[0].match(/[\s]/)) return string[0] + getSpace(string.slice(1));
  return "";
}
function getSeparator(string) {
  if (string === "") return "";
  if (string[0].match(/[,.:;(){}\[\]]/)) return string[0];
  return "";
}
function getOperator(string) {
  if (string === "") return "";
  if (string[0].match(/[=<>!&|+\-*/%]/)) return string[0] + getOperator1(string.slice(1));
  return "";
  function getOperator1(string) {
    if (string === "") return "";
    if (string[0].match(/[=<>!&|+\-*/%]/)) return string[0];
    return "";
  }
}
function getNumber(string) {
  if (string === "") return "";
  if (string[0].match(/[0-9]/)) return string[0] + getNumber1(string.slice(1));
  return "";
  function getNumber1(string) {
    if (string === "") return "";
    if (string[0].match(/[0-9]/)) return string[0] + getNumber1(string.slice(1));
    if (string[0].match(/[.]/)) return string[0] + getNumber2(string.slice(1));
    return "";
  }
  function getNumber2(string) {
    if (string === "") return "";
    if (string[0].match(/[0-9]/)) return string[0] + getNumber2(string.slice(1));
    return "";
  }
}
function getString(string) {
  const text = string;
  if (string === "") return "";
  if (string[0].match(/["]/)) return string[0] + getString1(string.slice(1));
  return "";
  function getString1(string) {
    if (string === "") throw `syntax error: " is missing in ${text}.`;
    if (string[0].match(/["]/)) return string[0];
    return string[0] + getString1(string.slice(1));
  }
}
function getIdentifier(string) {
  if (string === "") return "";
  if (string[0].match(/[A-Za-z]/)) return string[0] + getIdentifier1(string.slice(1));
  return "";
  function getIdentifier1(string) {
    if (string === "") return "";
    if (string[0].match(/[0-9A-Za-z]/)) return string[0] + getIdentifier1(string.slice(1));
    return "";
  }
}
function getTokens(string) {
  try {
    if (string === "")
      return [];
    else if (getSpace(string) !== "")
      return getTokens(string.slice(getSpace(string).length));
    else if (getSeparator(string) !== "")
      return [getSeparator(string)].concat(getTokens(string.slice(getSeparator(string).length)));
    else if (getOperator(string) !== "")
      return [getOperator(string)].concat(getTokens(string.slice(getOperator(string).length)));
    else if (getNumber(string) !== "")
      return [getNumber(string)].concat(getTokens(string.slice(getNumber(string).length)));
    else if (getString(string) !== "")
      return [getString(string)].concat(getTokens(string.slice(getString(string).length)));
    else if (getIdentifier(string) !== "")
      return [getIdentifier(string)].concat(getTokens(string.slice(getIdentifier(string).length)));
    else
      throw `syntax error: ${string} contains one or more unknown characters.`;
  }
  catch (error) {
    console.error(error);
  }
}
console.log(getTokens("y0 += 12.34 * x1 + x1y2z3 / 5678. i++ a&&b \"abcde\""));
console.log(getTokens("\"abcde"));
console.log(getTokens("abc@#xyz"));
 

 

結果


['y0', '+=', '12.34', '*', 'x1', '+', 'x1y2z3', '/', '5678.', 'i', '++', 'a', '&&', 'b', '"abcde"']
syntax error: " is missing in "abcde.
undefined
syntax error: @#xyz contains one or more unknown characters.
['abc', undefined]

 

EmscriptenでC/C++をWebAssemblyにコンパイルする

ここではEmscriptenを使ってC/C++をWebAssembly(.wasm)にコンパイルし、WASMモジュール(動的ライブラリ)やスタンドアロンWASMを作成する方法について紹介します。

 

Emscripten SDKのインストール

まず最初に下記のツールをインストールします。次に公式マニュアルの手順通りにすれば、WindowsEmscripten SDKをインストールできます。

〈D:\sdkの直下にEmscripten SDKをインストールする手順〉
Windowsコマンドプロンプト)
cd /d d:\sdk
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
git pull
emsdk install latest
emsdk activate latest

 

WASMモジュール(動的ライブラリ)の生成

example.cのようにmain関数を含まない場合は、下記のようにコンパイルしてWASMモジュールを生成することができます。

example.c

double add(double x, double y) { return x + y; }
double mul(double x, double y) { return x * y; }
 

 


set path=d:\sdk\emsdk\upstream\emscripten;%path%
emcc -Os -s SIDE_MODULE=1 example.c -o example.wasm

注)-Osはサイズの最適化(optimization for size)です。実行速度を最適化する場合は、-O1、-O2、-O3を使ってください。詳しくはこちらを参照してください。GCCコンパイラのOptimization Levelのベンチマーク結果はこちらです。

C++(.cpp)の場合は、下記のように em++ を使います。

example.cpp

auto add(double x, double y) -> double { return x + y; }
auto mul(double x, double y) -> double { return x * y; }
 

 


set path=d:\sdk\emsdk\upstream\emscripten;%path%
em++ -std=c++17 -Os -s SIDE_MODULE=1 example.cpp -o example.wasm

注)上記のem++の場合コンパイルはうまくいくのですが、実行時に呼び出しでエラー(emccの方は問題ありません)になります。原因はaddとmulの関数名が別の関数名_Z3addddと_Z3mulddに書き換えられるからです。 そこでWABT(WebAssembly Binary Toolkit)のwasm2watコマンドを使って.wasm(バイナリ形式)から.wat(テキスト形式)に変換し、エディタで関数名を元のaddとmulに戻します。 それから再びWABTのwat2wasmコマンドを使って.watから.wasmに変換すれば、関数を呼び出せるようになります。もちろん書き換えられた関数名で呼び出すようにしても構いません。ちょっと面倒ですが仕方ありません。

〈書き換えられた関数名を元に戻す〉

wasm2wat example.wasm -o example.wat
"_Z3adddd" → "add"
"_Z3muldd" → "mul"
wat2wasm example.wat -o example.wasm

 

Node.jsでWASMモジュールを実行

生成されたexample.wasmは、Node.jsから下記のように呼び出すことができます。

main.mjs

import * as fs from "fs";
const example = await WebAssembly.instantiate(fs.readFileSync("example.wasm"));
console.log(example.instance.exports.add(1, 1));
console.log(example.instance.exports.mul(1, 1));
 

 

〈Node.jsで実行〉

node main.mjs

 

ブラウザでWASMモジュールを実行

次のようにするとscript要素の中でWASMモジュールを呼び出すことができます。

main.html

<!doctype html>
<html>
<body>
<script>
(async () => {
  const response = await fetch("example.wasm");
  const buffer = await response.arrayBuffer();
  const example = await WebAssembly.instantiate(buffer);
  document.body.innerHTML += `<p>${example.instance.exports.add(1, 1)}</p>`;
  document.body.innerHTML += `<p>${example.instance.exports.mul(1, 1)}</p>`;
})();
</script>
</body>
</html>
 

 

但し、このHTMLファイルは単独では実行できないので、HTMLファイルがあるディレクトリでWebサーバーを起動しブラウザからアクセスします。

Pythonの簡易Webサーバーを起動〉

python -m http.server 8080

 

〈ブラウザからWebサーバーにアクセス〉

(ブラウザのアドレスバー)
http://localhost:8080/main.html



スタンドアロンWASMの生成

main.cppのようにmain関数を含む場合は、次のようにコンパイルしてスタンドアロンWASMを生成することができます。

main.cpp

#include <iostream>
auto main() -> int { std::cout << "Hello!" << std::endl; }
 

 


set path=d:\sdk\emsdk\upstream\emscripten;%path%
em++ -std=c++17 -Os -s STANDALONE_WASM main.cpp -o main.wasm

 

生成されたmain.wasmは、WasmerWasmtimeなどのWASM実行エンジンを使って実行できます。

〈Wasmerで実行〉

wasmer main.wasm

 

まとめ

Node.jsのC++ addonsを使ってモジュールを作るよりもはるかに簡単にWASMモジュールを作ることができます。ただ、実行速度はネイティブに比べかなり遅い場合があります。

 

参考記事

注)わかりやすくするためにタイトルを変更している記事があります。

 

Node.js:CSVファイルを読み込む方法

今回は、Node.js の標準モジュールだけを使ってCSVファイルを読み込み、指定した行(row)と列(column)を抽出するプログラムを紹介します。


import * as fs from "fs";

console.log(JSON.stringify(csv("sample.csv", 1).data));
console.log(csv("sample.csv", 1).row(3));
console.log(csv("sample.csv", 1).col(5));

function csv(csvfile, skiprows) {
  class CSV {
    constructor(csvfile, skiprows) {
      const text = fs.readFileSync(csvfile, "utf-8");
      const rows = text.trim().replace(/^\s*(\r\n|\n|\r)/gm, "").split(/\r\n|\n|\r/).slice(skiprows);
      this.data = rows.map(row => row.split(/,/).map(value => isNaN(value) ? value : Number(value)));
    }
    row(row) {
      return this.data[row];
    }
    col(col) {
      return this.data.map(row => row[col]);
    }
  }
  return new CSV(csvfile, skiprows);
}
 

注)importステートメントを使ってモジュールを呼び出しているので、ファイルの拡張子は mjs にしてください。

sample.csv

csv data
0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9
1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9
2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9
3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9
4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9
5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9
6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9
7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9
8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9
9.0, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9

 

〈実行結果〉


[
 [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
 [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9],
 [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9],
 [3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9],
 [4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9],
 [5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9],
 [6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9],
 [7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9],
 [8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9],
 [9.0, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9]
]
[3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9]
[0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5]

 

NectarJSを使ってJavaScriptを事前コンパイルする

注意!
下記のプログラムを再度実行したところ、コンパイルエラーが発生しました。
以前は正常に動作したのですが??? (2021年3月13日)

今日は NectarJS の紹介です。私の知る限りでは、NectarJS は JavaScriptコードをマシンコード(バイナリコード)に事前コンパイルできる唯一のコンパイラではないかと思います。 JavaScript の全ての機能がサポートされている訳ではありませんが、簡単なコードであればコンパイルできます。 現状でも、Arduino のようなマイコン用のプログラムを作成するのであれば充分だと思います。

 

〈インストール〉


OS:MX Linux 19.2
$ sudo apt update
$ sudo apt install npm -y
$ sudo npm install nectarjs -g

 

コンパイルと実行〉


$ nectar main.js -o main
$ ./main

 

main.js


const add = function (x, y) { return x + y; };
console.log(add(1.111, 2.222));
console.log(add("aaa", "bbb"));
 

 

〈実行結果〉


3.333
aaabbb

 

次はガウスの消去法を使って連立一次方程式を解くプログラムです。大規模な数値計算は無理ですが、小規模の計算であれば問題なく実行できます。

gauss.js


function solve(a, b) {
  let length = a.length;
  for (let k = 0; k < length - 1; k++) {
    for (let i = k + 1; i < length; i++) {
      let s = a[i][k] / a[k][k];
      for (let j = k + 1; j < length; j++) {
        a[i][j] -= s * a[k][j];
      }
      b[i] -= s * b[k];
    }
  }
  for (let i = length - 1; i >= 0; i--) {
    for (let j = i + 1; j < length; j++) {
      b[i] -= a[i][j] * b[j];
    }
    b[i] /= a[i][i];
  }
}
let a = [[1.2, 0.5, 9.2, 7.6, 2.1], [9.8, 1.3, 8.4, 5.3, 3.3], [4.1, 2.7, 1.4, 7.3, 5.6], [0.2, 8.7, 4.5, 1.9, 0.6], [3.7, 6.4, 5.6, 2.6, 1.7]];
let b = [1.9, 8.3, 5.1, 3.4, 7.2];
solve(a, b);
for (let i = 0; i < b.length; i++) console.log(b[i]);
 

注)const length = a.length; にするとエラーになります。

 

〈実行結果〉


-0.93230297368388
-0.26417804844125
 2.10502247334133
-3.85061459325048
 6.21396035552607


参考記事