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
今回は、最近注目のBunとNode.jsの処理速度をGaussの消去法を使って比較しました。 より正確に言うと、それらに搭載されているJavaScriptエンジンのJavaScriptCoreとV8の比較です。 結果は表1のようになりました。Bun(JavaScriptCore)は、Node.js(V8)よりも約1.5倍高速でした。 尚、ベンチマークの計測で使用したプログラム(gauss.cpp, gauss.js)は、こちらの記事 C++ vs Python vs JavaScript vs Julia にあります。
プログラミング言語 | 計算時間 | 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++、Python、JavaScript、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) で # を外しても十数%遅くなりました。どうしてこんなに遅くなるのでしょう?
プログラミング言語 | 計算時間 | 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次元の列ベクトル)
今回、計算時間を計測するに当たって用いたプログラムは以下のようになります。
/*
$ 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から呼び出して高速化します。
"""
$ 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")
/*
$ 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)
#=
$ 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のインストール
まず最初に下記のツールをインストールします。次に公式マニュアルの手順通りにすれば、WindowsにEmscripten SDKをインストールできます。
- GitのWindows版(https://git-scm.com/download/win)
- PythonのWindows版(https://www.python.org/downloads/windows/)
(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モジュールを生成することができます。
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++ を使います。
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から下記のように呼び出すことができます。
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 main.mjs
♦ ブラウザでWASMモジュールを実行
次のようにするとscript要素の中でWASMモジュールを呼び出すことができます。
<!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 -m http.server 8080
(ブラウザのアドレスバー) http://localhost:8080/main.html
♦ スタンドアロンWASMの生成
main.cppのようにmain関数を含む場合は、次のようにコンパイルしてスタンドアロンWASMを生成することができます。
#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は、WasmerやWasmtimeなどのWASM実行エンジンを使って実行できます。
wasmer main.wasm
♦ まとめ
Node.jsのC++ addonsを使ってモジュールを作るよりもはるかに簡単にWASMモジュールを作ることができます。ただ、実行速度はネイティブに比べかなり遅い場合があります。
参考記事
- Emscripten
- Emscripten Download and Install
- Emscripten SDK(emsdk)
- Emscripten Compiler Frontend
- Emscripten Option Settings
- Standalone WASM and WASM Module
- WebAssembly Standalone Example
- WebAssembly Dynamic Linking
- WebAssembly: Building Standalone and Dynamic Linking Modules in Windows
- Emscripten:WebAssembly Standalone
- EmscriptenのStandaloneモードについて
- Mozilla:C/C++からWebAssemblyにコンパイルする
注)わかりやすくするためにタイトルを変更している記事があります。
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 にしてください。
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
参考記事
- NectarJS
- NectarJS Quick Start
- NectarJSがJavaScriptのサービスとしてのコンパイルを提供
- Compiling an Arduino firmware written in JavaScript from your Raspberry Pi 3 with NectarJS