blob: 1a05bf0502c4c8231217d00bca0f86ea2ea04bd3 [file]
/*
* Copyright 2010-2026 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.
*/
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@file:Suppress("PackageDirectoryMismatch")
package kotlin.js.internal.boxedLong
import withType
import kotlin.internal.InlineOnly
import kotlin.internal.UsedFromCompilerGeneratedCode
import kotlin.js.internal.longAsBigInt.LongAsBigIntApi
import kotlin.math.floor
import kotlin.reflect.js.internal.PrimitiveKClassImpl
/* Several algorithms come from "the JS Long paper":
* S. Doeraene and T. Schlatter,
* "64-bit Integer Division for the JavaScript Platform,"
* 33rd IEEE Symposium on Computer Arithmetic (ARITH), Fulda, Germany, 2026.
* https://arith2026.org/papers/64-bit%20Integer%20Division%20for%20the%20JavaScript%20Platform.pdf
*/
private const val TWO_PWR_32_DBL_ = 4294967296.0
private const val TWO_PWR_M32_DBL_ = 1.0 / TWO_PWR_32_DBL_
private const val TWO_PWR_63_DBL_ = 9223372036854775808.0
/**
* Marks the stdlib functions that implement the pre-BigInt Long boxing or rely on [Long] being implemented as a regular class
* with two [Int] fields.
*
* If you use a function annotated with this annotation, you assume that [Long] is implemented as a regular class with
* two [Int] fields. Don't do it unless you are sure that you also handle the BigInt-backed Long values.
*
* These declarations will need to be removed when we drop the ES5 target (KT-70480).
*/
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
@Retention(AnnotationRetention.BINARY)
internal annotation class BoxedLongApi
/* Several algorithms come from "the JS Long paper":
* S. Doeraene and T. Schlatter,
* "64-bit Integer Division for the JavaScript Platform,"
* 33rd IEEE Symposium on Computer Arithmetic (ARITH), Fulda, Germany, 2026.
* https://arith2026.org/papers/64-bit%20Integer%20Division%20for%20the%20JavaScript%20Platform.pdf
*/
/**
* @see kotlin.js.internal.longAsBigInt.toNumber
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.toNumber(): Double {
// JS Long paper, Section V.A.
return high.toDouble() * TWO_PWR_32_DBL_ + uintToDouble(low)
}
@BoxedLongApi
@InlineOnly
private inline fun fromUnsignedSafeDouble(x: Double): Long {
// JS Long paper, Section V.B.
return Long(jsToInt32(x), jsToInt32(x * TWO_PWR_M32_DBL_))
}
/**
* @see kotlin.js.internal.longAsBigInt.convertToByte
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.convertToByte(): Byte = low.toByte()
/**
* @see kotlin.js.internal.longAsBigInt.convertToChar
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.convertToChar(): Char = low.toChar()
/**
* @see kotlin.js.internal.longAsBigInt.convertToShort
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.convertToShort(): Short = low.toShort()
/**
* @see kotlin.js.internal.longAsBigInt.convertToInt
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.convertToInt(): Int = low
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun hashCode(l: Long) = l.low xor l.high
// toString(), see the JS Long paper, Section VIII
@InlineOnly
private inline fun jsToString(x: Double, base: Int): String = js("x.toString(base)")
/**
* @see kotlin.js.internal.longAsBigInt.toStringImpl
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.toStringImpl(radix: Int): String {
// JS Long paper, Algorithm 11
val low = this.low
val high = this.high
if ((low shr 31) == high) {
// -2^31 <= this < 2^31
return jsToString(low.toDouble(), radix)
} else if (((high xor (high shr 10)) and 0xffe00000.toInt()) == 0) {
// -2^53 <= this < 2^53
return jsToString(toNumber(), radix)
} else {
// |this| >= 2^53
if (high >= 0) {
return ulongToStringLarge(this, radix)
} else {
return "-" + ulongToStringLarge(negate(), radix)
}
}
}
/**
* @see kotlin.js.internal.longAsBigInt.negate
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.negate(): Long {
// manually inlined and simplified from 0L - this
val rlow = -low
val rhigh = -high - boolToInt(rlow != 0)
return Long(rlow, rhigh)
}
@BoxedLongApi
private fun Long.isNegative() = high < 0
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.equalsLong(other: Long) = bothZero(high xor other.high, low xor other.low)
@BoxedLongApi
@UsedFromCompilerGeneratedCode
private fun Long.lessThan(other: Long): Boolean {
if (high == other.high) {
return uintToDouble(low) < uintToDouble(other.low)
} else {
return high < other.high
}
}
@BoxedLongApi
@UsedFromCompilerGeneratedCode
private fun Long.greaterThan(other: Long): Boolean {
if (high == other.high) {
return uintToDouble(low) > uintToDouble(other.low)
} else {
return high > other.high
}
}
@BoxedLongApi
@UsedFromCompilerGeneratedCode
private fun Long.greaterThanOrEqual(other: Long): Boolean {
if (high == other.high) {
return uintToDouble(low) >= uintToDouble(other.low)
} else {
return high > other.high
}
}
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.compare(other: Long): Int {
if (high == other.high) {
return uintCompare(low, other.low)
} else {
return if (high < other.high) -1 else 1
}
}
/**
* @see kotlin.js.internal.longAsBigInt.add
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.add(other: Long): Long {
// JS Long paper, Algorithm 1
val rlow = low + other.low
val rhigh = high + other.high + boolToInt(uintToDouble(rlow) < uintToDouble(low))
return Long(rlow, rhigh)
}
/**
* @see kotlin.js.internal.longAsBigInt.subtract
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.subtract(other: Long): Long {
// JS Long paper, Algorithm 2
val rlow = low - other.low
val rhigh = high - other.high - boolToInt(uintToDouble(rlow) > uintToDouble(low))
return Long(rlow, rhigh)
}
/**
* @see kotlin.js.internal.longAsBigInt.multiply
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.multiply(other: Long): Long {
// JS Long paper, Algorithm 3
val alow = low
val blow = other.low
val a0 = alow and 0xffff
val a1 = alow ushr 16
val b0 = blow and 0xffff
val b1 = blow ushr 16
val a0b0 = a0 * b0
val a1b0 = a1 * b0
val a0b1 = a0 * b1
/* rlow = alow * blow, but we compute the above 3 subproducts for rhigh
* anyway, we reuse them to compute rlow too, trading a * for 2 +'s and 1 <<.
*/
val rlow = a0b0 + ((a1b0 + a0b1) shl 16)
val c1part = (a0b0 ushr 16) + a0b1
val rhigh = (
alow * other.high + high * blow + a1 * b1 + (c1part ushr 16) +
(((c1part and 0xffff) + a1b0) ushr 16)
)
return Long(rlow, rhigh)
}
/**
* @see kotlin.js.internal.longAsBigInt.divide
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.divide(other: Long): Long {
// JS Long paper, Section VI, Algorithm 7, specialized to get the quotient
val thisHigh = this.high
val otherHigh = other.high
val a = if (thisHigh < 0) this.negate() else this
val b = if (otherHigh < 0) other.negate() else other
val alo = a.low
val ahi = a.high
val blo = b.low
val bhi = b.high
/* Conveniently, when b = 0, we enter the first case, where the first thing
* we do is an int division by blo. If that throws an ArithmeticException,
* we will throw it as well. If it ignores it and returns 0, we'll also
* compute a 0 quotient, so we're in sync with the behavior of Int division.
*/
val q = if (bothZero(bhi, blo and 0xffe00000.toInt())) {
// b < 2^21, Algorithm 4
val quotHi = (ahi.toUInt() / blo.toUInt()).toInt()
val k = ahi - blo * quotHi
val quotLo = jsToInt32(toUnsignedNumber(alo, k) / blo.toDouble())
Long(quotLo, quotHi)
} else {
// 2^21 <= b <= 2^63, Algorithm 5
val aHat = toUnsignedNumber(alo, ahi)
val bHat = toUnsignedNumber(blo, bhi)
val qHat = fromUnsignedSafeDouble((aHat / bHat) + 0.00390625) // 2^(-8)
val rHat = a - b * qHat
if (rHat.isNegative()) qHat - 1L else qHat
}
return if ((thisHigh xor otherHigh) < 0) {
return q.negate()
} else {
return q
}
}
/**
* @see kotlin.js.internal.longAsBigInt.modulo
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.modulo(other: Long): Long {
// JS Long paper, Section VI, Algorithm 7, specialized to get the remainder
val thisHigh = this.high
val otherHigh = other.high
val a = if (thisHigh < 0) this.negate() else this
val b = if (otherHigh < 0) other.negate() else other
val alo = a.low
val ahi = a.high
val blo = b.low
val bhi = b.high
// See divide about division by 0
val r = if (bothZero(bhi, blo and 0xffe00000.toInt())) {
// b < 2^21, Algorithm 4
val k = (ahi.toUInt() % blo.toUInt()).toInt()
val quotLo = jsToInt32(toUnsignedNumber(alo, k) / blo.toDouble())
val remLo = alo - blo * quotLo
Long(remLo, 0)
} else {
// 2^21 <= b <= 2^63, Algorithm 5
val aHat = toUnsignedNumber(alo, ahi)
val bHat = toUnsignedNumber(blo, bhi)
val qHat = fromUnsignedSafeDouble((aHat / bHat) + 0.00390625) // 2^(-8)
val rHat = a - b * qHat
if (rHat.isNegative()) rHat + b else rHat
}
if (thisHigh < 0) {
return r.negate()
} else {
return r
}
}
/**
* @see kotlin.js.internal.longAsBigInt.shiftLeft
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.shiftLeft(numBits: Int): Long {
/* For a proof, see the comment in the Scala.js implementation:
* https://github.com/scala-js/scala-js/blob/v1.22.0/linker-private-library/src/main/scala/org/scalajs/linker/runtime/RuntimeLong.scala#L111
*/
if ((numBits and 32) == 0) {
return Long(low shl numBits, (high shl numBits) or ((low ushr 1) ushr (31 - numBits)))
} else {
return Long(0, low shl numBits)
}
}
/**
* @see kotlin.js.internal.longAsBigInt.shiftRight
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.shiftRight(numBits: Int): Long {
// Similar to shiftLeft
if ((numBits and 32) == 0) {
return Long((low ushr numBits) or ((high shl 1) shl (31 - numBits)), high shr numBits)
} else {
return Long(high shr numBits, high shr 31)
}
}
/**
* @see kotlin.js.internal.longAsBigInt.shiftRightUnsigned
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.shiftRightUnsigned(numBits: Int): Long {
// Similar to shiftLeft
if ((numBits and 32) == 0) {
return Long((low ushr numBits) or ((high shl 1) shl (31 - numBits)), high ushr numBits)
} else {
return Long(high ushr numBits, 0)
}
}
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.bitwiseAnd(other: Long) = Long(this.low and other.low, this.high and other.high)
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.bitwiseOr(other: Long) = Long(this.low or other.low, this.high or other.high)
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.bitwiseXor(other: Long) = Long(this.low xor other.low, this.high xor other.high)
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun Long.invert() = Long(this.low.inv(), this.high.inv())
/**
* Returns a Long representing the given (32-bit) integer value.
* @param value The 32-bit integer in question.
* @return The corresponding Long value.
*
* @see kotlin.js.internal.longAsBigInt.fromInt
*/
// TODO: cache
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun fromInt(value: dynamic) = Long(value, value.unsafeCast<Int>() shr 31)
/**
* @see kotlin.js.internal.longAsBigInt.numberToLong
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun numberToLong(a: dynamic): Long = if (a is Long) a else fromNumber(a)
/**
* Converts this [Double] value to [Long].
* The fractional part, if any, is rounded down towards zero.
* Returns zero if this `Double` value is `NaN`, [Long.MIN_VALUE] if it's less than `Long.MIN_VALUE`,
* [Long.MAX_VALUE] if it's bigger than `Long.MAX_VALUE`.
*
* @see kotlin.js.internal.longAsBigInt.fromNumber
*/
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun fromNumber(value: Double): Long {
/* For a proof, see the comment in the Scala.js implementation:
* https://github.com/scala-js/scala-js/blob/v1.22.0/linker-private-library/src/main/scala/org/scalajs/linker/runtime/RuntimeLong.scala#L402
*/
if (value <= -TWO_PWR_63_DBL_) {
return MIN_VALUE;
} else if (value >= TWO_PWR_63_DBL_) {
return MAX_VALUE;
} else {
val rawLow = jsToInt32(value)
val rawHigh = jsToInt32(value * TWO_PWR_M32_DBL_)
return Long(
rawLow,
if (value < 0 && rawLow != 0) rawHigh - 1 else rawHigh
)
}
}
@BoxedLongApi
@UsedFromCompilerGeneratedCode
@OptIn(ExperimentalStdlibApi::class)
@Suppress("DEPRECATION")
@EagerInitialization
internal val ONE = fromInt(1)
@BoxedLongApi
@OptIn(ExperimentalStdlibApi::class)
@Suppress("DEPRECATION")
@EagerInitialization
private val MAX_VALUE = Long(-1, -1 ushr 1)
@BoxedLongApi
@OptIn(ExperimentalStdlibApi::class)
@Suppress("DEPRECATION")
@EagerInitialization
private val MIN_VALUE = Long(0, 1 shl 31)
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun isLongArray(a: dynamic): Boolean = isJsArray(a) && a.`$type$` === "LongArray"
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun longCopyOfRange(arr: dynamic, fromIndex: dynamic = VOID, toIndex: dynamic = VOID): LongArray =
withType("LongArray", arr.slice(fromIndex, toIndex)).unsafeCast<LongArray>()
@InlineOnly
@OptIn(JsIntrinsic::class)
private inline fun jsToInt32(x: Double): Int = jsBitOr(x, 0)
@InlineOnly
@OptIn(JsIntrinsic::class)
private inline fun boolToInt(x: Boolean): Int = jsBitOr(x, 0)
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun ulongDivide(v1: ULong, v2: ULong): ULong {
// JS Long paper, Section VI, Algorithm 6, specialized to get the quotient
val a = v1.toLong()
val b = v2.toLong()
val alo = a.toInt()
val ahi = (a ushr 32).toInt()
val blo = b.toInt()
val bhi = (b ushr 32).toInt()
/* Conveniently, when b = 0, we enter the first case, where the first thing
* we do is an int division by blo. If that throws an ArithmeticException,
* we will throw it as well. If it ignores it and returns 0, we'll also
* compute a 0 quotient, so we're in sync with the behavior of Int division.
*/
if (bothZero(bhi, blo and 0xffe00000.toInt())) {
// b < 2^21, Algorithm 4
val quotHi = (ahi.toUInt() / blo.toUInt()).toInt()
val k = ahi - blo * quotHi
val quotLo = jsToInt32(toUnsignedNumber(alo, k) / blo.toDouble())
return makeULong(quotLo, quotHi)
} else if (bhi >= 0) {
// 2^21 <= b < 2^63, Algorithm 5
val aHat = toUnsignedNumber(alo, ahi)
val bHat = toUnsignedNumber(blo, bhi)
val qHat = ulongFromUnsignedSafeDouble((aHat / bHat) + 0.00390625).toLong() // 2^(-8)
val rHat = a - b * qHat
return if (rHat < 0L) (qHat - 1L).toULong() else qHat.toULong()
} else {
if (v1 >= v2) {
return 1UL
} else {
return 0UL
}
}
}
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun ulongRemainder(v1: ULong, v2: ULong): ULong {
// JS Long paper, Section VI, Algorithm 6, specialized to get the remainder
val a = v1.toLong()
val b = v2.toLong()
val alo = a.toInt()
val ahi = (a ushr 32).toInt()
val blo = b.toInt()
val bhi = (b ushr 32).toInt()
// See ulongDivide about division by 0
if (bothZero(bhi, blo and 0xffe00000.toInt())) {
// b < 2^21, Algorithm 4
val k = (ahi.toUInt() % blo.toUInt()).toInt()
val quotLo = jsToInt32(toUnsignedNumber(alo, k) / blo.toDouble())
val remLo = alo - blo * quotLo
return makeULong(remLo, 0)
} else if (bhi >= 0) {
// 2^21 <= b < 2^63, Algorithm 5
val aHat = toUnsignedNumber(alo, ahi)
val bHat = toUnsignedNumber(blo, bhi)
val qHat = ulongFromUnsignedSafeDouble((aHat / bHat) + 0.00390625).toLong() // 2^(-8)
val rHat = a - b * qHat
return if (rHat < 0L) (rHat + b).toULong() else rHat.toULong()
} else {
if (v1 >= v2) {
return v1 - v2
} else {
return v1
}
}
}
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun ulongToDouble(value: Long): Double =
toUnsignedNumber(value.toInt(), (value ushr 32).toInt())
@InlineOnly
private inline fun toUnsignedNumber(low: Int, high: Int): Double {
// See the JS Long paper, Section V.A.
return uintToDouble(high) * TWO_PWR_32_DBL_ + uintToDouble(low)
}
@InlineOnly
private inline fun bothZero(a: Int, b: Int): Boolean = (a or b) == 0
@InlineOnly
private inline fun makeULong(low: Int, high: Int): ULong =
low.toUInt().toULong() or (high.toUInt().toULong() shl 32)
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun ulongFromUnsignedSafeDouble(value: Double): ULong {
// JS Long paper, Section V.B.
return makeULong(jsToInt32(value), jsToInt32(value * TWO_PWR_M32_DBL_))
}
/* Conversion from ulong to string.
* See the JS Long paper, Section VIII.
*/
private class ToStringTableEntry(
val w: Int,
val d: Int,
val mHat: Double,
val paddingZeros: String
)
private fun makeToStringTable(): Array<ToStringTableEntry?> {
val r = arrayOfNulls<ToStringTableEntry>(37)
for (base in 2..36) {
/* - d must be the biggest exact power of base that is <= 2^30.
* - w is then log_radix(d).
* - paddingZeros is a string with exactly w '0's.
* - mHat = (1.0 / d.toDouble()) + 2^(-75).
*/
val barrier = (1 shl 30) / base
var d = base
var w = 1
var paddingZeros = "0"
while (d <= barrier) {
d *= base
w += 1
paddingZeros += "0"
}
val mHat = (1.0 / d.toDouble()) + 2.6469779601696886e-23 // 2^(-75)
r[base] = ToStringTableEntry(w, d, mHat, paddingZeros)
}
return r
}
@OptIn(ExperimentalStdlibApi::class)
@Suppress("DEPRECATION")
@EagerInitialization
private val ToStringTable: Array<ToStringTableEntry?> = makeToStringTable()
@BoxedLongApi
@UsedFromCompilerGeneratedCode
internal fun ulongToString(value: Long, base: Int): String {
// JS Long paper, Algorithm 10
val low = value.toInt()
val high = (value ushr 32).toInt()
if (high == 0) {
// value < 2^32
return jsToString(uintToDouble(low), base)
} else if ((high and 0xffe00000.toInt()) == 0) {
// value < 2^53
return jsToString(ulongToDouble(value), base)
} else {
// value >= 2^53
return ulongToStringLarge(value, base)
}
}
/**
* Converts an unsigned long >= 2^53 to string.
*/
@BoxedLongApi
private fun ulongToStringLarge(value: Long, base: Int): String {
// JS Long paper, Algorithm 12
val entry = ToStringTable[base]!!
val d = entry.d
// initial approximation of the quotient and remainder
val aHat = ulongToDouble(value)
var qHat = floor(aHat * entry.mHat)
var rHat = value.toInt() - d * jsToInt32(qHat)
// correct the approximations
if (rHat < 0) {
qHat -= 1.0
rHat += d
}
// build the result string
val qStr = jsToString(qHat, base)
val rStr = jsToString(rHat.toDouble(), base)
return qStr + entry.paddingZeros.substring(rStr.length) + rStr
}