そもそも「多倍長」ってどういう意味?

[ 2019-10-25追記 ] いよいよ「多倍長精度数値計算」(森北出版)が2019年11月22日に発売になります。この部分も手直ししたものが収録されますんで・・・(売れそうもないので)お高いですが・・・よろしくです。

第1章の最初の記述がまとまらんので,ざっと書き出してみる。こんな感じでどうっすかね?>Fさん

—-
1.1 そもそも「多倍長」ってどういう意味?

コンピュータ内部におけるデータは全て2進数(binary number),即ち,0もしくは1の並びで表現される「数値」(numerical value)である。データを格納するメモリ(memory)の大きさは制限されているので,全ての数値の長さは有限でなければならない。標準的に使用される数値の形式(format),即ちデータ型(data type)は規定されており,大別して整数(integer)型と実数型(real number)型に分類される。C/C++の標準的な整数型と実数型の変数,即ち,数値を格納するために確保されるメモリ領域を使用するために宣言するデータ型は

  • 整数型
    • 符号付き整数型(signed integer)
        • int型
        • long int型
    • 符号なし整数型(unsigned integer)
        • unsigned int型
        • unsigned long int型
  • 実数型
      • float型
      • double型

となる。現在主流の64bit CPUではint型,unsigned int型,float型が32bit長,long int型,unsigned long int型, double型が64bit長である。これらの標準的なデータ型はCPU内部のハードウェア演算回路を使用し,1命令(instruction)での高速な演算処理(arithmetic)が可能である。
本書では,これらの標準的なデータ型を複数組み合わせ,64bitより長い数値を扱う計算全般を広義の多倍長(multiple precision)計算と呼ぶことにする。もっとも主題は多倍長の実数型を使用する数値計算(numerical computation)なので,特に断らない限り,double型より長い実数型を扱う数値計算を多倍長計算と呼ぶ。これらの多倍長計算は標準的なfloat型,double型を使用した数値計算に比べて多大な計算時間を要するのが普通である。

本書で扱うのは下記の多倍長整数型と多倍長浮動小数点型である。

  • 多倍長整数型(mpz_t, mpz_class)・・・整数型を可変長の配列(固定長にする場合もある)にして長い桁をサポートする
  • 多倍長浮動小数点型
    • 多数桁方式(mpf_t, mpf_class, mpfr_t, mpfr::mpreal)・・・固定長の整数型の配列を仮数部(小数部)とする
    • マルチコンポーネント方式(dd_real, qd_real)・・・固定長の実数型(浮動小数点型)の配列を丸ごと利用する

多倍長有理数型(mpq_t, mpq_class)と多倍長複素数型(mpc_t, complex)については,前者は分子と分母にそれぞれ多倍長整数型を,後者は実数部と虚数部にそれぞれ多倍長浮動小数点型を使用して実装される。

ところで,本書では「多倍長」をかように定義するが,複数のデータ型を混合して計算する場合もmultiple(複数) precision(精度)と呼称しているケースもある。後述するように,現在の計算環境は標準データ型演算速度の高速化が限界に達しており,計算効率を上げるために,例えば,高速なfloat型と比較的低速なdouble型を混合させる方法も盛んである。本書で取り上げる長い実数型でも,精度は様々なものを混在させて利用することが前提となっており,その意味では「より長い精度の実数型を取り混ぜで使用する数値計算」という意味合いも「多倍長計算」に込められていると解釈してもらっても構わない。

しかし,これでは「より長い精度桁数」を利用しているかどうかは判然としないこともあるので,そこを強調したい向きには,可変精度(variable precision)計算,任意精度(arbitrary precision)計算という言葉を使用すると良い。無限精度(infinite precision)計算という言葉を使うケースもあったが,無限の精度をコンピュータで扱うことができない以上,過大表現っぽくて著者個人としては好きになれず,使用したことはない。もし見かけた時には本書で扱う多倍長計算と同一のものと考えてよい。

3/21 (水・祝) 駿府・氷雨

 ここのところ春らしい温かさだったのだが,昨日の夜から急に寒気が入り込み,本日はやたらに寒い日。ソメイヨシノが開花し始め,本日あたりが最初の花見クラスタ多数出現日になるハズが,雨は降るわ寒いわで殆ど人出なしロクデナシ。折角のごった返していない春の祝日なので,眼鏡屋で3年ぶりに老眼鏡のレンズを新調。今よりだいぶマシにはなるが,老眼と乱視が進んだ分,無理やり補正すると景色がゆがむ。ネトウヨや極左の世の中はこう見えているのかと思うほどである。来週には到着するとのことなので,それまではJINSの安物遠近両用中途半端な矯正眼鏡で凌がねばならぬ。年寄りのメンテナンスは金がかかるのう。

 先週までにSIAM PP 2018の英語プレゼンと情報処理学会の卒研発表三件の付き添いを完了。前者はともかく,後者は自分がやるより気疲れすることを知る。ま,S君,K君,A君,ご苦労様でした。

 

 その合間に,寄席通い2件と,みうらじゅんフェス来訪を果たす。トリの三遊亭白鳥「はらぺこ奇譚」と桃月庵白酒「幾代餅」を目当てに出かけたのだが,いやまぁ二つとも捧腹絶倒の高座で満腹満腹。やっぱり勢いのある中堅どころは素晴らしい。みうらじゅんフェスはあいにく駐車場が満杯で,だいぶ遠くに車を置いての移動が大変であったが,展示物はこれでもかこれでもかというほどのゴミ(?)からお宝油絵やコレクションまで並んでいて(個人的には「はかせたろう」が宜しかった),見ごたえがあった。

 つーことで,出張が一段落して合間の楽しみも堪能したところで,「多倍長数値計算入門(仮)」の原稿執筆と,新年度の学生実験の準備に勤しみたいなと思っている毎日である。・・・いややってますヨホントに!

 風呂入って寝ます。

2/19(Mon) – 23(Fri) Auckland, Warm

 つーことで,ANODE 2018に遠路はるばる参加してきた。常微分方程式(ODE)の数値解法(Numerical Methods)の大家,John C. Butcher主催の小規模な研究集会,前回は5年前,80歳の誕生日を記念して開催されていたが,その時は1月開催だったので,日本の現役の方々はことごとく参加できなかった。つーことで,今回は2月開催だし,その直前のSciCADE2017はネタ切れで出られなかったこともあり,確保していた旅費を回すにはちょうどいいという事情もあり,いそいそと参加を申し込んだのだが,小規模とはいいながらあーた,Butcherを慕う各国の優秀な教え子や,その道の大家が超真摯な態度でサボることなく5日間のセッションに参加していたのである。

 とはいえ,こちとら11月からスカイプ英会話で特訓していたこともあり,まぁ何とかプレゼンはスムーズかつブロークンに終えたのだが,相変わらずヒアリング力がダメダメで質問にちゃんと答えられなかったのが残念であった。もっと精進せにゃぁいかんです。幸い,日本からは陽的Runge-Kutta公式のレコードホルダーO先生が参加してたので,孤独に陥ることもなく,それなりに楽しく過ごすことができた。

 Conference Dinnerは絶対歌合戦になるだろーなーと思ったら案の定そうなった。O先生のところには各国の歌詞をまとめたものが送られてきていたそうで,「さくらさくら」も入ってたとの由。幸い(?)日本人まで回ってくることはなく,ドイツ人による歓喜の歌,中国人による流行歌(?)と中国語版のハッピーバースデー,Butcher+αによるマオリ語の歌(下記写真),あたりでお開きとなった。

 ANODE2018の特集号がNumerial Algorithmに予定されているそうなので,5月下旬までに投稿するようにとのこと。投稿予定者が多くて一人15ページまでという制限付きだが,さてワシのはそのまま後ダメ臭いので,もう少し改良してからだなぁ。まぁダメでも来訪記念ということで頑張ってみることにしよう。

 つーことで充実したAuckland訪問であった。めんどくさかったので,お土産は全部大橋巨泉の店で買いまくってきました。楽なもんで。

 片道10時間の空の旅,ずーっと座りっぱなしでケツが痛くなったのがまだ続いていたりする。尻の肉が落ちさせいだと神さんには言われたが,つまりは寄る年波ということだな。

 寝ます。

内田春菊「がんまんが ~私たちは大病している~」ぶんか社

[ Amazon ] ISBN 978-4-8211-3566-0, \1000 + TAX

 内田春菊のエッセイ漫画に外れはない。流麗なペンタッチが白い画面に艶めかしい柔肌を描き出す。それでいて,内容はシビアで容赦ない。ダメンズを次々に恋人にして子供を作り,ダメと分かった時点で放り出す。内田本人の生命力・経済力が図抜けている分,割り切り方はスッパリしていて小気味良い。ワシはその力強い生き方を描いたエッセイ漫画である「私たちは繁殖している」は読んではいたものの,途中で脱落していたが,続刊が次々に出ているのを見るにつけ,このまま佐藤愛子か瀬戸内寂聴のように「完成」していくのであろうなと思っていたら,大腸がんになってしまったという。その顛末を,手術直後まで描いているのが本書である。その後については連載中なのでまた続刊が出るとのことである。

 20年以上前,オストメイトの方と仕事をしていたことがある。当時は人工肛門をサポートする技術が未熟だったらしく,アンモニアの匂いが漏れてはた目から見てても気の毒であった。さすがに最近は匂い消しフィルター付きの排便袋になっているらしく,昔よりずっと活動しやすいようである。その辺の事情については,内田のインタビュー記事((1),(2),(3),(4))を読むとよい。本書を買って読む気のない人にはこちらを勧める。

 しかし,ワシとしては,本書まるごと読まれることをお勧めしたい。ガン発覚のいきさつから,治療方針が決まるまでの医療機関とのゴタゴタ,そして最終的に人工肛門を形成するに至るまでの一部始終が流麗なタッチで描かれていることが,がん治療というものの真実を知るいい資料になっていると感じるからである。

 内田春菊は我慢しない。言いたいことは言うし,やりたいことはやる。それ故に医者との軋轢もあったりするが,そのおかげで信頼できる医師とはしっかりしたコミュニケーションが取れており,家族(子息)からサポートも篤い。科学を無視した耳障りがいいだけの暴論がSNSに蔓延る昨今,客観的な科学の知識に基づき,自分の意志の表明を躊躇なく行うことの重要性を知らしめる本書は,がんを含めた病とともに生きていくワシら中高年にとっては,良い資料となるに違いないのである。

メルセンヌ素数で”FLOPS vs. Memory Bandwidth”を体験する


 50番目のメルセンヌ素数が見つかった,つーか,素数であることの検証が済んだ,という記事に呼応して下記のようなTweetをした。

 これって,”FLOPS vs. Memory Bandwidth”に通じる話でもあるなと思ったので,ここでもう少し詳細にメモっておく。上記のTweetより少し手直ししたプログラムは下記の通り。

// Mersenne Prime Number Calculator by GNU MP
// Copyright (c) 2018 Tomonori Kouya
//
// Compile: cc mpz_mersennne.c -lgmp
//
#include <stdio.h>
#include <stdlib.h>

#include "gmp.h"

/* Mersenne Prime Number */
/* 2^p - 1

/* cf.) http://primes.utm.edu/mersenne/ */

int max_index = 50; // 2018-01-05
unsigned long known_mersenne_prime_p[] = {
	       2,        3,        5,        7,       13,       17,       19,       31,       61,       89, \
	     107,      127,      521,      607,     1279,     2203,     2281,     3217,     4253,     4423, \
	    9689,     9941,    11213,    19937,    21701,    23209,    44497,    86243,   110503,   132049, \
	  216091,   756839,   859433,  1257787,  1398269,  2976221,  3021377,  6972593, 13466917, 20996011, \
	24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609, 57885161, 74207281, 77232917 \
};

int main(void)
{
	mpz_t a;
	int index;

	mpz_init(a);

	// input
	fprintf(stderr, "2^p[index] - 1 = ? ");
	fprintf(stderr, "index(1 - %d) = ", max_index);
	scanf("%d", &index);
	if((index > max_index) || (index <= 0))
	{
		fprintf(stderr, "ERROR: index = %d is illegal!\n", index);
		return EXIT_FAILURE;
	}

	index--;
	printf("p[%d] = %ld\n", index + 1, known_mersenne_prime_p[index]);

	// a := 2^p - 1
	mpz_ui_pow_ui(a, 2UL, known_mersenne_prime_p[index]);
	//gmp_printf("2^%ld     = %Zd\n", known_mersenne_prime_p[index], a);
	mpz_sub_ui(a, a, 1UL);
	gmp_printf("2^%ld - 1 = %Zd\n", known_mersenne_prime_p[index], a);

	mpz_clear(a);

	return 0;
}

 Tweetに挙げたのは\(2^p\)(mpz_ui_pow_ui関数で計算)と\(2^{p – 1}\)(mpz_sub_ui関数で計算)の両方をファイルに書き出す奴だったので,後者のみ書き出すようにするとほぼ半分の時間で済む。

 10進数テキストで書き出すと大体45MBぐらいのファイルになる訳だが,ネット越しだとダウンロードする方が時間がかかる。100BASE-TXの環境でもイーブン。大体,計算そのものにかかる時間よりファイルに書き出す時間の方が問題で,更に言うと,メインメモリ内部のデータ移動の法に時間が費やされている。128MBぐらいのL3キャッシュがあれば,FLOPS(この場合は整数演算だからMIPSだけど)上げるよりもずっと高速に計算終了している筈で,さらに言えば,ファイルに書き出しせずにオンメモリで済むならもっと高速。つまりCPU内部の演算効率よりメモリ帯域(memory bandwidth)の方がよっぽど問題,というお話になるわけで。

 という自分用メモ。

[2018-01-26(Fri) 追記] 「多倍長数値計算入門(仮)」執筆開始につき,上記のCプログラムをJuliaスクリプトで書き直してみた。なーるほど,これは使いやすいや。

#  Mersenne Prime Number Calculator by GNU MP
#  Copyright (c) 2018 Tomonori Kouya
# 
#  Run: julia mersennne.jl 
# 

# Mersenne Prime Number
# 2^p - 1

# cf.) http:# primes.utm.edu/mersenne/

max_index = 50; #  2018-01-05
known_mersenne_prime_p  = [
	       2,        3,        5,        7,       13,       17,       19,       31,       61,       89,
	     107,      127,      521,      607,     1279,     2203,     2281,     3217,     4253,     4423,
	    9689,     9941,    11213,    19937,    21701,    23209,    44497,    86243,   110503,   132049,
	  216091,   756839,   859433,  1257787,  1398269,  2976221,  3021377,  6972593, 13466917, 20996011,
	24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609, 57885161, 74207281, 77232917
];

#  input
print("2^p[index] - 1 = ? ");
print("index(1 - $max_index) = ");
index = parse(Int, chomp(readline(STDIN)));
if (index > max_index) || (index <= 0)
	print("ERROR: index = $index is illegal!\n");
	return EXIT_FAILURE;
end

println("p[$index] = ", known_mersenne_prime_p[index]);

#  a := 2^p - 1
a = BigInt(2)^known_mersenne_prime_p[index] - 1;
println("2^", known_mersenne_prime_p[index], " - 1 = $a");