baja/obj/Double.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Gareth Johnson
 */

/**
 * Defines {@link baja.Double}.
 * @module baja/obj/Double
 */
define([ 'bajaScript/sys',
        'bajaScript/baja/obj/numberUtil',
        'bajaScript/baja/obj/objUtil' ], function (
        baja,
        numberUtil,
        objUtil) {

  'use strict';

  var floatingPointToString = numberUtil.floatingPointToString,
      oldNumberToString = Number.prototype.toString;

  /**
   * The native JavaScript Number constructor. BajaScript extends it to add
   * Niagara-related functionality: {@link baja.Double}
   *
   * @external Number
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number Number}
   */

  /**
   * Represents a `baja:Double` in BajaScript.
   *
   * Augments `Number` to be a Baja `Double`. ECMAScript only has one numeric
   * type for Doubles, Floats, Longs and Integers. Therefore, this type
   * naturally maps to `baja:Double`.
   *
   * @class
   * @alias baja.Double
   * @extends external:Number
   */
  var BDouble = Number;

  /**
   * Default `Number` instance.
   * @type {Number}
   */
  BDouble.DEFAULT = 0;

  /**
   * Make a `Number`.
   *
   * @param {Number} num - the number value.
   *
   * @returns {Number}
   */
  BDouble.make = function (num) {
    return Number(num);
  };

  /**
   * Make a `Number`.
   *
   * @param {Number} num - the number value.
   *
   * @returns {Number}
   */
  BDouble.prototype.make = function (num) {
    return Number.make(num);
  };

  /**
   * Decode a `Number` from a `String`.
   *
   * @param {String} str - an encoded `Number`.
   *
   * @returns {Number}
   */
  BDouble.prototype.decodeFromString = function (str) {
    switch (str) {

    case "0": return 0;

    // Infinity and NaN
    case '+inf': return Number.POSITIVE_INFINITY;
    case '-inf': return Number.NEGATIVE_INFINITY;
    case 'nan': return Number.NaN; // Workaround for JsLint

    default: return Number(str);
    }
  };

  BDouble.prototype.decodeAsync = function (str) {
    return this.decodeFromString(str);
  };

  /**
   * Encode the `Number` (itself) to a `String`.
   *
   * @returns {String}
   */
  BDouble.prototype.encodeToString = function () {
    var num = this.valueOf();
    // Infinity and NaN
    if (num === Number.POSITIVE_INFINITY) { return '+inf'; }
    if (num === Number.NEGATIVE_INFINITY) { return '-inf'; }
    if (isNaN(num)) { return 'nan'; }

    return uppercaseExponent(String(num));
  };

  /**
   * Return the data type symbol.
   *
   * Used for encoding this data type (primarily for facets).
   *
   * @returns {String}
   */
  BDouble.prototype.getDataTypeSymbol = function () {
    return "d";
  };

  /**
   * Equality test.
   *
   * @param obj
   *
   * @returns {Boolean}
   */
  BDouble.prototype.equals = function (obj) {
    return objUtil.valueOfEquals(this, obj);
  };

  /**
   * Equivalence test.
   *
   * Used to compare if two objects have equivalent state, but might not want to
   * return true for equals since it it has implied semantics for many
   * operations.  The default implementation returns the result of
   * <code>equals()</code>.
   *
   * @param obj
   *
   * @returns {Boolean}
   */
  BDouble.prototype.equivalent = function (obj) {
    return objUtil.equalsEquivalent(this, obj);
  };

  /**
   * New Copy.
   *
   * @returns {Number}
   */
  BDouble.prototype.newCopy = function (exact) {
    return objUtil.valueOfNewCopy(this, exact);
  };

  /**
   * Return the Object's Icon.
   *
   * @returns {baja.Icon}
   */
  BDouble.prototype.getIcon = function () {
    return objUtil.objectGetIcon(this);
  };

  /**
   * Return the `Number` (itself).
   *
   * @returns {Number}
   */
  BDouble.prototype.getNumber = function () {
    return this.valueOf();
  };

  /**
   * Returns the `Number` (itself).
   * @returns {Number}
   */
  BDouble.prototype.getNumeric =  function () {
    return this.valueOf();
  };

  /**
   * Return a `Number` from a BINumeric.
   *
   * @returns {Number}
   */
  BDouble.getNumberFromINumeric = function (numeric) {
    var type = numeric.getType(),
        out;

    if (typeof numeric.getNumeric === "function") {
      return numeric.getNumeric();
    }

    if (type.isComplex()) {
      out = numeric.get("out");
      if (out && out.getType().is("baja:StatusNumeric")) {
        return out.getValue().valueOf();
      } else {
        var value = numeric.get('value');
        if (baja.hasType(value, 'baja:Number')) {
          return +value;
        }
      }
    }

    if (type.is("baja:Number")) {
      return numeric.valueOf();
    } else if (typeof numeric.getNumber === "function") {
      return numeric.getNumber();
    }

    if (numeric.getType().is("baja:Simple")) {
      var encodedValue = numeric.encodeToString(),
        result = baja.Double.DEFAULT.decodeFromString(encodedValue);
      if (!isNaN(result) ||  [ "nan", "+inf", "-inf" ].includes(encodedValue)) {
        return result;
      }
    } 

    return 0;
  };

  /**
   * Return facets from an INumeric
   * 
   * @param {baja.Value} numeric 
   * @returns {baja.Facets}
   */
  BDouble.getFacetsFromINumeric = function (numeric) {
    // First check if getNumericFacets is available
    if (typeof numeric.getNumericFacets === 'function') {
      return numeric.getNumericFacets();
    }

    if (baja.hasType(numeric, 'baja:StatusNumeric')) {
      return baja.Facets.NULL;
    }

    // Next check the 'out' and 'facets' slots
    return baja.Facets.getFacetsFromObject(numeric);
  };

  /**
   * Return the `String` representation of the `Double` (itself).
   *
   * @param {baja.Facets|Object} [cx] - used to specify formatting facets. The
   * argument can also be an Object Literal.
   *
   * @param {Boolean} [cx.forceSign] - specifying 'true' will concatenate a '+'
   * to the beginning of the number if positive.
   *
   * @param {Number} [cx.precision] - the number of decimal places to show in
   * the return string. Specifying '0' will also remove the decimal. If a context
   * is provided without precision, this value will default to 2. If no context
   * is provided, there will be no precision applied.
   *
   * @param {Boolean} [cx.showSeparators] - include separators.
   *
   * @param {baja.Unit} [cx.units] - the baja Unit to apply to the return
   * string.
   *
   * @param {baja.Enum|Number|String} [cx.unitConversion] - the
   * `baja:UnitConversion` enum, an ordinal, or tag. If omitted, the
   * user-configured `baja.getUnitConversion()` will be used.
   *
   * @param {Number} [cx.zeroPad] - the minimum number of the whole-number
   * digits to be displayed, filling in zeroes when necessary.
   *
   * @param {boolean} [cx.trimTrailingZeros] - set to true if trailing zeros
   * should not be included in the string. If the only digits after the decimal
   * point are zeros, the decimal point will be removed as well.
   *
   * @returns {String|Promise.<String>} returns a Promise if a cx is passed in.
   */
  BDouble.prototype.toString = function (cx) {
    if (!cx || typeof cx === 'number') {
      return uppercaseExponent(oldNumberToString.apply(this, arguments));
    }
    if (cx instanceof baja.Facets) {
      cx = cx.toObject();
    }

    return floatingPointToString(this, cx);
  };

  /**
   * Returns a promise that resolves to the agent list for this BDouble.
   *
   * @see baja.registry.getAgents
   *
   * @param  {Array<String>} [is] An optional array of filters to add to the
   * agent query.
   * @param  {baja.comm.Batch} [batch] An optional object used to batch network
   * calls together.
   * @returns {Promise} A promise that will resolve with the Agent Info.
   * @since Niagara 4.15
   */
  BDouble.prototype.getAgents = function (is, batch) {
    return baja.registry.getAgents("type:" + this.getType().toString(), is, batch);
  };

  function uppercaseExponent(str) {
    //BDouble will not accept lowercase exponents
    return str.replace(/e([+-])/, function (match, sign) {
      return 'E' + sign;
    });
  }

  return BDouble;
});