Skip to content

Commit eb01f47

Browse files
committed
Use JS bigint's if possible inside the parseFloat algorithm.
We use a `linkTimeIf` to select a `bigint`-based implementation of `parseFloatDecimalCorrection` when they are supported. We need a `linkTimeIf` in this case because it uses the JS `**` operator, which does not link below ES 2016. The `bigint`-based implementation avoids bringing in the entire `BigInteger` implementation, which is a major code size win if that was the only reason `BigInteger` was needed.
1 parent 440777f commit eb01f47

File tree

1 file changed

+77
-13
lines changed

1 file changed

+77
-13
lines changed

javalib/src/main/scala/java/lang/Float.scala

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
package java.lang
1414

1515
import java.lang.constant.{Constable, ConstantDesc}
16-
import java.math.BigInteger
1716

1817
import scala.scalajs.js
1918

@@ -233,9 +232,11 @@ object Float {
233232
fractionalPartStr: String, exponentStr: String,
234233
zDown: scala.Float, zUp: scala.Float, mid: scala.Double): scala.Float = {
235234

235+
val bigIntImpl = BigIntImpl.getImpl()
236+
236237
// 1. Accurately parse the string with the representation f × 10ᵉ
237238

238-
val f: BigInteger = new BigInteger(integralPartStr + fractionalPartStr)
239+
val f: bigIntImpl.Repr = bigIntImpl.fromString(integralPartStr + fractionalPartStr)
239240
val e: Int = Integer.parseInt(exponentStr) - fractionalPartStr.length()
240241

241242
/* Note: we know that `e` is "reasonable" (in the range [-324, +308]). If
@@ -268,24 +269,23 @@ object Float {
268269

269270
val mExplicitBits = midBits & ((1L << mbits) - 1)
270271
val mImplicit1Bit = 1L << mbits // the implicit '1' bit of a normalized floating-point number
271-
val m = BigInteger.valueOf(mExplicitBits | mImplicit1Bit)
272+
val m = bigIntImpl.fromUnsignedLong53(mExplicitBits | mImplicit1Bit)
272273
val k = biasedK - bias - mbits
273274

274275
// 3. Accurately compare f × 10ᵉ to m × 2ᵏ
275276

276-
@inline def compare(x: BigInteger, y: BigInteger): Int =
277-
x.compareTo(y)
277+
import bigIntImpl.{multiplyBy2Pow, multiplyBy10Pow}
278278

279279
val cmp = if (e >= 0) {
280280
if (k >= 0)
281-
compare(multiplyBy10Pow(f, e), multiplyBy2Pow(m, k))
281+
bigIntImpl.compare(multiplyBy10Pow(f, e), multiplyBy2Pow(m, k))
282282
else
283-
compare(multiplyBy2Pow(multiplyBy10Pow(f, e), -k), m) // this branch may be dead code in practice
283+
bigIntImpl.compare(multiplyBy2Pow(multiplyBy10Pow(f, e), -k), m) // this branch may be dead code in practice
284284
} else {
285285
if (k >= 0)
286-
compare(f, multiplyBy2Pow(multiplyBy10Pow(m, -e), k))
286+
bigIntImpl.compare(f, multiplyBy2Pow(multiplyBy10Pow(m, -e), k))
287287
else
288-
compare(multiplyBy2Pow(f, -k), multiplyBy10Pow(m, -e))
288+
bigIntImpl.compare(multiplyBy2Pow(f, -k), multiplyBy10Pow(m, -e))
289289
}
290290

291291
// 4. Choose zDown or zUp depending on the result of the comparison
@@ -300,11 +300,75 @@ object Float {
300300
zUp
301301
}
302302

303-
@inline private def multiplyBy10Pow(v: BigInteger, e: Int): BigInteger =
304-
v.multiply(BigInteger.TEN.pow(e))
303+
/** An implementation of big integer arithmetics that we need in the above method. */
304+
private sealed abstract class BigIntImpl {
305+
type Repr
306+
307+
def fromString(str: String): Repr
308+
309+
/** Creates a big integer from a `Long` that needs at most 53 bits (unsigned). */
310+
def fromUnsignedLong53(x: scala.Long): Repr
311+
312+
def multiplyBy2Pow(v: Repr, e: Int): Repr
313+
def multiplyBy10Pow(v: Repr, e: Int): Repr
314+
315+
def compare(x: Repr, y: Repr): Int
316+
}
317+
318+
private object BigIntImpl {
319+
/** Get the best available implementation of big integers for the given platform.
320+
*
321+
* If JS bigint's are supported, use them. Otherwise fall back on
322+
* `java.math.BigInteger`.
323+
*
324+
* We need a `linkTimeIf` here because the JS bigint implementation uses
325+
* the `**` operator, which does not link when `esVersion < ESVersion.ES2016`.
326+
*/
327+
@inline def getImpl(): BigIntImpl = {
328+
import scala.scalajs.LinkingInfo._
329+
linkTimeIf[BigIntImpl](esVersion >= ESVersion.ES2020) {
330+
JSBigInt
331+
} {
332+
JBigInteger
333+
}
334+
}
335+
336+
private object JSBigInt extends BigIntImpl {
337+
type Repr = js.BigInt
305338

306-
@inline private def multiplyBy2Pow(v: BigInteger, e: Int): BigInteger =
307-
v.shiftLeft(e)
339+
@inline def fromString(str: String): Repr = js.BigInt(str)
340+
341+
/* The 53-bit restriction allows us to take twice 31 bits, which avoids
342+
* the issue of considering the bit 31 as a sign bit in the rhs of the |.
343+
* It also guarantees we can safely build a `Double` out of it.
344+
*/
345+
@inline def fromUnsignedLong53(x: scala.Long): Repr =
346+
js.BigInt((x >>> 31).toInt.toDouble * 2147483648.0 + (x.toInt & 0x7fffffff).toDouble)
347+
348+
@inline def multiplyBy2Pow(v: Repr, e: Int): Repr = v << js.BigInt(e)
349+
@inline def multiplyBy10Pow(v: Repr, e: Int): Repr = v * (js.BigInt(10) ** js.BigInt(e))
350+
351+
@inline def compare(x: Repr, y: Repr): Int = {
352+
if (x < y) -1
353+
else if (x > y) 1
354+
else 0
355+
}
356+
}
357+
358+
private object JBigInteger extends BigIntImpl {
359+
import java.math.BigInteger
360+
361+
type Repr = BigInteger
362+
363+
@inline def fromString(str: String): Repr = new BigInteger(str)
364+
@inline def fromUnsignedLong53(x: scala.Long): Repr = BigInteger.valueOf(x)
365+
366+
@inline def multiplyBy2Pow(v: Repr, e: Int): Repr = v.shiftLeft(e)
367+
@inline def multiplyBy10Pow(v: Repr, e: Int): Repr = v.multiply(BigInteger.TEN.pow(e))
368+
369+
@inline def compare(x: Repr, y: Repr): Int = x.compareTo(y)
370+
}
371+
}
308372

309373
private def parseFloatHexadecimal(integralPartStr: String,
310374
fractionalPartStr: String, binaryExpStr: String): scala.Float = {

0 commit comments

Comments
 (0)