Skip to content

Commit a34ef73

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 9cb3fed commit a34ef73

File tree

1 file changed

+73
-13
lines changed

1 file changed

+73
-13
lines changed

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

Lines changed: 73 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

@@ -226,9 +225,11 @@ object Float {
226225
fractionalPartStr: String, exponentStr: String,
227226
zDown: scala.Float, zUp: scala.Float, mid: scala.Double): scala.Float = {
228227

228+
val bigIntImpl = BigIntImpl.getImpl()
229+
229230
// 1. Accurately parse the string with the representation f × 10ᵉ
230231

231-
val f: BigInteger = new BigInteger(integralPartStr + fractionalPartStr)
232+
val f: bigIntImpl.Repr = bigIntImpl.fromString(integralPartStr + fractionalPartStr)
232233
val e: Int = Integer.parseInt(exponentStr) - fractionalPartStr.length()
233234

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

262263
val mExplicitBits = midBits & ((1L << mbits) - 1)
263264
val mImplicit1Bit = 1L << mbits // the implicit '1' bit of a normalized floating-point number
264-
val m = BigInteger.valueOf(mExplicitBits | mImplicit1Bit)
265+
val m = bigIntImpl.fromUnsignedLong53(mExplicitBits | mImplicit1Bit)
265266
val k = biasedK - bias - mbits
266267

267268
// 3. Accurately compare f × 10ᵉ to m × 2ᵏ
268269

269-
@inline def compare(x: BigInteger, y: BigInteger): Int =
270-
x.compareTo(y)
270+
import bigIntImpl.{multiplyBy2Pow, multiplyBy10Pow}
271271

272272
val cmp = if (e >= 0) {
273273
if (k >= 0)
274-
compare(multiplyBy10Pow(f, e), multiplyBy2Pow(m, k))
274+
bigIntImpl.compare(multiplyBy10Pow(f, e), multiplyBy2Pow(m, k))
275275
else
276-
compare(multiplyBy2Pow(multiplyBy10Pow(f, e), -k), m) // this branch may be dead code in practice
276+
bigIntImpl.compare(multiplyBy2Pow(multiplyBy10Pow(f, e), -k), m) // this branch may be dead code in practice
277277
} else {
278278
if (k >= 0)
279-
compare(f, multiplyBy2Pow(multiplyBy10Pow(m, -e), k))
279+
bigIntImpl.compare(f, multiplyBy2Pow(multiplyBy10Pow(m, -e), k))
280280
else
281-
compare(multiplyBy2Pow(f, -k), multiplyBy10Pow(m, -e))
281+
bigIntImpl.compare(multiplyBy2Pow(f, -k), multiplyBy10Pow(m, -e))
282282
}
283283

284284
// 4. Choose zDown or zUp depending on the result of the comparison
@@ -293,11 +293,71 @@ object Float {
293293
zUp
294294
}
295295

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

299-
@inline private def multiplyBy2Pow(v: BigInteger, e: Int): BigInteger =
300-
v.shiftLeft(e)
332+
@inline def fromString(str: String): Repr = js.BigInt(str)
333+
334+
// The 53-bit restriction guarantees that the conversion to `Double` is lossless.
335+
@inline def fromUnsignedLong53(x: scala.Long): Repr = js.BigInt(x.toDouble)
336+
337+
@inline def multiplyBy2Pow(v: Repr, e: Int): Repr = v << js.BigInt(e)
338+
@inline def multiplyBy10Pow(v: Repr, e: Int): Repr = v * (js.BigInt(10) ** js.BigInt(e))
339+
340+
@inline def compare(x: Repr, y: Repr): Int = {
341+
if (x < y) -1
342+
else if (x > y) 1
343+
else 0
344+
}
345+
}
346+
347+
private object JBigInteger extends BigIntImpl {
348+
import java.math.BigInteger
349+
350+
type Repr = BigInteger
351+
352+
@inline def fromString(str: String): Repr = new BigInteger(str)
353+
@inline def fromUnsignedLong53(x: scala.Long): Repr = BigInteger.valueOf(x)
354+
355+
@inline def multiplyBy2Pow(v: Repr, e: Int): Repr = v.shiftLeft(e)
356+
@inline def multiplyBy10Pow(v: Repr, e: Int): Repr = v.multiply(BigInteger.TEN.pow(e))
357+
358+
@inline def compare(x: Repr, y: Repr): Int = x.compareTo(y)
359+
}
360+
}
301361

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

0 commit comments

Comments
 (0)