blob: a0b4dc82cf4b6dc3f8938a7220f646b81080ae5b [file] [log] [blame]
/*-------------------------------------------------------------------------
* 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 '<any>';
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
};
});