summaryrefslogblamecommitdiff
path: root/examples/scala-js/library/src/main/scala/scala/scalajs/runtime/Bits.scala
blob: 38b2c3efdf97a149eea95f604865bd8c9043a535 (plain) (tree)















































































































































































































































                                                                                                                     
/*                     __                                               *\
**     ________ ___   / /  ___      __ ____  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
  }

}