各言語での「翌月1日」の求め方

C# で「翌月1日」の DateTime を作るのに、こんなコードを書いてしまいました。

DateTime today = DateTime.Today;
DateTime nextMonth =
    new DateTime(today.Year, today.Month + 1, 1);

結果、12月1日になったら new DateTime(2017, 13, 1) となってしまい、「System.ArgumentOutOfRangeException: Year、Month および Day パラメーターが表現できない DateTime を示しています。」 で落ちました。
適当なツールだったからよかったものの、本番のコードだったらと思うとぞっとします。
(日付のバグはテストで見つけにくいです…)


そこで、復習を兼ねて各言語ごとの「翌月1日の求め方」を確認してみました。
もっといい方法があるよ!って場合は教えてください\(^o^)/

C#の場合

AddMonths メソッドを使います。

DateTime today = DateTime.Today;
DateTime nextMonth =
    new DateTime(today.Year, today.Month, 1).AddMonths(1);

ちなみに、これを new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1) って書くのはアウトです。
ほとんど起こらないぐらい極めててまれな確率ですが、第一引数の評価時と、第二引数の評価時で年をまたいだら日付がおかしくなるというバグになります。
現在時刻を処理するときは、変数に代入しておいたほうが安全です。

Java の場合

Date and Time API を使うと便利です。

 LocalDateTime nextMonth = 
     LocalDateTime.now()
                  .truncatedTo(ChronoUnit.DAYS)
                  .withDayOfMonth(1)
                  .plusMonths(1);

ちなみに、年月までで切り捨てる処理を truncatedTo(ChronoUnit.MONTHS) って書いたら、 ''java.time.temporal.UnsupportedTemporalTypeException: Unit is too large to be used for truncation'' でした。

JavaScript の場合

C#Java と異なり、範囲外の値を入れると繰り越し・繰り下げしてくれます。
なので、単純に加算するだけでオーケー。

let now = new Date();
let nextMonth =
    new Date(now.getFullYear(), now.getMonth() + 1, 1);

一番わかりやすいですが、最初にも書いたように C#Java ではだめなので要注意ですね…。

main メソッドは private クラスに書いても動く?

static なメンバー・クラス*1は、以下のように "private" として宣言できます。
この時、"java Main$Internal" と打ってこの private クラス内の main メソッドを起動できるのでしょうか。

class Main{
    private static class Internal{
        public static void main(String[] args){
            System.out.println("Hello world!");
        }
    }
}

答え:できる

Java の main メソッドは、以下のすべての条件を満たしている必要があります。*2

  • public かつ static メソッドであること
  • 戻り値の宣言は void であること
  • メソッド名は main であること
  • 引数は1つであること
  • 引数で String の配列を受け取ること


メソッドに関しては制約がありますが、クラスに関してはロードできれば OK とのこと。
なので、private なクラスでも問題なく起動できるようです。


そもそも、javap してみた限り、コンパイルした段階で private クラスであるという情報は消えるていので、判断しようがなさそうです…。

*1:「static 内部クラス」と書かれていることもありますが、これは間違い。言語仕様の 8.1.3 で「static ならば内部クラスではない」と明記されています。

*2:Java 仮想マシン仕様には、5.2 仮想マシンの開始 で「その *public クラス* の メソッド void main(String[]) を起動する」とりますが、これは誤訳か原文の間違いかと。

64bit 版の Windows に Java 1.1 をインストールする

誰得な気がしなくもないですが。


Java 1.1 は、いまでも Oracle Java Archive からダウンロードできます。
Java Archive Downloads - Java SE 1.1


しかし、これを 64bit 版の Windows にインストールしようとすると、インストーラの一部が 16bit アプリケーションなので、途中でエラーになります*1

                                                    • -

サポートされていない 16 ビット アプリケーション

                                                    • -

64 ビット バージョンの Windows での非互換性のため、プログラムまたは機能である
"\??\C:\USERS\TEST\APPDATA\LOCAL\TEMP\~EXB0000\setup.exe" を開始または実行できません。ソフトウェア製造元に問い合わせて 64 ビット Windows 互換バージョンが利用可能であるかどうか確認してください。

回避策

ReactOS プロジェクトの成果物を使えば、上記のエラーを解消してインストールができます。
(大抵の古い 16bit インストーラーは、これを使ってインストールができます)


まずは前段階として、Java のインストールファイルを入手します。

  1. jdk-1_1_8_010-windows-i586.exe を実行
  2. 前述のエラーダイアログが表示されたら、OKボタンを押さずに "%TEMP%\~EXB0000" *2を開く
  3. フォルダ内のファイルを、すべて適当なフォルダーにコピー
  4. エラーダイアログを閉じる

続いて、ツールをダウンロードして実行します。

  1. ReactOSInstallSheild Engine 3.0 16bit ランチャー解凍ツール (Is3Engine.zip) をダウンロード
  2. "Is3Engine.zip" を展開
  3. 展開すると "setup32.exe" があるので、コピーした Java インストーラと同じフォルダへ移動
  4. "setup32.exe" を実行

これで昔懐かしいインストーラが起動するので、あとは指示に従ってインストールすれば完了です。

ところで動くの?

「インストールできても、動くの?」と思うかもしれませんが、大丈夫です、動きます。
Windows の互換性すごい…。

D:\Java>C:\Program Files (x86)\Java\jdk1.1.8\bin\java.exe -version
java version "1.1.8"

D:\Java>:\Program Files (x86)\Java\jdk1.1.8\bin\java.exe Main
Hello world!


初期の Java に触れてみたいという方はお試しあれ。

*1:Java 自体は 32bit アプリケーションです。

*2:ファイル名を指定して実行ダイアログで、"%TEMP%\~EXB0000" と入力すれば開きます

Java API の規模がどれだけ大きくなったか気になったので、コード行数を確認してみました

Java API は、バージョンが上がるごとにどんどん大きくなっています。
でも、「大きくなった」といってもピンとこなかったので、目安としてコード行数を確認してみました。

Java API のコード行数

調べた結果がこちら。

バージョン コード行数
jdk1.1.8 63,418
jdk1.2.2 201,317 (+137,899)
jdk1.3.1_20 227,997 (+26,680)
jdk1.4.2_19 316,289 (+88,292)
jdk1.5.0_22 395,499 (+79,210)
jdk1.6.0_45 445,139 (+49,640)
jdk1.7.0_80 498,834 (+53,695)
jdk1.8.0_121 561,036 (+62,202)
  • 調査対象: JDK の各バージョンに付属している src.zip
  • 対象パッケージ: java.*, javax.* (com.sun.* などは除外)
  • 調査ツール: StepCounter
  • 補足: 行数に空行やコメントなどは含まない、いわゆる実効行数


jdk1.1 と比べて、jdk1.8 は約9倍の規模になっています。jdk1.2 と比べても約2.8倍です。
すごい増えてますね…。

各バージョンで追加されたパッケージ

細かいメソッドの追加とかもありますが、パッケージがごそっと追加されたことでコードが増えた分が大きいようです。ざっと確認してみたところ、以下のパッケージがありました。

  • jdk1.3
    • javax/naming
  • jdk1.4
  • jdk1.6
    • javax/annotation
    • javax/lang/model
    • javax/script
    • javax/tools
  • jdk1.7
  • jdk1.8

C# で、変数に代入したかどうかで値が変わってしまう不思議なコード

この前の Java で NaN や Infinity を int にキャストしたときの値C# でやってみたら、面白い挙動になったのでメモ。

C# で NaN や Infinity を int にキャストしたときの値

C# で NaN や Infinity を int にキャストしてみたところ、結果はいずれも "0" になりました。

Console.WriteLine(unchecked((int)double.NaN));       // 0
Console.WriteLine(unchecked((int)double.Infinity));  // 0


ところが、NaN や Infinity を一度ローカル変数に格納してからキャストしてみたところ、今度は "-2147483648" になりました。
NaN でやねん。

double value = double.NaN;
Console.WriteLine ((int) value);       // -2147483648

double infinity = double.Infinity;
Console.WriteLine ((int) infinity);    // -2147483648

言語仕様は?

言語仕様によれば、このような NaN から int へのキャストをした結果は「未指定値 (unspecified value)」*1
つまり、特に値の指定はないので 0 になろうが -2147483648 になろうが、言語仕様通りということ。

6.2.1 明示的な数値変換の一覧表

  • float または double から整数型への変換では、処理は、変換が行われるオーバーフロー チェックのコンテキスト (7.6.12 を参照) に依存します。
    • (中略)
    • unchecked コンテキストでは、変換は常に成功し、続けて以下の処理が行われます。
      • オペランドの値が非数 (NaN) または無限の場合は、変換先の型の未指定値が変換の結果になります。
      • それ以外の場合、変換前のオペランドはゼロに向かって最も近い整数値に丸められます。この整数値が変換先の型の範囲内である場合は、この値が変換の結果になります。
      • それ以外の場合は、変換先の型の未指定値が変換の結果になります。

でも、「変数に代入するかどうか」でキャストの結果が変わるのは不思議です。
よくわからなかったので、Stack Overflow に質問を投げてみました。

すると、返ってきた回答は「手元の環境で再現しない」。しかも、複数の人からこれが vote up されていました。あれ…?

疑問

なぜ、0 になったり -2147483648 になったりするのでしょうか。
なぜ、変数に代入するかしないかで値が変わってしまうのでしょうか。
なぜ、ほかの人の環境では再現しなかったのでしょうか。


言語仕様通りと言えばそれまでです。
でも、この動きにはその先があるはずです。


さらに調べてみました。

変数に格納するかどうかで値が変わった理由

Stack Overflow のコメントの中で「コンパイラなに使ってる?」というのがありました。
これがヒントになりました。
自分が試していたのは Visual Studio 2013 *2 と古いバージョンだったためです。
そこで、Visual Studio 2015 の Roslyn コンパイラーで再度実行してみたところ、(int)double.NaN の値が 0 ではなく -2147483648 になりました。

Console.WriteLine(unchecked((int)double.NaN));  // -2147483648


つまり、(int)double.NaN と書いたときはコンパイラがこれを評価して結果を定数としてバイナリに埋め込んでいるということ。
リバースアセンブルしてILを確認したところ、たしかにそうなっていました。

  // Visual Studio 2013
  IL_0000:  ldc.i4.0
  IL_0001:  call       void [mscorlib]System.Console::WriteLine(int32)
  
  // Visual Studio 2015
  IL_0000:  ldc.i4     0x80000000
  IL_0005:  call       void [mscorlib]System.Console::WriteLine(int32)


たぶん、以前のコンパイラーは NaN を int にキャストしたときの評価結果を決め打ちで 0 にしているのではないかと思います*3
一方、変数に格納してからキャストした場合は実行時に評価されるので、その時の実行環境に依存しています(これについては後述)。
この評価するタイミングの差によって、値が変わってしまうようです。

-2147483648 はどこから来た値?

なぜ、NaN を変数に格納して int にキャストすると -2147483648 になるのでしょうか。
これは、x86 (SSE2) の仕様のようです。


該当部分を逆アセンブルして確認したところ、SSE2 の cvttsd2si 命令*4が使われていました。

            Console.WriteLine((int)value);
00B73544  movsd       xmm0,mmword ptr [ebp-0Ch]  
00B73549  cvttsd2si   ecx,xmm0  
00B7354D  call        6DC26C0C  

この命令は、もし入力が NaN だった場合は結果は整数不定*5 0x80000000 になるそうです。
不定値という名前なので紛らわしいですが、0x80000000 固定のようです。

E.4.2.2. SSE、SSE2SSE3数値命令でNaNオペランドまたはNaN結果を含む演算の結果
以下の表(E-1.〜E-10.)は、NaNの入力値(またはNaNの結果を生じさせるNaNでない入力値)に対する、SSE、SSE2SSE3の応答を示している。単精度 QNaN 不定値は0xffc00000であり、倍精度 QNaN 不定値は0xfff8000000000000であり、整数不定値は0x80000000である。この値は浮動小数点値ではないが、浮動小数点値から整数への変換命令の結果になりうる。

IA-32 インテル® アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル


0x80000000 は 10進数で -2147483648。
よって、NaN を変数に格納して int にキャストした場合に -2147483648 が表示されていたのは、この命令の仕様によるもののようです。

CPUが変われば値も変わるのか?

SSE2 命令の仕様で -2147483648 になるのなら、仕様が異なる ARM などの CPU で動かした場合は値も変わるのでしょうか。


そこで、Raspberry Pi 上に mono を入れて*6コンパイル&実行してみました。
すると、結果はいずれの場合も 0 になりました。やはり、NaN を int にキャストしたときの値は CPU に依存するようです。

Console.WriteLine(unchecked((int)double.NaN));   // 0 (on ARM)

double value = double.NaN;
Console.WriteLine ((int) value);                 // 0 (on ARM)

まとめ

  • C# で NaN を int にキャストした場合の結果は未指定。
    • 定数式の場合、コンパイル時に評価されるため、結果はコンパイラに依存する。
    • 変数の場合、実行時に評価されるため、結果は CPU に依存する。

プログラムはCPUの上で動いている。当たり前といえば当たり前ですが、これが見えてくるのは面白いですね!

*1:初期値(initial value) の 0 ではないです。

*2:.NET Framework 4.6 がインストールされている環境の "C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe" と同じものです。再現実験する際は、これをご利用ください。

*3:Roslyn は、実際にその場でコードを走らせて、その結果を定数として格納したのではないかと思います。

*4:「切り捨てを使用して、xmm/m64の1つの倍精度浮動小数点値をr32の1つの符号付きダブルワード整数に変換する。」 IA-32 インテル® アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル より

*5:「整数不定値は、x87 FPUが整数値を操作するときに戻すことがある、特殊な値である。」IA-32 インテル® アーキテクチャ・ソフトウェア・デベロッパーズ・マニュアル より

*6:.NET Core で実験しようとしたのですが、まだ ARM は未対応なので諦めました。

Java で、NaN や Infinity を int にキャストしたときの値

NaN や Infinity を int にキャストなんて普通はしないと思うのですが、やってみたらこうなりました。

  • (int)Double.NaN == 0
  • (int)Double.POSITIVE_INFINITY == Integer.MAX_VALUE
  • (int)Double.NEGATIVE_INIFINITY == Integer.MIN_VALUE
public static void main(String[] args) throws Exception {
    // NaN: 0
    System.out.println(
        Double.NaN + ": " + (int) Double.NaN);
    
    // Infinity: 2147483647
    System.out.println(
        Double.POSITIVE_INFINITY + ": " + (int) Double.POSITIVE_INFINITY);
    
    // -Infinity: -2147483648
    System.out.println(
        Double.NEGATIVE_INFINITY + ": " + (int) Double.NEGATIVE_INFINITY);
}

この挙動について、Java 言語仕様 第3版には以下のように書かれていました。

5.1.3 プリミティブ型のナローイング変換
浮動小数点から整数型 T へのナローイング変換は、以下の2ステップにて行われる:

  1. 第1のステップでは、浮動小数点数を以下に従って、T が long である場合は long へと、T が byte, short, char, int である場合は int へと変換する:
    • 浮動小数点数が NaN である場合、変換の第1ステップの結果は、int あるいは long のゼロとなる。
      そうでなく、浮動小数点が無限大でない場合、その浮動小数点をIEEE754規格によるゼロへの丸めモードによってゼロに向かった整数値 V へと丸める。その後、以下の2つの場合に分かれる:
      • T が long であり、この整数値が long で表現できる場合、第1ステップの結果は long 値の V となる。
      • そうでなく、この整数値が int で表現できる場合、第1ステップの結果は int 値の V となる。
    • そうでない場合は、以下のいずれかが真となるはずである:
      • 値が小さすぎる(大きな絶対値となる負の値や、負の無限大)場合、第1ステップの結果は int あるいは long 型で表現可能な最小値となる。
      • 値が大きすぎる(大きな絶対値となる性の値や、正の無限大)場合、第1ステップの結果は int あるいは long 型で表現可能な最大値となる。
  2. 第2のステップでは:
    • T が int あるいは long である場合、変換結果は第1ステップの結果となる。
    • T が byte, char, shrot である場合、変換結果は第1ステップの結果をT型にナローイング変換した結果となる。

たしかに、このような変換なら納得です。

2016年にやってきたことを振り返ってみました

もう間もなく2016年が終わります。
その前に、今年一年間でやってきたことを振り返ってみたいと思います。

JJUG CCC 2016 - Timetable (非公式)

ふと思いついて、飲み会から帰ってきて2時間ぐらいの突貫作業でリリースした代物。
その後、スピーカーの方の情報を表示できるようにしたりとちょこちょことバージョンアップしました。


Spring のときは公式のスマートフォン用ページがなかったこともあって結構な人に使っていただけたみたいです。
懇親会のときに「あ!それ私も使っていました!便利ですね!」って言ってただけたり、改良案をいただけたのはすごいうれしかったです!


Fall のときは、公式ページがスマートフォン対応したのでいらなかったかなと思っていたのですが、「時間別」のタイムテーブルがあったほうがいいかなということで再リリース。
こちらは、アクセス解析によれば約120人に使っていただけました!


次の JJUG CCC のときにどうするかはわかりませんが、需要がありそうだなと思ったらまた公開する予定です。
そのときは、またぜひご利用ください。

勉強会スライドbot - @tech_slideshare

公開された勉強会のスライドをひたすらツイートしていくbotです。
アルゴリズムは単純ながら、個人的にはこれのおかげで情報収集にとても役立っています。
(思ったよりも流れてくる量が多いので、全部ではなく気になったのだけ見ています)


需要もありそうなので、これからも改良して行くつもりです。
あと、こうしてほしい!などのご意見募集中です!

AC Unit Test (Firefox版Chrome版)

AtCoder の問題ページから自動でユニットテストを生成するツールです。
自分自身がほしくて、今でも一番よく使っています。


当初はユーザ数がぜんぜん伸びなくて悲しい思いをしましたが、*1JJUGナイトセミナーのLT大会のときに飛び込みで宣伝をした効果か、今見たら Chrome 版のユーザ数がなんと17人に増えていました!
やっぱり使ってもらえるとうれしいなーというのと、宣伝しないと使ってもらえないんだなーというのを体感しました。

Copy text without selecting (Firefox版Chrome版)

範囲選択はもう不要!右クリックコピーだけでテキストがコピーできるようになるプラグインです!
(クリックした場所のDOMノードのテキストをコピーする、という仕組み)
Firefox版とChrome版あわせてユーザ数は500人ぐらい。54ヶ国語対応したおかげか結構な人に使っていただけています*2


アイディア一発勝負で作ってみたものの、最初は使いづらいなーと自分で思っていました。
ただ、表内のセルとかチケットのタイトルのように、タグの範囲が見た目どおりの場合に使うと案外便利ということにあとから気づき、自分の中での評価が変わったという不思議な経験をしました。


こちらは、宣伝すればもうちょいユーザ数を延ばせる余地がありそうです。

GitHub の Longest Streak

無事1年分の草を生やすことに成功しました!
(↓GitHub Original Streak アドオンを入れています)

ただ、内容は薄いです…。
上記のツール類を作っているときもあれば(草が濃いあたり)、AtCode の超簡単な問題を解いただけとか、READMEの誤字修正だけとかもありました。


毎日コードを書くと言うことの、個人的なメリット・デメリットは…。
メリットは、毎日何かしらコードを書くぞ!ってやってたおかげでツールを作るときの敷居が下がったということ。おかげでアウトプットが増えました。
デメリットは、寝る前にコードを書くようになったことで寝る時間がどんどん遅くなっていったこと。


メリットが大きいのでこのまま続けていきたいのですが、デメリットを何とかする必要がありそうです。
コードを朝起きてから書くなどして、解消していく必要があるなぁと考えています。
とはいえ、早寝早起きは何回も挑戦して失敗しているので、なかなか難しいです orz

今年は2冊しか読んでいません…。しかも簡単なやつです。
例年書いている「今年読んだ本のまとめ」を今年は書いていないのはこのためです。


インプットが減っているのは、この先の技術の土台が先細りになるまずい状況です…。
今の自分のがあるのも、過去に読んできた技術書から得た知識のおかげです。
それがないと言うことは数年後に技術不足に陥ることになるかな、と。


スマホのログを確認したところ、一日平均1時間以上だらだらと Twitter を見ている日が多々ありました。
この時間を減らして本を読む時間に当てることで、インプットを増やせそうかなと考えてます。

勉強会

登壇はしていないのですが、LTを何度か。
JJUG CCC Fall で「前回の面白かったです!今回はやらないんですか?」と言っていただけたのはとてもびっくりしました。

またそのうちLTでネタをぶっこむつもりなので、そのときは笑ってやってください m(_ _)m

まとめ

アウトプットを増やせて、個人的に成果の大きい年でした。
この勢いはこのまま来年も維持していければと思います。


最後になりましたが、いつも読んでくださる皆様、ツールを使ってくださる皆様、そして勉強会などで声をかけてくださった皆様、誠にありがとうございました。
これからも、ちょっとづつ何かアウトプットを出し続けていければともいますので、そのときはぜひひいきに。

それでは、2017年もよろしくお願いいたします。

*1:実は、AtCoder社長の chokudai さんにリツイートとしていただけたときにダウンロード数が伸びたのですが、自分のミスで使えなくなっていたという致命的なミスをやっていました…。それなければ、もっとユーザ数伸びていたのかなと後悔しています。

*2:必要なのが「コピー」という文言だけなので、ブラウザのメッセージリソースから集めるだけで対応できました。