レタスのかわをぜんぶむく

ぜんぶむきます

abs(int)のコーナーケース

与えられたある整数の絶対値を取得するような
一見シンプルなメソッドにもコーナーケースが存在する。
言語によって挙動が異なるので注意する必要がある。

期待される振る舞い

絶対値を取得するメソッドなので直感的にはこのように動いて欲しい。
{} $$ abs(x) = \left\{ \begin{array}{} x & ( x > 0) \\ 0 & ( x = 0) \\ -x & ( x < 0) \\ \end{array} \right. $$

言語によって実装は異なるため実際には下記。 {} $$ abs(x) = \left\{ \begin{array}{} x & ( x > 0) \\ 0 & ( x = 0) \\ -x & ( x < 0) \\ ? & (x = 型で表現できる最小値) \\ \end{array} \right. $$

原因

符号付数値表現 - Wikipedia

符号付整数値の内部表現が2の補数となっているため
表現できる幅が負の方向に1だけ広い。

例えば8bit符号付き整数型の場合
最大値は 01111111 127
最小値は 10000000 -128
最小値-128の絶対値である128は8bitで表現できる範囲を超えてしまう。

言語ごとの挙動の違い

1. 最小値をそのまま返却する (C++, Java)

#include <climits>
#include <cstdlib>
#include <iostream>
 
using namespace std;  
 
int main(void) {
    cout << INT_MIN << endl;
    cout << abs(INT_MIN) << endl;
}
import java.util.*;
 
public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println(String.valueOf(Integer.MIN_VALUE));
        System.out.println(String.valueOf(Math.abs(Integer.MIN_VALUE)));
    }
}
結果
-2147483648
-2147483648

負数そのまま返しとるやんけ!!!

実際問題、値として最小値が入ってくるケースはごくごく稀なものの
内部的にabsを利用している、みたいな場合に発生すると辛そう。
というか正の値を返して欲しい。

Javaでの内部実装はこちら。

    // Math.java内の実装
    public static int abs(int a) {
        return (a < 0) ? -a : a;
    }

そりゃそうなるかという感じ。

2. 例外を発生させる (C#)

using System;
 
public class IntMin {
    public static void Main(){
        Console.WriteLine(Math.Abs(Int32.MinValue));
    }
}
結果
Unhandled Exception:
System.OverflowException: Negating the minimum value of a twos complement number is invalid.
  at System.Math.AbsHelper (System.Int32 value) [0x00015] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.Math.Abs (System.Int32 value) [0x00009] in <8f2c484307284b51944a1a13a14c0266>:0 
  at IntMin.Main () [0x00010] in /workspace/Main.cs:6 
[ERROR] FATAL UNHANDLED EXCEPTION: System.OverflowException: Negating the minimum value of a twos complement number is invalid.
  at System.Math.AbsHelper (System.Int32 value) [0x00015] in <8f2c484307284b51944a1a13a14c0266>:0 
  at System.Math.Abs (System.Int32 value) [0x00009] in <8f2c484307284b51944a1a13a14c0266>:0 
  at IntMin.Main () [0x00010] in /workspace/Main.cs:6 

桁あふれなども含めて例外出して欲しい派なので
静的型付け言語において、個人的に望ましい動作はこっち。

3. 多倍長整数として返却する (Python)

任意精度演算 - Wikipedia

# coding: utf-8
import sys
 
print sys.maxint
print -sys.maxint - 1
print abs(-sys.maxint - 1)
結果
9223372036854775807
-9223372036854775808
9223372036854775808

表現できる範囲を超えたら内部的に広げてしまって返す。
内部でどうなっているのか、あまり意識せずに多倍長整数を扱えるのは自然だし便利。
調べた限りだとRubyもそんな感じ。

そもそも

    static Random rnd = new Random();

    static int random(int n) {
        return Math.abs(rnd.nextInt()) % n;
    }

書籍Effective Javaのある項目で、上記コードにはいくつか問題が
あってそれはどこでしょう?という記述に絡めて紹介されていた。

rnd.nextInt()は極めて稀にInteger.MIN_VALUEを返すので
極めて稀に負数が返却されるよって話で、うわ怖ってなって
他言語だとどうなのだろうと思ったのが調べたきっかけ。

確かにリファレンスには注意が書いてあるけど、absなんてどの言語にもあるし
雰囲気で使えちゃうので、知らずに実世界でこのバグ踏んでたらとても辛そう。

引き続き慎重にやっていきましょう。

100万円上限でKyashリアルカードの再発行案内が来た

久しぶりに技術以外の記事。題名通り。

100万円って書いたけど14ヶ月弱メインカードとして使った結果なので
残念ながら日頃からすごい額の散財をしている訳ではない。(したい)

結論から

  • Kyashリアルカードにはいくつか制限がある
    • 1日の利用限度額5万円
    • 1ヶ月での利用限度額12万円
    • カード自体の利用限度額100万円
  • カード自体の利用限度額に達すると使えなくなるが再発行で対応
    • 自分の場合は90万使った時点でメールが来た
      • メールから再発行依頼が可能で一週間目処で届く
      • 届いた後にその旨返信するとそのタイミングでカードの切替処理が行われる
      • そのためいきなり使えなくなることはなさそう(その前にメール等で気づけば)

再発行の流れ

f:id:uskey:20190218190114p:plain:w500

こんな案内メールが来た。
リンクを踏むとtayoriという外部サービスのメールフォームに飛ばされて
そこで住所やらなにやらを入力すると再発行手続きは終わり。
(飛んだ先がkyash.tayori.comから始まるURLだったので、本物…?となった)

新しいリアルカードが届くのは一週間前後かかるとのこと。
新しいカード受領後に別メールに返信すると、新しい方がアクティブになるとのことなので
それまでは変わらず手元のリアルカードを使い続けられる。

これまで

Kyashを使い始めたのは2018年1月頃からで、最初は仲間内での割り勘などに利用していた。
他にもpaymoというサービスがあって、その時のメンバーに合わせて使い分けていたのだけど
Kyashが2018年6月に2%還元を開始した後はとりあえずネットでの支払いはほぼ全てKyashを通すようにした。

運良くリアルカードが初期に手に入ったため、飲み会での立て替えや普段の買い物などにも利用した結果
時々は月の上限額ギリギリになって、キャッシュバックがおいしい感じになった。

f:id:uskey:20190218190733p:plain:w300
(おいしい様子)

2%還元キャンペーン以降は周囲の人も積極的にKyashを使うようになったので
現金しか使えないようなお店でも、後でKyashで回収みたいな事ができて
物理通貨をやりとりする煩わしさから解放されてかなり楽になった。

Kyash残高が残っているときは優先的にそちらから利用されるのも透過的でかなり使い勝手がいい。
Amazonだったり、近所の薬局やスーパーでKyashを利用するだけで勝手に残高から利用されるので
その体験に慣れてからはKyashに残高がいくら残っているか気にしなくなった。

アカウント凍結問題

あんまり手放しで称賛しまくるのもフェアじゃない(感じがする)ので
一度アカウントが凍結されてしまったことがあったのでその件も載せる。

理由はクレジットカードを変更した際に、名前のスペルが違っていたため(多分)。
片一方は母音を重ねたスペルで、もう一方は一つだけみたいなスペルの違い。

変更後に無事アカウントが凍結されてしまった。

こちらもエンジニアなので「あ〜なるほどですね〜」という気持ちになりながら
想定される理由とかをずらっと書いてサポートにメールを送って祈った。


(凍結してあわあわする様子)

やっぱり決済系サービスは分散させとかないとダメか…
と傷心していたところ翌日には解除されてファンになる私(ちょろい)

当時Kyash自体まだすごい大きい組織ではないと記憶していたけど
休日で解決まで24時間かからないのは、中の人すげーなと普通に感動してしまった。

リスク管理の記事とかで『ピンチをチャンスに』とか書いてあると
いや普通にピンチはピンチだろ正気か?とか思ってたけど
この件の対応である種の安心感とか信頼を持ってしまったので不覚にも腹落ちしてしまった。

運営のみなさんへ

Kyashいつもとても便利に使っています!ありがとうございます!
39円送る時に出る隠しアニメーションもかなりイケてるので
自分の携わるプロダクトのどこかでそういう遊び心を入れたいなって思ってます。

今後もガンガン使っていきたいので引き続きよろしくおねがいします。

Qiita/Qiita:Teamの投稿を一括置換するツールをGoで作った

経緯

社で運用している複数のQiita:Teamを統合することになった。
統合にあたり、投稿やプロジェクト自体は問題なく移行できるが記事内のURL参照は自動で置換されないことがわかった。

そのままだと他の投稿への参照が切れてしまうので修正が必要だが膨大な量の投稿を手動でやるのは面倒、というか無理。

作った

github.com

Qiita, Qiita:Teamのトークン、置換前文字列、置換後文字列、所属Qiita:Teamドメインを設定すると
これまで自分が書いた全ての投稿の本文内の文字列を置換する。

試したけどできなかったこと

管理者アカウントでの全ユーザー全投稿の一括置換。作業工数的には一番これが理想だったけど無理そう。
もしかするとAPIドキュメントで見落としがあるかもしれないけど、見つけられなかった。

実装メモ

  • github管理しているプロジェクトは、$GOPATH/src/github.com/{user}/{repo}として置いた方がよい
    • 更新かけるたびに毎回 go getするハメになって何かおかしいと気づく
      • これまでプロジェクト内で他のパッケージを作ったことがなかった
  • Goをすごい便利でモダンなBetter Cと思ってたのでAPIアクセス大変そうに思ってたけどそうでもなかった
    • リクエスト/レスポンスの定義はAPIリファレンスのサンプルオブジェクトをjson-to-goに貼って終わり
      • 便利!!!
  • type hogehoge []structで定義した型の要素でハマった。具体的にはトップレベルが配列のjson
    • hogehogeをパースした後別メソッドにhogehoge[0]を渡したいときどうすれば…
      • 配列を内包するオブジェクトを作ってそれをUnmarshalした
      • 言語仕様をふわっとしか理解してない弊害が
  • Qiita API v2の PATCH /api/v2/items/:item_idでハマる
    • エラーメッセージは特になくて、"forbidden"みたいなログしか出ない
    • 記事によってtagがある無しでハマってた…?更新対象をtitleとbodyだけにすると解決。

オチ

統合後、Qiita記事同士のリンクはいい感じに対応されていて必要なかった。無念。
それはそうだよね…

どこかで使うことがあれば使おうと思う。多分ない。

はじめてOSSにコントリビュートした

概要

github.com

上記のリポジトリに出したPullRequestが2つマージされた。
仕事でも使っていたライブラリなのでうれしい!

行った変更

  • テスト設定の変更
  • 依存するライブラリの更新

テスト設定の変更

この変更を入れる前はテストコードの一部がシステムのロケーションに依存していて
いわゆるen_USの環境でないシステム上だとmvn testに失敗するケースのある状態になっていた。

原因はxls(xlsx)ファイルのセル内にある日付形式の文字列 aa/bb/cc を読み込む際に
mm/dd/yyとするか、dd/mm/yyと解釈するかの基準が明示的に指定されていないためだった。

最初にチェックアウトしてまずテストを試した時に、日付文字列の読み込みテストで失敗してしまっていたので
多分ロケーション周りだろうと思って、Macの言語設定やタイムゾーンを色々試して通るようになったのを確認後に
mvn testJVMのパラメータを渡すにはどうすれば良いんだっけという点を調査して対応した。

知らなかったけど、maven-surefire-pluginというものがあるらしい。

https://github.com/takezoe/xlsbeans/pull/12

理由に気がつくと確かに理屈はわかるんだけど、自国以外で動作するようなコードであって
かつ言語設定が絡むようなものを実装したことがなかったので、色々調べながら対応していて勉強になった。

依存するライブラリの更新

こっちが最初解決しようとしていた問題。いわゆるJar地獄問題で
依存するライブラリが別バージョンの同じライブラリを参照していると
不幸な問題が発生することがまれによくある

今回のケースでは内部的に利用しているjavassistのバージョンに問題があった

$ mvn dependency:tree
~略~
[INFO] com.github.takezoe:xlsbeans:jar:1.2.6
[INFO] +- net.sourceforge.jexcelapi:jxl:jar:2.6.12:compile
[INFO] |  \- log4j:log4j:jar:1.2.14:compile
[INFO] +- org.apache.poi:poi:jar:3.6:compile
[INFO] |  \- commons-logging:commons-logging:jar:1.1:runtime
[INFO] |     +- logkit:logkit:jar:1.0.1:runtime
[INFO] |     +- avalon-framework:avalon-framework:jar:4.1.3:runtime
[INFO] |     \- javax.servlet:servlet-api:jar:2.3:runtime
[INFO] +- org.apache.poi:poi-ooxml:jar:3.6:compile
[INFO] |  +- org.apache.poi:poi-ooxml-schemas:jar:3.6:compile
[INFO] |  |  +- org.apache.xmlbeans:xmlbeans:jar:2.3.0:compile
[INFO] |  |  |  \- stax:stax-api:jar:1.0.1:compile
[INFO] |  |  \- org.apache.geronimo.specs:geronimo-stax-api_1.0_spec:jar:1.0:compile
[INFO] |  \- dom4j:dom4j:jar:1.6.1:compile
[INFO] |     \- xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] +- ognl:ognl:jar:3.0.2:compile
[INFO] |  \- javassist:javassist:jar:3.11.0.GA:compile <=ココ
[INFO] \- junit:junit:jar:3.8.1:test

詳しくはPullRequestに記載しているけど、あるバージョン以下のjavassistの機能(?)をJDK8で利用すると
java.io.IOException: invalid constant type: 18というエラーを吐いて死ぬという問題がある。

https://github.com/takezoe/xlsbeans/pull/10

もちろんプロジェクト依存関係の設定側で特定バージョンの使用を強制させるワークアラウンドもあるけど
依存しているライブラリのAPIの互換性に問題がなければ、より色々なバージョンで動作した方がよいでしょう
ということで、古いjavassistを参照しないよう、ognlのバージョンを上げた差分を作ってPullRequestを出した。

オチ

両方調べて解決するのに4時間くらいかかったんだけど実際の差分はpom.xmlの数行だけ。
次回はソース部分の修正で貢献できるようにがんばります。

f:id:uskey:20190211163856p:plain:w400

Mac環境 再構築用メモ

いつも使ってるマシンが急に死んだりしたときリカバリ手順が無いと詰んでしまうので整理。
両方のマシンが生きていて、移行するならツールなりなんなりを使ったほうが良さそう。

優先度をつけて一時的な環境として割り切って使うならフルで実施しなくても大丈夫だった。

普段からやっておくこと

やってないと飛んだ時に詰むので注意する。

  • 鍵ファイルやhome/.*に配置されているファイルのバックアップ
  • ネットワーク設定の把握()

導入するもの

意外といろいろ使ってたので列挙

Chrome

www.google.com

初期ブラウザは普段遣いブラウザを入れるためにある。
ログインするとブックマーク、拡張機能やパスワード管理が使えるので
人によってはこれだけで一通りの環境が戻ってくるかもしれない。

入力関連

  • karabiner-elements

pqrs.org

普段JISキーボード使いなので左CapsLockを潰してControlにするくらいしかしてない。
USキーボードを使うことになったときは、最下段を大胆に変えて対応する。
記号キー周りは使ってるうちに思い出すのでメイン部分はほぼ変えない。

f:id:uskey:20190120081723p:plain:w500
US→嘘JISへのバインディングの様子

www.google.co.jp

システム環境設定>キーボード>入力ソースからデフォルトの設定を外す。

コミュニケーションツール

  • Slack

slack.com

複数ワークスペースに入っている人は個別にドメインを入れて参加するより
メールアドレスから所属済みワークスペースを検索した方が早い。

https://slack.com/get-started#find

  • LINE

開発ツール

  • Git Kraken

www.gitkraken.com

Gitクライアント。イカがかわいいのと体感でSourceTreeより軽い。

  • SDKMAN

sdkman.io

JVM系は一通りこれで入りそう。
メイン言語JavaなのでJavaとGradleなどを導入。

www.jetbrains.com

  • Docker

docs.docker.com

  • Postman

www.getpostman.com

  • iTerm2

www.iterm2.com

その他

  • Inkdrop

inkdrop.app

ノートアプリ。
日々の記録とかいろいろなメモは一旦放り込んで後からタグ付けして整理している。便利。

思い出したら追記する。