diff options
Diffstat (limited to 'examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala')
-rw-r--r-- | examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala new file mode 100644 index 0000000..38b2c3e --- /dev/null +++ b/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala @@ -0,0 +1,240 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js API ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package scala.scalajs.runtime + +import scala.scalajs.js +import js.Dynamic.global +import js.typedarray + +/** Low-level stuff. */ +object Bits { + + val areTypedArraysSupported = ( + !(!global.ArrayBuffer) && !(!global.Int32Array) && + !(!global.Float32Array) && !(!global.Float64Array)) + + private val arrayBuffer = + if (areTypedArraysSupported) new typedarray.ArrayBuffer(8) + else null + + private val int32Array = + if (areTypedArraysSupported) new typedarray.Int32Array(arrayBuffer, 0, 2) + else null + + private val float32Array = + if (areTypedArraysSupported) new typedarray.Float32Array(arrayBuffer, 0, 2) + else null + + private val float64Array = + if (areTypedArraysSupported) new typedarray.Float64Array(arrayBuffer, 0, 1) + else null + + val areTypedArraysBigEndian = { + if (areTypedArraysSupported) { + int32Array(0) = 0x01020304 + (new typedarray.Int8Array(arrayBuffer, 0, 8))(0) == 0x01 + } else { + true // as good a value as any + } + } + + private val highOffset = if (areTypedArraysBigEndian) 0 else 1 + private val lowOffset = if (areTypedArraysBigEndian) 1 else 0 + + /** Hash code of a number (excluding Longs). + * + * Because of the common encoding for integer and floating point values, + * the hashCode of Floats and Doubles must align with that of Ints for the + * common values. + * + * For other values, we use the hashCode specified by the JavaDoc for + * *Doubles*, even for values which are valid Float values. Because of the + * previous point, we cannot align completely with the Java specification, + * so there is no point trying to be a bit more aligned here. Always using + * the Double version should typically be faster on VMs without fround + * support because we avoid several fround operations. + */ + def numberHashCode(value: Double): Int = { + val iv = value.toInt + if (iv == value) iv + else doubleToLongBits(value).hashCode() + } + + def intBitsToFloat(bits: Int): Float = { + if (areTypedArraysSupported) { + int32Array(0) = bits + float32Array(0) + } else { + intBitsToFloatPolyfill(bits).toFloat + } + } + + def floatToIntBits(value: Float): Int = { + if (areTypedArraysSupported) { + float32Array(0) = value + int32Array(0) + } else { + floatToIntBitsPolyfill(value.toDouble) + } + } + + def longBitsToDouble(bits: Long): Double = { + if (areTypedArraysSupported) { + int32Array(highOffset) = (bits >>> 32).toInt + int32Array(lowOffset) = bits.toInt + float64Array(0) + } else { + longBitsToDoublePolyfill(bits) + } + } + + def doubleToLongBits(value: Double): Long = { + if (areTypedArraysSupported) { + float64Array(0) = value + ((int32Array(highOffset).toLong << 32) | + (int32Array(lowOffset).toLong & 0xffffffffL)) + } else { + doubleToLongBitsPolyfill(value) + } + } + + /* --- Polyfills for floating point bit manipulations --- + * + * Originally inspired by + * https://github.com/inexorabletash/polyfill/blob/a682f42c1092280bb01907c245979fb07219513d/typedarray.js#L150-L255 + * + * Note that if typed arrays are not supported, it is almost certain that + * fround is not supported natively, so Float operations are extremely slow. + * + * We therefore do all computations in Doubles here, which is also more + * predictable, since the results do not depend on strict floats semantics. + */ + + private def intBitsToFloatPolyfill(bits: Int): Double = { + val ebits = 8 + val fbits = 23 + val s = bits < 0 + val e = (bits >> fbits) & ((1 << ebits) - 1) + val f = bits & ((1 << fbits) - 1) + decodeIEEE754(ebits, fbits, s, e, f) + } + + private def floatToIntBitsPolyfill(value: Double): Int = { + val ebits = 8 + val fbits = 23 + val (s, e, f) = encodeIEEE754(ebits, fbits, value) + (if (s) 0x80000000 else 0) | (e << fbits) | f.toInt + } + + private def longBitsToDoublePolyfill(bits: Long): Double = { + val ebits = 11 + val fbits = 52 + val hifbits = fbits-32 + val hi = (bits >>> 32).toInt + val lo = ((bits.toInt: js.prim.Number) >>> 0).toDouble + val s = hi < 0 + val e = (hi >> hifbits) & ((1 << ebits) - 1) + val f = (hi & ((1 << hifbits) - 1)).toDouble * 0x100000000L.toDouble + lo + decodeIEEE754(ebits, fbits, s, e, f) + } + + private def doubleToLongBitsPolyfill(value: Double): Long = { + val ebits = 11 + val fbits = 52 + val hifbits = fbits-32 + val (s, e, f) = encodeIEEE754(ebits, fbits, value) + val hif = (f / 0x100000000L.toDouble).toInt + val hi = (if (s) 0x80000000 else 0) | (e << hifbits) | hif + val lo = f.toInt + (hi.toLong << 32) | (lo.toLong & 0xffffffffL) + } + + @inline private def decodeIEEE754(ebits: Int, fbits: Int, + s: Boolean, e: Int, f: Double): Double = { + + import Math.pow + + val bias = (1 << (ebits-1)) - 1 // constant + + if (e == (1 << ebits) - 1) { + // Special + if (f != 0.0) Double.NaN + else if (s) Double.NegativeInfinity + else Double.PositiveInfinity + } else if (e > 0) { + // Normalized + val x = pow(2, e-bias) * (1 + f / pow(2, fbits)) + if (s) -x else x + } else if (f != 0.0) { + // Subnormal + val x = pow(2, -(bias-1)) * (f / pow(2, fbits)) + if (s) -x else x + } else { + // Zero + if (s) -0.0 else 0.0 + } + } + + @inline private def encodeIEEE754(ebits: Int, fbits: Int, + v: Double): (Boolean, Int, Double) = { + + import Math._ + + val bias = (1 << (ebits-1)) - 1 // constant + + if (v.isNaN) { + // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping + (false, (1 << ebits) - 1, pow(2, fbits-1)) + } else if (v.isInfinite) { + (v < 0, (1 << ebits) - 1, 0.0) + } else if (v == 0.0) { + (1 / v == Double.NegativeInfinity, 0, 0.0) + } else { + val LN2 = 0.6931471805599453 + + val s = v < 0 + val av = if (s) -v else v + + if (av >= pow(2, 1-bias)) { + val twoPowFbits = pow(2, fbits) + + var e = min(floor(log(av) / LN2).toInt, 1023) + var f = roundToEven(av / pow(2, e) * twoPowFbits) + if (f / twoPowFbits >= 2) { + e = e + 1 + f = 1 + } + if (e > bias) { + // Overflow + e = (1 << ebits) - 1 + f = 0 + } else { + // Normalized + e = e + bias + f = f - twoPowFbits + } + (s, e, f) + } else { + // Subnormal + (s, 0, roundToEven(av / pow(2, 1-bias-fbits))) + } + } + } + + @inline private[runtime] def roundToEven(n: Double): Double = { + val w = Math.floor(n) + val f = n - w + if (f < 0.5) w + else if (f > 0.5) w + 1 + else if (w % 2 != 0) w + 1 + else w + } + +} |