KotlinのDouble型

はじめに

本記事ではKotlinのDouble型の仕様に関して確認したことをまとめます.

等価演算子

import kotlin.Double.Companion.NaN

fun main() {
    println(NaN == NaN) // false
    println(NaN != NaN) // true
    println(NaN as Any == NaN) // true
    println(NaN as Any != NaN) // false

    println(0.0 == -0.0) // true
    println(0.0 as Any == -0.0) // false
}

Kotlinの等価演算子は両辺が静的にDouble型であるときはIEEE 754に従います. そのため,NaN == NaNfalseとなります. また,NaN != NaNtrueとなります.

しかし,NaN as Any == NaNは左辺がAny型であるため,equalsメソッドによってtrueとなります. また,NaN as Any != NaNは左辺がAny型であるため,falseとなります.

同様に,0.0 == -0.0IEEE 754に従ってtrueとなりますが,0.0 as Any == -0.0equalsメソッドによってfalseとなります.

関係演算子

import kotlin.Double.Companion.NEGATIVE_INFINITY as nInf
import kotlin.Double.Companion.POSITIVE_INFINITY as pInf
import kotlin.Double.Companion.NaN

fun main() {
    println(NaN < NaN) // false
    println(NaN > NaN) // false
    println(NaN < pInf) // false
    println(NaN > pInf) // false
    println(NaN < nInf) // false
    println(NaN > nInf) // false

    println(NaN <= NaN) // false
    println(NaN as Comparable<Double> <= NaN) // true

    println(-0.0 < 0.0) // false
    println(-0.0 as Comparable<Double> < 0.0) // true
}

Kotlinの関係演算子は両辺が静的にDouble型であるときはIEEE 754に従います. そのため,右辺または左辺がNaNであるときは,関係演算子の結果はfalseとなります. また,-0.0 < 0.0falseとなります. しかし,NaN as Comparable<Double> <= NaNは左辺がComparable<Double>型であるため,compareToメソッドを用いて判定され,結果はtrueとなります. 同様に,-0.0 as Comparable<Double> < 0.0は左辺がComparable<Double>型であるため,compareToメソッドを用いて判定され,結果はtrueとなります.

四則演算

import kotlin.Double.Companion.NEGATIVE_INFINITY as nInf
import kotlin.Double.Companion.POSITIVE_INFINITY as pInf

fun main() {

    // plus
    println(pInf + 1.0) // Infinity
    println(nInf + 1.0) // -Infinity

    println(pInf + pInf) // Infinity
    println(pInf + nInf) // NaN
    println(nInf + nInf) // -Infinity

    // times
    println(pInf * pInf) // Infinity
    println(pInf * 2.0) // Infinity
    println(pInf * 0.0) // NaN
    println(pInf * -0.0) // NaN
    println(pInf * -2.0) // -Infinity
    println(pInf * nInf) // -Infinity

    println(nInf * pInf) // -Infinity
    println(nInf * 2.0) // -Infinity
    println(nInf * 0.0) // NaN
    println(nInf * -0.0) // NaN
    println(nInf * -2.0) // Infinity
    println(nInf * nInf) // Infinity

    // minus
    println(pInf - 1.0) // Infinity
    println(nInf - 1.0) // -Infinity

    println(pInf - pInf) // NaN
    println(pInf - nInf) // Infinity
    println(nInf - pInf) // -Infinity
    println(nInf - nInf) // NaN

    // div
    println(pInf / pInf) // NaN
    println(pInf / 2.0) // Infinity
    println(pInf / 0.0) // Infinity
    println(pInf / -0.0) // -Infinity
    println(pInf / -2.0) // -Infinity
    println(pInf / nInf) // NaN

    println(pInf / pInf) // NaN
    println(2.0 / pInf) // 0.0
    println(0.0 / pInf) // 0.0
    println(-0.0 / pInf) // -0.0
    println(-2.0 / pInf) // -0.0
    println(nInf / pInf) // NaN

    println(pInf / nInf) // NaN
    println(2.0 / nInf) // -0.0
    println(0.0 / nInf) // -0.0
    println(-0.0 / nInf) // 0.0
    println(-2.0 / nInf) // 0.0
    println(nInf / nInf) // NaN
}

KotlinのDouble型の演算でオーバーフローが発生する場合は演算の結果はInfinityとなります. アンダーフローが発生する場合は演算の結果は-Infinityとなります. また,Int型の除算と異なり,Double型の除算では割る数が0.0であっても例外が発生しません.

pow

import kotlin.math.pow
import kotlin.Double.Companion.NEGATIVE_INFINITY as nInf
import kotlin.Double.Companion.POSITIVE_INFINITY as pInf
import kotlin.Double.Companion.NaN

fun main() {
    // a^0.0 == 1.0
    println(0.0.pow(0.0)) // 1.0
    println(NaN.pow(0.0)) // 1.0
    println(pInf.pow(0.0)) // 1.0
    println(nInf.pow(0.0)) // 1.0

    // a^1.0 == a
    println(0.0.pow(1.0)) // 0.0
    println(NaN.pow(1.0)) // NaN
    println(pInf.pow(1.0)) // Infinity
    println(nInf.pow(1.0)) // -Infinity
    
    // a^Infinity
    println(pInf.pow(pInf)) // Infinity
    println(2.0.pow(pInf)) // Infinity
    println(1.0.pow(pInf)) // NaN
    println(0.5.pow(pInf)) // 0.0
    println(0.0.pow(pInf)) // 0.0
    println((-0.0).pow(pInf)) // 0.0
    println((-0.5).pow(pInf)) // 0.0
    println((-1.0).pow(pInf)) // NaN
    println((-2.0).pow(pInf)) // Infinity
    println(nInf.pow(pInf)) // Infinity

    // a^-Infinity
    println(pInf.pow(nInf)) // 0.0
    println(2.0.pow(nInf)) // 0.0
    println(1.0.pow(nInf)) // NaN
    println(0.5.pow(nInf)) // Infinity
    println(0.0.pow(nInf)) // Infinity
    println((-0.0).pow(nInf)) // Infinity
    println((-0.5).pow(nInf)) // Infinity
    println((-1.0).pow(nInf)) // NaN
    println((-2.0).pow(nInf)) // 0.0
    println(nInf.pow(nInf)) // 0.0

    // a^b (a < 0.0)
    println((-2.0).pow(2.0)) // 4.0
    println((-2.0).pow(2.5)) // NaN
    println((-2.0).pow(3.0)) // -8.0
}

NaN.pow(0.0)NaNではなく1.01.0.pow(pInf)および1.0.pow(nInf)1.0ではなくNaN(-2.0).pow(pInf)nInf.pow(pInf)および(-0.5).pow(nInf)NaNではなくInfinitypInf.pow(nInf)NaNではなく0.0となりました. これらはJavaMath.powの規定に合致するものですが私の直感に反するものだったため気をつけなければいけないと感じました.

log

import kotlin.math.log
import kotlin.Double.Companion.NEGATIVE_INFINITY as nInf
import kotlin.Double.Companion.POSITIVE_INFINITY as pInf

fun main() {
    println(log(pInf, pInf)) // NaN
    println(log(2.0, pInf)) // 0.0
    println(log(1.0, pInf)) // 0.0
    println(log(0.5, pInf)) // -0.0
    println(log(0.0, pInf)) // NaN
    println(log(-0.0, pInf)) // NaN

    println(log(pInf, 2.0)) // Infinity
    println(log(2.0, 2.0)) // 1.0
    println(log(1.0, 2.0)) // 0.0
    println(log(0.5, 2.0)) // -1.0
    println(log(0.0, 2.0)) // -Infinity
    println(log(-0.0, 2.0)) // -Infinity

    println(log(pInf, 0.5)) // -Infinity
    println(log(2.0, 0.5)) // -1.0
    println(log(1.0, 0.5)) // -0.0
    println(log(0.5, 0.5)) // 1.0
    println(log(0.0, 0.5)) // Infinity
    println(log(-0.0, 0.5)) // Infinity
}

Kotlinのlogに関して,特に気になる結果はありませんでした.

リファレンス