[Scala] scalaのBigDecimalをjava.text.DecimalFormatで変換するとずれることがある

なんかまだ合点がいってないのだが、細かい桁計算が必要でscalaのBigDecimalを使っていて、それを文字列で打ち出す必要が出た際に、javaのDecimalFormatを使ったら出力文字列が合致しなかった。

val a = "4.95"
val b = "0.000000000000000001"

val bigA = BigDecimal(a)
val bigB = BigDecimal(b)

val bigC = bigA+bigB

bigC.toString() // シンプルにtoStringにしたほうが正しい文字列になった

val fm = new java.text.DecimalFormat("0.###################")

println(fm.format(bigA))
println(fm.format(bigB))
println(fm.format(bigC)) // bigA+bigB に文字列一致しない

val fm2 = new java.text.DecimalFormat("0.0000000000000000000")

println(fm2.format(bigA))
println(fm2.format(bigB))
println(fm2.format(bigC)) // bigA+bigB に文字列一致しない

REPL出力

a: String = 4.95
b: String = 0.000000000000000001

biga: scala.math.BigDecimal = 4.95
bigb: scala.math.BigDecimal = 1E-18

bigc: scala.math.BigDecimal = 4.950000000000000001

res0: String = 4.950000000000000001
// シンプルにtoStringにしたほうが正しい文字列になった

fm: java.text.DecimalFormat = java.text.DecimalFormat@674dc

4.95
0.000000000000000001
4.95  // bigA+bigB に文字列一致しない

fm2: java.text.DecimalFormat = java.text.DecimalFormat@674dc

4.9500000000000000000
0.0000000000000000010
4.9500000000000000000  // bigA+bigB に文字列一致しない

ネットで調べて scalaのBigDecimalとjavaのBigDecimalがちょっと違うよ(MathContext.UNLIMITED設定関係)って話は出てきたが、DecimalFormatが違うって話はよくわからんかった。

[Apache poi]そろそろXSSFがデフォルトでよいんじゃないかな

賛否はあるもののお客さんからはExcel等でドキュメントを要求されることが多いことも事実。なのでApache poi(Apacheで出しているMS Excel.Word等MS文書をJavaで操作するライブラリ)などでドキュメント自動生成みたいなところを結局最近やっているのだが、Excel操作ライブラリはXSSF系APIとHSSF系APIに分かれている。

HSSFは拡張子xlsの旧形式、XSSFは拡張子xlsxの今の形式用のAPI。Excelも長いのでAPIが2系統に分かれているのは仕方ないと思うものの、そろそろXSSFがデフォルトでよいんじゃないかなぁ。

Workbook wb = WorkbookFactory.create("file.xslx")
↓
XSSFWorkbook wb = (XSSFWorkbook)WorkbookFactory.create("file.xslx")
または
XSSFWorkbook wb = new XSSFWorkbook("file.xslx")

IDEのコンテキストメニューでAPIがさくっと出ないし、ネットの情報でもHSSFの情報と混じって小一時間悩むし。

[sbt]あまり複雑なことを考えずに機能を追加する

正直まだsbtは理解できていない。まぁ依存性管理ツールはmakeにしろMSbuildにせよ複雑すぎて中をきちんと使いこなすとかできてなかったし。出来合スクリプトで困らんし。

なので専門家からするとまずいのかもしれないが、とりあえず既存のビルドの動きに何かを付け足すくらいの話向け。

  1. ちょこっと作業をまとめたバッチを作りたいだけなのよ→コマンドを使う
    https://www.scala-sbt.org/1.x/docs/ja/Howto-Sequence-using-Commands.html
build.sbt

commands += Command.command("batchtest") { state =>
  "testOnly somepackage.ValidationSpec > list.txt" ::
  "testOnly somepackage.ExecSpec >> list.txt" ::
    state
}
// commandsの使い方はよいがここでリダイレクトは使えないな。。。

2. タスクを作りたいのよ、タスクの前後にラベルを出したいのよ→ 単純なタスク作成
https://qiita.com/mtn81/items/ce482ed16a19f770cc68

3. sbtから外部コマンドを呼びたいのよ→ shell起動
https://www.scala-sbt.org/release/docs/Process.html

lazy val batch = taskKey[Unit]("外部バッチ")
batch in Test := {"powershell.exe ls " !}

4. 依存関係は悩まずにタスクからタスクを呼びたいのよ→ runTask, runInputTask
https://stackoverflow.com/questions/32142055/how-to-call-sbt-inputtask-dynamically

build.sbt

lazy val batch2 = taskKey[Unit]("ドキュメント生成呼び")
batch2 in Test := (doc in Test).value

lazy val batch3 = taskKey[Unit]("テスト1つだけ呼び")
batch3 in Test := (testOnly in Test).toTask(" somepackage.ValidationSpec").value
(↑ toTask(" の部分のスペースが重要)

5. 依存関係は悩まずにタスクでタスクを連続実行させたいのよ→Def.sequential
https://www.scala-sbt.org/1.x/docs/ja/Howto-Sequential-Task.html

build.sbt

lazy val batch4 = taskKey[Unit]("タスク連続")
batch4 in Test := {
  Def.sequential(
    Def.task {
      "powershell.exe date" !
    } ,
    Def.task {
      (testOnly in Test).toTask(" somepackage.ValidationSpec").value
  ).value
}

6. プロジェクトにREPL代わりに書いたAppコードをsbtから呼びたいのよ→runMain

Hello.scala

package tools

object Hello extends App {
   println("Hello")
}
build.sbt

lazy val batch5 = taskKey[Unit]("内部ツール呼び出し")
batch5 in Test := (runMain in Test).toTask(" tools.Hello").value
(↑ toTask(" の部分のスペースが重要)

[ScalaTest] sbt内でタイムスタンプが使いたい

ログの書き出しなどでsbtで書き出しファイルにタイムスタンプを付けたいとか。

build.sbt

testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest,"-fWDT", s"log${java.time.LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))}.txt" )

調べたものの結局は使わなかったのだが。。。sbtはまだ勘所がよくつかめぬ。

[Scala,ScalaTest]テスト実行内でテスト名を使う

ScalaTestでテストケースの中で、テストの名称を使う。

多用はしないだろうが、テストツール内でログを自力でレポート形式に荒加工する場合などに使えるかも。

SampleSpec.scala

class SampleSpec extends FreeSpec {
 "第一テスト" - {
  "第一項" in {
    val a = 1+1
    println(s"エビデンス:$a")
    assert(a == 2)
  }
 }
}

import org.scalatest.{FreeSpec, fixture}

class SampleSpec extends fixture.FreeSpec with fixture.TestDataFixture {
 "第一テスト" - {
  "第一項" in { td => {
      println(s"テスト「${td.scopes.mkString("/")}/${td.text}」)
      val a = 1+1
      println(s"エビデンス:$a")
      assert(a == 2)
     }
  }
 }
}

IntelliJでのnodejs-expressのdockerリモートデバッグ設定

開発環境前提(セキュリティ関係はなし状態),サーバー側は docker-composeとnodemonで実行

サーバー側のdocker-compose.yml内の nodemon 行にinspect項を追加。port 9229を通す

app:
・・・
  ports:
    - "0.0.0.0:9000:9000"
    - "0.0.0.0:9229:9229"
  command: sh -c "・・・ nodemon --inspect=0.0.0.0:9229 ・・・"

ローカルのIntelliJ側は Run/Debug Configuration で Attach to Node.js/Chrome を選んでデバッグ実行

つながらないときは サーバー側に
Debugger listening on ws://0.0.0.0:9229/b4f8874b-e433-429c-8e61-1xxxxx などのログが出ているので websocket がつながるか別のツールで確認する(wscatなど)

何も考えずにJoda-Time (Java, Scala)

sdt.

libraryDependencies += "joda-time" % "joda-time" % "2.9.9"

meven

<dependency>
  <groupId>joda-time</groupId>
  <artifactId>joda-time</artifactId>
  <version>2.9.9</version>
</dependency>

今または指定日時

val date = DateTime.now()
val date = new Date(2001,1,1,12,30,0)

足す、引く

val date = date.plusDays(1)
val date = date.minusYear(1)

書く

val str = date.toString()
val str = date.toString("yyyy/MM/dd HH:mm:ss")
//エスケープは ' で囲む 
val str = date.toString("yyyy-MM-dd'T'HH:mm:ss")
val date = DateTime.parse(str,DateTimeFormat.forPattern("yyyyMMdd"))