50d4979531
- floorDiv/mod for unsigned types - floorDiv/mod for signed types - mod for floating point types - mod return type: same as divisor for integer types - Update JS API dump - Avoid triggering division overflow in tests due to K/N - Workaround failing test in JS-legacy
287 lines
10 KiB
Kotlin
287 lines
10 KiB
Kotlin
/*
|
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
|
*/
|
|
|
|
package test.unsigned
|
|
|
|
import kotlin.math.*
|
|
import kotlin.random.*
|
|
import kotlin.test.*
|
|
|
|
class ULongTest {
|
|
|
|
private fun identity(u: ULong): ULong =
|
|
(u.toLong() + 0).toULong()
|
|
|
|
val zero = 0uL
|
|
val one = 1uL
|
|
val max = ULong.MAX_VALUE
|
|
|
|
@Test
|
|
fun equality() {
|
|
|
|
fun testEqual(uv1: ULong, uv2: ULong) {
|
|
assertEquals(uv1, uv2, "Boxed values should be equal")
|
|
assertTrue(uv1.equals(uv2), "Boxed values should be equal: $uv1, $uv2")
|
|
assertTrue(uv1 == uv2, "Values should be equal: $uv1, $uv2")
|
|
assertEquals(uv1.hashCode(), uv2.hashCode())
|
|
assertEquals((uv1 as Any).hashCode(), (uv2 as Any).hashCode())
|
|
assertEquals(uv1.toString(), uv2.toString())
|
|
assertEquals((uv1 as Any).toString(), (uv2 as Any).toString())
|
|
}
|
|
|
|
testEqual(one, identity(one))
|
|
testEqual(max, identity(max))
|
|
|
|
fun testNotEqual(uv1: ULong, uv2: ULong) {
|
|
assertNotEquals(uv1, uv2, "Boxed values should be equal")
|
|
assertTrue(uv1 != uv2, "Values should be not equal: $uv1, $uv2")
|
|
assertNotEquals(uv1.toString(), uv2.toString())
|
|
assertNotEquals((uv1 as Any).toString(), (uv2 as Any).toString())
|
|
}
|
|
|
|
testNotEqual(one, zero)
|
|
testNotEqual(max, zero)
|
|
}
|
|
|
|
@Test
|
|
fun convertToString() {
|
|
fun testToString(expected: String, u: ULong) {
|
|
assertEquals(expected, u.toString())
|
|
assertEquals(expected, (u as Any).toString(), "Boxed toString")
|
|
assertEquals(expected, "$u", "String template")
|
|
}
|
|
|
|
repeat(100) {
|
|
val v = Random.nextLong() ushr 1
|
|
testToString(v.toString(), v.toULong())
|
|
}
|
|
|
|
repeat(100) {
|
|
val v = Random.nextLong(8446744073709551615L + 1)
|
|
testToString("1${v.toString().padStart(19, '0')}", (5000000000000000000.toULong() * 2.toULong() + v.toULong()))
|
|
}
|
|
|
|
testToString("18446744073709551615", ULong.MAX_VALUE)
|
|
}
|
|
|
|
@Test
|
|
fun operations() {
|
|
assertEquals(9223372036854775808u, Long.MAX_VALUE.toULong() + identity(1u))
|
|
assertEquals(11u, ULong.MAX_VALUE + identity(12u))
|
|
assertEquals(ULong.MAX_VALUE - 99u, 45u - identity(145u))
|
|
|
|
assertEquals(ULong.MAX_VALUE - 1u, Long.MAX_VALUE.toULong() * identity(2u))
|
|
assertEquals(9223372036854775805u, Long.MAX_VALUE.toULong() * identity(3u))
|
|
|
|
testMulDivRem(125u, 3u, 41u, 2u)
|
|
testMulDivRem(210u, 5u, 42u, 0u)
|
|
testMulDivRem(ULong.MAX_VALUE, 65536uL * 65536u, 4294967295u, 4294967295u)
|
|
testMulDivRem(ULong.MAX_VALUE - 1u, ULong.MAX_VALUE, 0u, ULong.MAX_VALUE - 1u)
|
|
testMulDivRem(ULong.MAX_VALUE, ULong.MAX_VALUE - 1u, 1u, 1u)
|
|
testMulDivRem(ULong.MAX_VALUE, Long.MAX_VALUE.toULong(), 2u, 1u)
|
|
}
|
|
|
|
|
|
private fun testMulDivRem(number: ULong, divisor: ULong, div: ULong, rem: ULong) {
|
|
assertEquals(div, number / divisor)
|
|
assertEquals(rem, number % divisor)
|
|
assertEquals(div, number.floorDiv(divisor))
|
|
assertEquals(rem, number.mod(divisor))
|
|
|
|
assertEquals(number, div * divisor + rem)
|
|
assertTrue(rem < divisor)
|
|
assertTrue(div < number)
|
|
}
|
|
|
|
@Test
|
|
fun divRem() = repeat(1000) {
|
|
val number = Random.nextULong()
|
|
val divisor = Random.nextULong(until = ULong.MAX_VALUE) + 1u
|
|
testMulDivRem(number, divisor, number / divisor, number % divisor)
|
|
}
|
|
|
|
@Test
|
|
fun comparisons() {
|
|
fun <T> compare(op1: Comparable<T>, op2: T) = op1.compareTo(op2)
|
|
|
|
fun testComparison(uv1: ULong, uv2: ULong, expected: Int) {
|
|
val desc = "${uv1.toString()}, ${uv2.toString()}"
|
|
assertEquals(expected, uv1.compareTo(uv2).sign, "compareTo: $desc")
|
|
assertEquals(expected, (uv1 as Comparable<ULong>).compareTo(uv2).sign, "Comparable.compareTo: $desc")
|
|
assertEquals(expected, compare(uv1, uv2).sign, "Generic compareTo: $desc")
|
|
|
|
assertEquals(expected < 0, uv1 < uv2)
|
|
assertEquals(expected <= 0, uv1 <= uv2)
|
|
assertEquals(expected > 0, uv1 > uv2)
|
|
assertEquals(expected >= 0, uv1 >= uv2)
|
|
}
|
|
|
|
fun testEquals(uv1: ULong, uv2: ULong) = testComparison(uv1, uv2, 0)
|
|
fun testCompare(uv1: ULong, uv2: ULong, expected12: Int) {
|
|
testComparison(uv1, uv2, expected12)
|
|
testComparison(uv2, uv1, -expected12)
|
|
}
|
|
|
|
testEquals(one, identity(one))
|
|
testEquals(max, identity(max))
|
|
|
|
testCompare(zero, one, -1)
|
|
testCompare(Long.MAX_VALUE.toULong(), zero, 1)
|
|
|
|
testCompare(zero, ULong.MAX_VALUE, -1)
|
|
testCompare((Long.MAX_VALUE).toULong() + one, ULong.MAX_VALUE, -1)
|
|
}
|
|
|
|
|
|
@Test
|
|
fun convertToFloat() {
|
|
fun testEquals(v1: Float, v2: ULong) = assertEquals(v1, v2.toFloat())
|
|
|
|
testEquals(0.0f, zero)
|
|
testEquals(1.0f, one)
|
|
|
|
testEquals(2.0f.pow(ULong.SIZE_BITS) - 1, max)
|
|
testEquals(2.0f * Long.MAX_VALUE + 1, max)
|
|
|
|
repeat(100) {
|
|
val long = Random.nextLong(from = 0, until = Long.MAX_VALUE)
|
|
testEquals(long.toFloat(), long.toULong())
|
|
}
|
|
|
|
repeat(100) {
|
|
val long = Random.nextLong(from = 0, until = Long.MAX_VALUE)
|
|
val float = Long.MAX_VALUE.toFloat() + long.toFloat() // We lose accuracy here, hence `eps` is used.
|
|
val ulong = Long.MAX_VALUE.toULong() + long.toULong()
|
|
|
|
// TODO: replace with ulp comparison when available on Float
|
|
val eps = 1e+13
|
|
assertTrue(abs(float - ulong.toFloat()) < eps)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun convertToDouble() {
|
|
fun testEquals(v1: Double, v2: ULong) = assertEquals(v1, v2.toDouble())
|
|
|
|
testEquals(0.0, zero)
|
|
testEquals(1.0, one)
|
|
|
|
testEquals(2.0.pow(ULong.SIZE_BITS) - 1, max)
|
|
testEquals(2.0 * Long.MAX_VALUE + 1, max)
|
|
|
|
repeat(100) {
|
|
val long = Random.nextLong(from = 0, until = Long.MAX_VALUE)
|
|
testEquals(long.toDouble(), long.toULong())
|
|
}
|
|
|
|
repeat(100) {
|
|
val long = Random.nextLong(from = 0, until = Long.MAX_VALUE)
|
|
val value = Long.MAX_VALUE.toULong() + long.toULong()
|
|
val expected = Long.MAX_VALUE.toDouble() + long.toDouble() // Should be accurate to one ulp
|
|
val actual = value.toDouble()
|
|
val diff = abs(expected - value.toDouble())
|
|
|
|
assertTrue(diff <= actual.ulp, "$actual should be within one ulp (${actual.ulp}) from the expected $expected")
|
|
}
|
|
|
|
fun testRounding(from: ULong, count: ULong) {
|
|
for (x in from..(from + count)) {
|
|
val double = x.toDouble()
|
|
val v = double.toULong()
|
|
val down = double.nextDown().toULong()
|
|
val up = double.nextUp().toULong()
|
|
|
|
assertTrue(down <= x && down <= v)
|
|
assertTrue(up >= x && up >= v)
|
|
|
|
if (v > x) {
|
|
assertTrue(v - x <= x - down, "Expected $x being closer to $v than to $down")
|
|
} else {
|
|
assertTrue(x - v <= up - x, "Expected $x being closer to $v than to $up")
|
|
}
|
|
}
|
|
}
|
|
|
|
testRounding(0u, 100u)
|
|
testRounding(Long.MAX_VALUE.toULong() - 520u, 100u)
|
|
testRounding(ULong.MAX_VALUE - 10000u, 10000u)
|
|
}
|
|
|
|
@Test
|
|
fun convertDoubleToULong() {
|
|
fun testEquals(v1: Double, v2: ULong) = assertEquals(v1.toULong(), v2)
|
|
|
|
testEquals(0.0, zero)
|
|
testEquals(-1.0, zero)
|
|
|
|
testEquals(-2_000_000_000_000.0, zero)
|
|
testEquals(-(2.0.pow(ULong.SIZE_BITS + 5)), zero)
|
|
testEquals(Double.MIN_VALUE, zero)
|
|
testEquals(Double.NEGATIVE_INFINITY, zero)
|
|
testEquals(Double.NaN, zero)
|
|
|
|
testEquals(1.0, one)
|
|
|
|
testEquals(2_000_000_000_000_000_000_000.0, max)
|
|
testEquals(2.0.pow(ULong.SIZE_BITS), max)
|
|
testEquals(2.0.pow(ULong.SIZE_BITS + 5), max)
|
|
testEquals(Double.MAX_VALUE, max)
|
|
testEquals(Double.POSITIVE_INFINITY, max)
|
|
|
|
repeat(100) {
|
|
val v = -Random.nextDouble(until = 2.0.pow(ULong.SIZE_BITS + 8))
|
|
testEquals(v, zero)
|
|
}
|
|
|
|
repeat(100) {
|
|
val v = Random.nextDouble(from = max.toDouble(), until = 2.0.pow(ULong.SIZE_BITS + 8))
|
|
testEquals(v, max)
|
|
}
|
|
|
|
repeat(100) {
|
|
val v = Random.nextDouble() * Long.MAX_VALUE
|
|
testEquals(v, v.toLong().toULong())
|
|
}
|
|
|
|
repeat(100) {
|
|
val d = 2.0.pow(63) * (1 + Random.nextDouble())
|
|
val expected = specialDoubleToULong(d)
|
|
val actual = d.toULong()
|
|
|
|
assertEquals(expected, actual, "Expected bit pattern: ${expected.toString(2)}, actual bit pattern: ${actual.toString(2)}")
|
|
}
|
|
|
|
fun testTrailingBits(v: Double, count: Int) {
|
|
val mask = (1uL shl count) - 1uL
|
|
assertEquals(0uL, v.toULong() and mask)
|
|
}
|
|
|
|
var withTrailingZeros = 2.0.pow(64)
|
|
repeat(10) {
|
|
withTrailingZeros = withTrailingZeros.nextDown()
|
|
testTrailingBits(withTrailingZeros, 11)
|
|
}
|
|
|
|
withTrailingZeros = 2.0.pow(63)
|
|
repeat(10) {
|
|
testTrailingBits(withTrailingZeros, 11)
|
|
withTrailingZeros = withTrailingZeros.nextUp()
|
|
}
|
|
|
|
repeat(100) {
|
|
val msb = Random.nextInt(53, 64)
|
|
val v = 2.0.pow(msb) * (1.0 + Random.nextDouble())
|
|
testTrailingBits(v, msb - 52)
|
|
}
|
|
}
|
|
|
|
/** Creates an ULong value directly from mantissa bits of Double that is in range [2^63, 2^64). */
|
|
private fun specialDoubleToULong(v: Double): ULong {
|
|
require(v >= 2.0.pow(63))
|
|
require(v < 2.0.pow(64))
|
|
val bits = v.toBits().toULong()
|
|
return (1uL shl 63) + ((bits and (1uL shl 52) - 1u) shl 11)
|
|
}
|
|
} |