JavaとScalaにおける浮動小数点数の大小比較

浮動小数点数の数値の種類

種類 Java Scala
正の無限大 Double.POSITIVE_INFINITY Double.NegativeInfinity
正の数 1.0 1.0
正のゼロ 0.0 0.0
負のゼロ -0.0 -0.0
負の数 -1.0 -1.0
負の無限大 Double.NEGATIVE_INFINITY Double.PositiveInfinity
NaN Double.NaN Double.NaN

ゼロが正負の2種類あるのとNaNがあるために数値の大小比較がややこしいです。

2つのゼロ

0.0-0.0はどちらもゼロです。

-0.00.0 / -1.0 などの計算結果として現れます。

比較演算子ではJavaでもScalaでも2つのゼロは同じ値として振る舞います。

0.0 == -0.0 // true
0.0 > -0.0 // false
0.0 >= -0.0 // false
0.0 != -0.0 // false

JavaDouble.compareメソッドでは0.0-0.0は違う値として振る舞います。

Double.compare(0.0, -0.0) // 1
Double.compare(-0.0, 0.0) // -1

ScalaのOrderingのcompareメソッドでも同様です。

Ordering.Double.IeeeOrdering.compare(0.0, -0.0) // 1
Ordering.Double.IeeeOrdering.compare(-0.0, 0.0) // -1

Ordering.Double.TotalOrdering.compare(0.0, -0.0) // 1
Ordering.Double.TotalOrdering.compare(-0.0, 0.0) // -1

NaN

NaNは非数(not-a-number)というものです。 0.0 / 0.0 などの計算結果として現れます。

!=以外の比較演算子ではJavaでもScalaでもNaNが含まれるとfalseを返します。NaN同士の==も常にfalseです。!=は常にtrueを返します。NaN同士の !=trueです。

Double.NaN >  Double.NaN // false
Double.NaN == Double.NaN // false
Double.NaN >= Double.NaN // false
0.0 > Double.NaN // false
0.0 < Double.NaN // false

0.0 != Double.NaN // true
Double.NaN != Double.NaN // true

JavaDouble.compareメソッドではNaNは正の無限大よりも大きな値として振る舞います。

Double.compare(Double.NaN, 0.0)                      // 1
Double.compare(Double.NaN, Double.POSITIVE_INFINITY) // 1
Double.compare(0.0,                      Double.NaN) // -1
Double.compare(Double.POSITIVE_INFINITY, Double.NaN) // -1

ScalaのOrderingのcompareメソッドでも同様です。

Ordering.Double.IeeeOrdering.compare(Double.NaN, 0.0)                     // 1
Ordering.Double.IeeeOrdering.compare(Double.NaN, Double.PositiveInfinity) // 1
Ordering.Double.IeeeOrdering.compare(0.0,                     Double.NaN) // -1
Ordering.Double.IeeeOrdering.compare(Double.PositiveInfinity, Double.NaN) // -1

Ordering.Double.TotalOrdering.compare(Double.NaN, 0.0)                     // 1
Ordering.Double.TotalOrdering.compare(Double.NaN, Double.PositiveInfinity) // 1
Ordering.Double.TotalOrdering.compare(0.0,                     Double.NaN) // -1
Ordering.Double.TotalOrdering.compare(Double.PositiveInfinity, Double.NaN) // -1

ソート

Java

ソートするときは、正負のゼロは区別されます。NaNは最後に並びます。

var arr = new Double[]{Double.NaN, -1.0, 0.0, -0.0, 1.0};
java.util.Arrays.sort(arr);
// => -1.0, -0.0, 0.0, 1.0, NaN

Scala

ScalaでソートしてもJavaと同じソート結果になります。

Scala浮動小数点数のソートをするには

import Ordering.Double.TotalOrdering

または

import Ordering.Double.IeeeOrdering

どちらかをインポートする必要があります。

インポートしないと

warning: object DeprecatedDoubleOrdering in object Ordering is deprecated (since 2.13.0): There are multiple ways to order Doubles (Ordering.Double.TotalOrdering, Ordering.Double.IeeeOrdering). Specify one by using a local import, assigning an implicit val, or passing it explicitly. See the documentation for details.

という警告が表示されます。

どちらをインポートしてもソートするだけであれば同じです。

ソートではなく minmaxメソッドでは2つのOrderingのどちらをインポートするかによって結果が変わってきます。

val seq = List(Double.NaN, -1.0, 0.0, -0.0, 1.0)

{
  import Ordering.Double.IeeeOrdering
  println(seq.sorted) // List(-1.0, -0.0, 0.0, 1.0, NaN) // Javaと同じ結果
  println(seq.min) // NaN <- minは上と結果が違う
  println(seq.max) // NaN <- maxは上と同じ
}

{
  import Ordering.Double.TotalOrdering
  println(seq.sorted) // List(-1.0, -0.0, 0.0, 1.0, NaN) // Javaと同じ結果
  println(seq.min) // -1.0
  println(seq.max) // NaN
}

sortedメソッドは内部ではOrderingのcompareメソッドで比較しています。 min, maxメソッドは内部ではOrderingのmin, maxメソッドで比較しています。

Scalaでの2つのOrdering

IeeeOrdering

IeeeOrdering.compareメソッドはJavaDouble.compareを呼び出しており、同じ結果になります。つまり0.0-0.0より大きく、NaNは正の無限大よりも大きいです。

IeeeOrderinggt, gteq, lt, lteq, equivメソッドはJavaの比較演算子と同じ結果になります。つまり0.0-0.0は同じ値で、NaNはなにと比較しても常にfalse!=は常にtrueになります。

IeeeOrderingmax, minメソッドはjava.lang.Math.max, java.lang.Math.minを呼び出しており、どちらかもしくは2つともNaNであれば、NaNを返します。

TotalOrdering

全順序というものです。浮動小数点数のすべての数を並べることができます。IeeeOrderingのようにNaNや正負のゼロの例外的な挙動がありません。

TotalOrdering.compareメソッドはJavaDouble.compareを呼び出しており、同じ結果になります。つまり0.0-0.0より大きく、NaNは正の無限大よりも大きいです。

TotalOrderinggt, gteq, lt, lteq, equivメソッドもJavaDouble.compareメソッドと同じ結果になります。つまり0.0-0.0より大きく、NaNは正の無限大よりも大きいです。

TotalOrderingmax, minメソッドは以下のような定義になっており、0.0-0.0より大きく、NaNは正の無限大よりも大きい扱いです。

def max(x: U, y: U): U = if (gteq(x, y)) x else y
def min(x: U, y: U): U = if (lteq(x, y)) x else y

以上。