オミクロン株が若干不穏な気配ですが、今年は久しぶりに北海道に帰省しました。でも次の帰省はいつになるやら……。COVID-19の動向次第ですね。
今までは帰省時期に飛行機に乗るとほぼ必ずインフルエンザをもらっていたのですが、今年は風邪一つ引きませんでした。道行く人が全員マスクを着ける、というのは本当に伝染病予防に効くんですね。マスクの効果を疑っていたわけではないですけど、思った以上の効果が出ていて驚いています。
Windows電卓は浮動小数点数を扱えます。10^9999から10^-9999という非常に大きい数から、小さい数まで扱えます。
単精度浮動小数点数の最小値は10^-38(正規化数)もしくは10^-45程度、倍精度浮動小数点数でも最小値は10^-308(正規化数)もしくは10^-324(非正規化数)程度ですから、IEEE 754準拠の浮動小数点数型ではなさそうです。
Windows電卓で1 - 1.e-204 - 1を計算した結果
そのためか少し変わった桁落ちの挙動を示します。同じ程度の大きさの数同士の演算(例: 1.e-9999 + 1.e-9999 = 2.e-9999)であれば桁落ちしませんが、大きい数と小さい数を演算(例: 1 - 1.e-204 - 1 = -9.807971461541689e-149)すると激しく桁落ちします。
実はWindows 10の電卓のソースコードはGitHubに公開されていて(GitHubのプロジェクトへのリンク)、誰でも見ることができます。MITライセンスで、2019年に公開されたそうです。Windowsの標準アプリがオープンソースになっているなんて、調べるまで知りませんでした。
利用方法は簡単でGit cloneしたあと、Visual Studioでsrcディレクトリの下にあるCalculator.slnを開くだけです。私のノートPCが非力なせいか、ビルドは非常に遅いですね……。
デバッグする際は、設定を「マネージド+ネイティブ」にしておかないと、ネイティブコードを実行している部分のデバッグが全くできず、ブレークポイントなどが無視されるので注意が必要です。
コードのありかとデバッグ方法がわかったところで、Windows電卓のコードを調べます。桁落ちはどこで発生しているでしょうか?
先ほど示した1 - 1.e-204 - 1という演算の場合、CalcManagerプロジェクトのRatPack/rat.cppのaddrat() 関数にてtrimit() を呼んでいる箇所で桁落ちが発生します。
//calculator/src/CalcManager/Ratpack/ratpak.h
typedef uint32_t MANTTYPE;
...
//-----------------------------------------------------------------------------
//
// NUMBER type is a representation of a generic sized generic radix number
//
//-----------------------------------------------------------------------------
#pragma warning(push)
#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
typedef struct _number
{
int32_t sign; // The sign of the mantissa, +1, or -1
int32_t cdigit; // The number of digits, or what passes for digits in the
// radix being used.
int32_t exp; // The offset of digits from the radix point
// (decimal point in radix 10)
MANTTYPE mant[];
// This is actually allocated as a continuation of the
// NUMBER structure.
} NUMBER, *PNUMBER, **PPNUMBER;
#pragma warning(pop)
//-----------------------------------------------------------------------------
//
// RAT type is a representation radix on 2 NUMBER types.
// pp/pq, where pp and pq are pointers to integral NUMBER types.
//
//-----------------------------------------------------------------------------
typedef struct _rat
{
PNUMBER pp;
PNUMBER pq;
} RAT, *PRAT;
//calculator/src/CalcManager/Ratpack/rat.cpp
void addrat(_Inout_ PRAT* pa, _In_ PRAT b, int32_t precision)
{
PNUMBER bot = nullptr;
if (equnum((*pa)->pq, b->pq))
{
// Very special case, q's match.,
// make sure signs are involved in the calculation
// we have to do this since the optimization here is only
// working with the top half of the rationals.
(*pa)->pp->sign *= (*pa)->pq->sign;
(*pa)->pq->sign = 1;
b->pp->sign *= b->pq->sign;
b->pq->sign = 1;
addnum(&((*pa)->pp), b->pp, BASEX);
}
else
{
// Usual case q's aren't the same.
//★bを変更できないので見づらい書き方になっているが、通分して加算している
//★pa = pa->pp / pa->pq = A / B, b = b->pp / b->pq = C / Dとおくと
DUPNUM(bot, (*pa)->pq); //★bot = B
mulnumx(&bot, b->pq); //★bot = BD
mulnumx(&((*pa)->pp), b->pq); //★pa = AD / B
mulnumx(&((*pa)->pq), b->pp); //★pa = AD / BC
addnum(&((*pa)->pp), (*pa)->pq, BASEX); //★pa = (AD + BC) / BC
destroynum((*pa)->pq);
(*pa)->pq = bot; //★pa = (AD + BC) / BD
trimit(pa, precision); //★★この呼び出しで桁落ち★★
// Get rid of negative zeros here.
(*pa)->pp->sign *= (*pa)->pq->sign;
(*pa)->pq->sign = 1;
}
#ifdef ADDGCD
gcdrat(pa);
#endif
}
Windows電卓は内部ではRAT型(struct rat型)で数値を保持しています。RATは分子ppと分母pqの2つのNUMBER型で構成された有理数を表す型です。
有理数の加算は小学校で習った通りで、通分して足します。大きな数+小さな数で通分すると、非常に仮数部(構造体のmant、mantissa: 仮数の意味)が長くなってしまいます。例えば1 + 1.e-9999だと仮数部が9511要素もあるint32_tの配列になります。trimit() は一定以上の仮数を切り落とす役目を果たします。
加算の桁落ちを削除したWindows電卓で1 - 1.e-204 - 1を計算した結果
桁落ちを防止したければtrimit() の呼び出しを削れば良いです。しかし不思議なことに完全にtrimit() を削除するとWindows電卓が起動しなくなります。
桁落ちを防止すると起動しなくなる理由は、Windows電卓はなぜか起動時にCCalcEngineのコンストラクタにて10^100を計算しているからです。
Windows電卓はx^yをx^y = exp(y * ln(x)) で求めているので、logの実装を見ましょう。ソースコードはexp.cppの _lograt() です。見たところ、十分に小さい項までテイラー展開を続けるアルゴリズムのようです。
//calculator/src/CalcManager/Ratpack/exp.cpp
void _lograt(PRAT* px, int32_t precision)
{
CREATETAYLOR();
createrat(thisterm);
// sub one from x
(*px)->pq->sign *= -1;
addnum(&((*px)->pp), (*px)->pq, BASEX);
(*px)->pq->sign *= -1;
DUPRAT(pret, *px);
DUPRAT(thisterm, *px);
n2 = i32tonum(1L, BASEX);
(*px)->pp->sign *= -1;
do
{
NEXTTERM(*px, MULNUM(n2) INC(n2) DIVNUM(n2), precision);
TRIMTOP(*px, precision);
} while (!SMALL_ENOUGH_RAT(thisterm, precision));
DESTROYTAYLOR();
}
桁落ちを一切なくすとループが終わらなくなってしまいます。変数thistermが0にならないとループを抜けないところを見ると、ループ条件のSMALL_ENOUGH_RATがバグっているような気もするんですが……、これ以上の深追いはやめておきます。
他にも √ の計算も非常に遅くなります。パフォーマンスと実用性のバランスからtrimit() は必須といえるでしょう。
結論から先に書いておくと、高速道路合流の速度問題が解決されていたので、そのメモです。
問題の説明の記事は、プロパイロット2.0の実現で浮かび上がった、高速道路の制限速度問題【岩貞るみこの人道車医】 - レスポンス(Response.jp)、などいろいろ出ているので、詳細はお任せするとして。
簡単に言うと、SAEレベル3以上、つまり機械が人の代わりに運転するタイプの自動運転では、法規上の最高速度に問題があります。普段の道路でも最高速度60km/hで走っている人はあまりいなくて、それも問題なんですけど、そこはさておき。当初、問題として取り上げられていたのが、高速道路への合流でした。
最高速度は道路交通法施行令という法律で定められていて、高速道路の加速車線の制限速度は一般道同様に60km/hでした。ご存じのとおり、高速道路の本線は100km/hなので、法律を守って運転すると、次のようになります。
追突の危険性があるのと、本線で急加速せざるを得ない、ギクシャクした運転になってしまいます。
これは昔から存在していた法律上のおかしな点なんですが、人間が運転する場合はあえて法律(=加速度車線の最高速度制限)を無視し、取り締まり側も厳密なことは言わず見逃す、というゆるい運用で問題を避けてきました。しかし自動運転車まで法律を無視する?それは本当に正しいのか??と問題が再燃したわけです。
引き続き運用でごまかすのも辛いでしょうし、この問題はどうやって直すのかな?と思っていたんですが、どうやら法律の方を直したみたいです。
第四章の二 高速自動車国道等における自動車の交通方法等の特例 (最高速度) 第二十七条 最高速度のうち、自動車が高速自動車国道の本線車道又はこれに接する加速車線若しくは減速車線を通行する場合の最高速度は、次の各号に掲げる自動車の区分に従い、それぞれ当該各号に定めるとおりとする。 一 次に掲げる自動車 百キロメートル毎時 ★★↑60km/hから100km/hに変わった★★ イ 大型自動車(三輪のもの並びに牽引するための構造及び装置を有し、かつ、牽引されるための構造及び装置を有する車両を牽引するものを除く。)のうち専ら人を運搬する構造のもの ロ 中型自動車(三輪のもの並びに牽引するための構造及び装置を有し、かつ、牽引されるための構造及び装置を有する車両を牽引するものを除く。)のうち、専ら人を運搬する構造のもの又は車両総重量が八千キログラム未満、最大積載重量が五千キログラム未満及び乗車定員が十人以下のもの ハ 準中型自動車(三輪のもの並びに牽引するための構造及び装置を有し、かつ、牽引されるための構造及び装置を有する車両を牽引するものを除く。) ニ 普通自動車(三輪のもの並びに牽引するための構造及び装置を有し、かつ、牽引されるための構造及び装置を有する車両を牽引するものを除く。) ホ 大型自動二輪車 ヘ 普通自動二輪車 二 前号イからヘまでに掲げる自動車以外の自動車 八十キロメートル毎時
自動運転の実現に向けて、継続して警察庁にて話し合いが持たれている(自動運転・自動走行 各種有識者会議等 - 警察庁)ので、今後も法律改正されていくことでしょう。
目次: 車
正月寒いなか放置しすぎたせいか車のバッテリーが死にました。テスターで電圧測ると2.3Vで室内灯すら点きません。またバッテリー交換コースかなー。
メモにあるだけでも2007年11月17日、2013年3月20日、2016年7月24日、2020年7月28日にバッテリー交換しています。次で5回目です。
車に乗る頻度が激減した理由は明確です。東京はあらゆる場所が「車で来るな!」とおっしゃるからです。それでも車で行くと
のどれかです。行く気がしませんのよ〜……。
デスクトップPCの部品のいくつかは昔のPCから引き継いで使っています。なかでもATX電源は交換を怠りがちですが、いざ壊れると起動しなくなるだけでなく、巻き添えでCPUやマザーボードまで故障する可能性もあって故障が怖い部品です。トラブルに遭う前に、予防的に交換します。
買ったのはCoolerMaster V650 GOLD V2(MPY-650V-AFBAG-JP, 650W)です。ヨドバシで14,000円くらいでした。ちらっと他店の値段を見たら11,000円くらいでした。ヨドバシはPCパーツがやや高いのかも?
今まではCoolerMaster SilentPro M600(RS-600-AMBA-D3, 600W)を使っていました。Core2 Quadマシンの時代(2009年〜)から使っていましたから、ほぼ付けっぱなしで13年使ったようです。CoolerMasterの電源は優秀ですね。これからも応援してます。
< | 2021 | > | ||||
<< | < | 12 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | - |
合計:
本日: