/*------------------------------------------------------------------------- * drawElements Quality Program Tester Core * ---------------------------------------- * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Adjustable-precision floating point operations. *//*--------------------------------------------------------------------*/ 'use strict'; goog.provide('framework.common.tcuFloatFormat'); goog.require('framework.common.tcuInterval'); goog.require('framework.delibs.debase.deMath'); goog.scope(function() { var tcuFloatFormat = framework.common.tcuFloatFormat; var deMath = framework.delibs.debase.deMath; var tcuInterval = framework.common.tcuInterval; /** * @param {tcuFloatFormat.YesNoMaybe} choice * @param {tcuInterval.Interval} no * @param {tcuInterval.Interval} yes * @return {tcuInterval.Interval} */ tcuFloatFormat.chooseInterval = function(choice, no, yes) { switch (choice) { case tcuFloatFormat.YesNoMaybe.NO: return no; case tcuFloatFormat.YesNoMaybe.YES: return yes; case tcuFloatFormat.YesNoMaybe.MAYBE: return no.operatorOrBinary(yes); default: throw new Error('Impossible case'); } }; /** * @param {number} maxExp * @param {number} fractionBits * @return {number} */ tcuFloatFormat.computeMaxValue = function(maxExp, fractionBits) { return deMath.deLdExp(1, maxExp) + deMath.deLdExp(Math.pow(2, fractionBits) - 1, maxExp - fractionBits); }; /** * @enum {number} */ tcuFloatFormat.YesNoMaybe = { NO: 0, MAYBE: 1, YES: 2 }; /** * @constructor * @param {number} minExp * @param {number} maxExp * @param {number} fractionBits * @param {boolean} exactPrecision * @param {tcuFloatFormat.YesNoMaybe=} hasSubnormal * @param {tcuFloatFormat.YesNoMaybe=} hasInf * @param {tcuFloatFormat.YesNoMaybe=} hasNaN */ tcuFloatFormat.FloatFormat = function(minExp, maxExp, fractionBits, exactPrecision, hasSubnormal, hasInf, hasNaN) { // /** @type{number} */ var exponentShift (int exp) const; // Interval clampValue (double d) const; /** @type {number} */ this.m_minExp = minExp; // Minimum exponent, inclusive /** @type {number} */ this.m_maxExp = maxExp; // Maximum exponent, inclusive /** @type {number} */ this.m_fractionBits = fractionBits; // Number of fractional bits in significand /** @type {tcuFloatFormat.YesNoMaybe} */ this.m_hasSubnormal = hasSubnormal === undefined ? tcuFloatFormat.YesNoMaybe.MAYBE : hasSubnormal; // Does the format support denormalized numbers? /** @type {tcuFloatFormat.YesNoMaybe} */ this.m_hasInf = hasInf === undefined ? tcuFloatFormat.YesNoMaybe.MAYBE : hasInf; // Does the format support infinities? /** @type {tcuFloatFormat.YesNoMaybe} */ this.m_hasNaN = hasNaN === undefined ? tcuFloatFormat.YesNoMaybe.MAYBE : hasNaN; // Does the format support NaNs? /** @type {boolean} */ this.m_exactPrecision = exactPrecision; // Are larger precisions disallowed? /** @type {number} */ this.m_maxValue = tcuFloatFormat.computeMaxValue(maxExp, fractionBits); }; /** * @return {number} */ tcuFloatFormat.FloatFormat.prototype.getMinExp = function() { return this.m_minExp; }; /** * @return {number} */ tcuFloatFormat.FloatFormat.prototype.getMaxExp = function() { return this.m_maxExp; }; /** * @return {number} */ tcuFloatFormat.FloatFormat.prototype.getMaxValue = function() { return this.m_maxValue; }; /** * @return {number} */ tcuFloatFormat.FloatFormat.prototype.getFractionBits = function() { return this.m_fractionBits; }; /** * @return {tcuFloatFormat.YesNoMaybe} */ tcuFloatFormat.FloatFormat.prototype.hasSubnormal = function() { return this.m_hasSubnormal; }; /** * @return {tcuFloatFormat.YesNoMaybe} */ tcuFloatFormat.FloatFormat.prototype.hasInf = function() { return this.m_hasInf; }; /** * @param {number} x * @param {number} count * @return {number} */ tcuFloatFormat.FloatFormat.prototype.ulp = function(x, count) { var breakdown = deMath.deFractExp(Math.abs(x)); /** @type {number} */ var exp = breakdown.exponent; /** @type {number} */ var frac = breakdown.significand; if (isNaN(frac)) return NaN; else if (!isFinite(frac)) return deMath.deLdExp(1.0, this.m_maxExp - this.m_fractionBits); else if (frac == 1.0) { // Harrison's ULP: choose distance to closest (i.e. next lower) at binade // boundary. --exp; } else if (frac == 0.0) exp = this.m_minExp; // ULP cannot be lower than the smallest quantum. exp = Math.max(exp, this.m_minExp); /** @type {number} */ var oneULP = deMath.deLdExp(1.0, exp - this.m_fractionBits); // ScopedRoundingMode ctx (DE_ROUNDINGMODE_TO_POSITIVE_INF); return oneULP * count; }; /** * Return the difference between the given nominal exponent and * the exponent of the lowest significand bit of the * representation of a number with this format. * For normal numbers this is the number of significand bits, but * for subnormals it is less and for values of exp where 2^exp is too * small to represent it is <0 * @param {number} exp * @return {number} */ tcuFloatFormat.FloatFormat.prototype.exponentShift = function(exp) { return this.m_fractionBits - Math.max(this.m_minExp - exp, 0); }; /** * @param {number} d * @param {boolean} upward * @return {number} */ tcuFloatFormat.FloatFormat.prototype.round = function(d, upward) { var breakdown = deMath.deFractExp(d); /** @type {number} */ var exp = breakdown.exponent; /** @type {number} */ var frac = breakdown.significand; var shift = this.exponentShift(exp); var shiftFrac = deMath.deLdExp(frac, shift); var roundFrac = upward ? Math.ceil(shiftFrac) : Math.floor(shiftFrac); return deMath.deLdExp(roundFrac, exp - shift); }; /** * Return the range of numbers that `d` might be converted to in the * floatformat, given its limitations with infinities, subnormals and maximum * exponent. * @param {number} d * @return {tcuInterval.Interval} */ tcuFloatFormat.FloatFormat.prototype.clampValue = function(d) { /** @type {number} */ var rSign = deMath.deSign(d); /** @type {number} */ var rExp = 0; // DE_ASSERT(!isNaN(d)); var breakdown = deMath.deFractExp(d); rExp = breakdown.exponent; if (rExp < this.m_minExp) return tcuFloatFormat.chooseInterval(this.m_hasSubnormal, new tcuInterval.Interval(rSign * 0.0), new tcuInterval.Interval(d)); else if (!isFinite(d) || rExp > this.m_maxExp) return tcuFloatFormat.chooseInterval(this.m_hasInf, new tcuInterval.Interval(rSign * this.getMaxValue()), new tcuInterval.Interval(rSign * Number.POSITIVE_INFINITY)); return new tcuInterval.Interval(d); }; /** * @param {number} d * @param {boolean} upward * @param {boolean} roundUnderOverflow * @return {number} */ tcuFloatFormat.FloatFormat.prototype.roundOutDir = function(d, upward, roundUnderOverflow) { var breakdown = deMath.deFractExp(d); var exp = breakdown.exponent; if (roundUnderOverflow && exp > this.m_maxExp && (upward == (d < 0.0))) return deMath.deSign(d) * this.getMaxValue(); else return this.round(d, upward); }; /** * @param {tcuInterval.Interval} x * @param {boolean} roundUnderOverflow * @return {tcuInterval.Interval} */ tcuFloatFormat.FloatFormat.prototype.roundOut = function(x, roundUnderOverflow) { /** @type {tcuInterval.Interval} */ var ret = x.nan(); if (!x.empty()) { var a = new tcuInterval.Interval(this.roundOutDir(x.lo(), false, roundUnderOverflow)); var b = new tcuInterval.Interval(this.roundOutDir(x.hi(), true, roundUnderOverflow)); ret.operatorOrAssignBinary(tcuInterval.withIntervals(a, b)); } return ret; }; //! Return the range of numbers that might be used with this format to //! represent a number within `x`. /** * @param {tcuInterval.Interval} x * @return {tcuInterval.Interval} */ tcuFloatFormat.FloatFormat.prototype.convert = function(x) { /** @type {tcuInterval.Interval} */ var ret = new tcuInterval.Interval(); /** @type {tcuInterval.Interval} */ var tmp = x; if (x.hasNaN()) { // If NaN might be supported, NaN is a legal return value if (this.m_hasNaN != tcuFloatFormat.YesNoMaybe.NO) ret.operatorOrAssignBinary(new tcuInterval.Interval(NaN)); // If NaN might not be supported, any (non-NaN) value is legal, // _subject_ to clamping. Hence we modify tmp, not ret. if (this.m_hasNaN != tcuFloatFormat.YesNoMaybe.YES) tmp = tcuInterval.unbounded(); } // Round both bounds _inwards_ to closest representable values. if (!tmp.empty()) ret.operatorOrAssignBinary( this.clampValue(this.round(tmp.lo(), true)).operatorOrBinary( this.clampValue(this.round(tmp.hi(), false)))); // If this format's precision is not exact, the (possibly out-of-bounds) // original value is also a possible result. if (!this.m_exactPrecision) ret.operatorOrAssignBinary(x); return ret; }; /** * @param {number} x * @return {string} */ tcuFloatFormat.FloatFormat.prototype.floatToHex = function(x) { if (isNaN(x)) return 'NaN'; else if (!isFinite(x)) return (x < 0.0 ? '-' : '+') + ('inf'); else if (x == 0.0) // \todo [2014-03-27 lauri] Negative zero return '0.0'; return x.toString(10); // TODO // var breakdown = deMath.deFractExp(deAbs(x)); // /** @type{number} */ var exp = breakdown.exponent; // /** @type{number} */ var frac = breakdown.significand; // /** @type{number} */ var shift = this.exponentShift(exp); // /** @type{number} */ var bits = deUint64(deLdExp(frac, shift)); // /** @type{number} */ var whole = bits >> m_fractionBits; // /** @type{number} */ var fraction = bits & ((deUint64(1) << m_fractionBits) - 1); // /** @type{number} */ var exponent = exp + m_fractionBits - shift; // /** @type{number} */ var numDigits = (this.m_fractionBits + 3) / 4; // /** @type{number} */ var aligned = fraction << (numDigits * 4 - m_fractionBits); // /** @type{string} */ var oss = ''; // oss + (x < 0 ? '-' : '') // + '0x' + whole + '.' // + std::hex + std::setw(numDigits) + std::setfill('0') + aligned // + 'p' + std::dec + std::setw(0) + exponent; //return oss; }; /** * @param {tcuInterval.Interval} interval * @return {string} */ tcuFloatFormat.FloatFormat.prototype.intervalToHex = function(interval) { if (interval.empty()) return interval.hasNaN() ? '{ NaN }' : '{}'; else if (interval.lo() == interval.hi()) return ((interval.hasNaN() ? '{ NaN, ' : '{ ') + this.floatToHex(interval.lo()) + ' }'); else if (interval == tcuInterval.unbounded(true)) return ''; return ((interval.hasNaN() ? '{ NaN } | ' : '') + '[' + this.floatToHex(interval.lo()) + ', ' + this.floatToHex(interval.hi()) + ']'); }; /** * @return {tcuFloatFormat.FloatFormat} */ tcuFloatFormat.nativeDouble = function() { return new tcuFloatFormat.FloatFormat(-1021 - 1, // min_exponent 1024 - 1, // max_exponent 53 - 1, // digits true, // has_denorm tcuFloatFormat.YesNoMaybe.YES, // has_infinity tcuFloatFormat.YesNoMaybe.YES, // has_quiet_nan tcuFloatFormat.YesNoMaybe.YES); // has_denorm }; });