/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES Utilities
 * ------------------------------------------------
 *
 * 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.
 *
 */

'use strict';
goog.provide('modules.shared.glsBuiltinPrecisionTests');
goog.require('framework.common.tcuFloatFormat');
goog.require('framework.common.tcuInterval');
goog.require('framework.common.tcuMatrix');
goog.require('framework.common.tcuMatrixUtil');
goog.require('framework.common.tcuTestCase');
goog.require('framework.delibs.debase.deMath');
goog.require('framework.delibs.debase.deRandom');
goog.require('framework.delibs.debase.deUtil');
goog.require('framework.opengl.gluShaderProgram');
goog.require('framework.opengl.gluShaderUtil');
goog.require('framework.opengl.gluVarType');
goog.require('framework.opengl.simplereference.sglrGLContext');
goog.require('modules.shared.glsBuiltinPrecisionTestsUnitTests');
goog.require('modules.shared.glsShaderExecUtil');

goog.scope(function() {

    var glsBuiltinPrecisionTests = modules.shared.glsBuiltinPrecisionTests;
    var tcuTestCase = framework.common.tcuTestCase;
    var gluShaderProgram = framework.opengl.gluShaderProgram;
    var gluShaderUtil = framework.opengl.gluShaderUtil;
    var tcuInterval = framework.common.tcuInterval;
    var tcuFloatFormat = framework.common.tcuFloatFormat;
    var deRandom = framework.delibs.debase.deRandom;
    var glsShaderExecUtil = modules.shared.glsShaderExecUtil;
    var sglrGLContext = framework.opengl.simplereference.sglrGLContext;
    var deMath = framework.delibs.debase.deMath;
    var deUtil = framework.delibs.debase.deUtil;
    var gluVarType = framework.opengl.gluVarType;
    var tcuMatrix = framework.common.tcuMatrix;
    var tcuMatrixUtil = framework.common.tcuMatrixUtil;
    var ref = modules.shared.glsBuiltinPrecisionTestsUnitTests.cppreference;
    var referenceComparison = modules.shared.glsBuiltinPrecisionTestsUnitTests.referenceComparison;

    var DE_ASSERT = function(x) {
        if (!x)
            throw new Error('Assert failed');
    };

var setParentClass = function(child, parent) {
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
};

    /** @typedef {(tcuInterval.Interval|Array<tcuInterval.Interval>|tcuMatrix.Matrix)} */
    glsBuiltinPrecisionTests.Intervals;

    /** @typedef {(number|Array<number>|tcuMatrix.Matrix)} */
    glsBuiltinPrecisionTests.Value;

    /** @typedef {(string)} */
    glsBuiltinPrecisionTests.Typename;

    //Change to true for WebGL unit testing
    var enableUnittests = false;

    /**
     * @param {number} value
     * @return {boolean}
     */
    glsBuiltinPrecisionTests.isFloat = function(value) {
        return value % 1 !== 0;
     };

    /**
     * @constructor
     * @param {string} R
     * @param {string=} P0
     * @param {string=} P1
     * @param {string=} P2
     * @param {string=} P3
     */
    glsBuiltinPrecisionTests.Signature = function(R, P0, P1, P2, P3) {
        this.Ret = R;
        this.Arg0 = P0 === undefined ? 'void' : P0;
        this.Arg1 = P1 === undefined ? 'void' : P1;
        this.Arg2 = P2 === undefined ? 'void' : P2;
        this.Arg3 = P3 === undefined ? 'void' : P3;
    };

    /** @typedef {Array<glsBuiltinPrecisionTests.FuncBase>} */
    glsBuiltinPrecisionTests.FuncSet;

    /**
     * @constructor
     * @template T
     * @param {T} A0
     * @param {T} A1
     * @param {T} A2
     * @param {T} A3
     */
    glsBuiltinPrecisionTests.Tuple4 = function(A0, A1, A2, A3) {
        this.a = A0;
        this.b = A1;
        this.c = A2;
        this.d = A3;
    };

    /**
     * @typedef {!glsBuiltinPrecisionTests.Tuple4<string>}
     */
    glsBuiltinPrecisionTests.ParamNames;

    /**
     * Returns true for all other types except Void
     * @param {string} typename
     */
    glsBuiltinPrecisionTests.isTypeValid = function(typename) {
        if (typename === 'void')
            return false;
        return true;
    };

    /**
     * Returns true for all other types except Void
     * @param {*} In
     * @return {number}
     */
    glsBuiltinPrecisionTests.numInputs = function(In) {
        return (!glsBuiltinPrecisionTests.isTypeValid(In.In0) ? 0 :
                !glsBuiltinPrecisionTests.isTypeValid(In.In1) ? 1 :
                !glsBuiltinPrecisionTests.isTypeValid(In.In2) ? 2 :
                !glsBuiltinPrecisionTests.isTypeValid(In.In3) ? 3 :
                4);
    };

    /**
     * Returns true for all other types except Void
     * @param {*} Out
     * @return {number}
     */
    glsBuiltinPrecisionTests.numOutputs = function(Out) {
        return (!glsBuiltinPrecisionTests.isTypeValid(Out.Out0) ? 0 :
                !glsBuiltinPrecisionTests.isTypeValid(Out.Out1) ? 1 :
                2);
    };

    /**
     * @constructor
     * @param {glsBuiltinPrecisionTests.Typename=} In0_
     * @param {glsBuiltinPrecisionTests.Typename=} In1_
     * @param {glsBuiltinPrecisionTests.Typename=} In2_
     * @param {glsBuiltinPrecisionTests.Typename=} In3_
     */
    glsBuiltinPrecisionTests.InTypes = function(In0_, In1_, In2_, In3_) {
        this.In0 = In0_ === undefined ? 'void' : In0_;
        this.In1 = In1_ === undefined ? 'void' : In1_;
        this.In2 = In2_ === undefined ? 'void' : In2_;
        this.In3 = In3_ === undefined ? 'void' : In3_;
    };

    /**
     * @constructor
     * @param {glsBuiltinPrecisionTests.Typename=} Out0_
     * @param {glsBuiltinPrecisionTests.Typename=} Out1_
     */
    glsBuiltinPrecisionTests.OutTypes = function(Out0_, Out1_) {
        this.Out0 = Out0_ === undefined ? 'void' : Out0_;
        this.Out1 = Out1_ === undefined ? 'void' : Out1_;
    };

    /**
     * @constructor
     */
    glsBuiltinPrecisionTests.Environment = function() {
        /** @type {Object} */ this.m_map = {};
    };

    /**
     * @param {glsBuiltinPrecisionTests.Variable} variable
     * @param {*} value
     */
    glsBuiltinPrecisionTests.Environment.prototype.bind = function(variable, value) {
        this.m_map[variable.getName()] = value;
    };

    /**
     * @param {*} variable
     * @return {glsBuiltinPrecisionTests.Intervals}
     */
    glsBuiltinPrecisionTests.Environment.prototype.lookup = function(variable) {
        if (variable instanceof glsBuiltinPrecisionTests.Variable)
           return this.m_map[variable.getName()];

        throw new Error('Invalid lookup input: ' + variable);
    };

    /**
     * @constructor
     * @param {tcuFloatFormat.FloatFormat} format_
     * @param {gluShaderUtil.precision} floatPrecision_
     * @param {glsBuiltinPrecisionTests.Environment} env_
     * @param {number=} callDepth_
     */
    glsBuiltinPrecisionTests.EvalContext = function(format_, floatPrecision_, env_, callDepth_) {
        this.format = format_;
        this.floatPrecision = floatPrecision_;
        this.env = env_;
        this.callDepth = callDepth_ === undefined ? 0 : callDepth_;
    };

    /**
     * @param {string} typename typename
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {glsBuiltinPrecisionTests.Intervals} value
     * @return {glsBuiltinPrecisionTests.Intervals}
     */
     glsBuiltinPrecisionTests.convert = function(typename, fmt, value) {
        var traits = glsBuiltinPrecisionTests.Traits.traitsFactory(typename);

        if (value instanceof Array) {
            var ret = [];
            for (var i = 0; i < value.length; i++)
                ret.push(traits.doConvert(fmt, value[i]));
            return ret;
        }

        if (value instanceof tcuMatrix.Matrix) {
            var ret = new tcuMatrix.Matrix(value.rows, value.cols);
            for (var i = 0; i < value.rows; i++)
                for (var j = 0; j < value.cols; j++)
                    ret.set(i, j, traits.doConvert(fmt, value.get(i, j)));
            return ret;
        }

        return traits.doConvert(fmt, value);
    };

    /**
     * Returns true if every element of `ival` contains the corresponding element of `value`.
     * @param {string} typename typename
     * @param {glsBuiltinPrecisionTests.Intervals} ival
     * @param {*} value
     * @return {boolean}
     */
     glsBuiltinPrecisionTests.contains = function(typename, ival, value) {
        var traits = glsBuiltinPrecisionTests.Traits.traitsFactory(typename);
        var contains = true;

        if (value instanceof Array) {
            for (var i = 0; i < value.length; i++)
                contains &= traits.doContains(ival[i], value[i]);
            return contains;
        }

        if (value instanceof tcuMatrix.Matrix) {
            for (var i = 0; i < value.rows; i++)
                for (var j = 0; j < value.cols; j++)
                    contains &= traits.doContains(ival.get(i, j), value.get(i, j));
            return contains;
        }

        return traits.doContains(ival, value);
    };

    /**
     * @param {string} typename typename
     * @param {glsBuiltinPrecisionTests.Intervals} ival0
     * @param {glsBuiltinPrecisionTests.Intervals} ival1
     * @return {glsBuiltinPrecisionTests.Intervals}
     */
     glsBuiltinPrecisionTests.union = function(typename, ival0, ival1) {
        var traits = glsBuiltinPrecisionTests.Traits.traitsFactory(typename);

        if (ival0 instanceof Array) {
            var ret = [];
            for (var i = 0; i < ival0.length; i++)
                ret.push(traits.doUnion(ival0[i], ival1[i]));
            return ret;
        }

        if (ival0 instanceof tcuMatrix.Matrix) {
            var ret = new tcuMatrix.Matrix(ival0.rows, ival0.cols);
            for (var i = 0; i < ival0.rows; i++)
                for (var j = 0; j < ival0.cols; j++)
                    ret.set(i, j, traits.doUnion(ival0.get(i, j), ival1.get(i, j)));
            return ret;
        }

        return traits.doUnion(ival0, ival1);
    };

    /**
     * @param {string} typename
     * @constructor
     */
    glsBuiltinPrecisionTests.Traits = function(typename) {
        this.typename = typename;
        this.rows = 1;
        this.cols = 1;
    };

    glsBuiltinPrecisionTests.Traits.prototype.isScalar = function() {
        return this.rows == 1 && this.cols == 1;
    };

    glsBuiltinPrecisionTests.Traits.prototype.isVector = function() {
        return this.rows > 0 && this.cols == 1;
    };

    glsBuiltinPrecisionTests.Traits.prototype.isMatrix = function() {
        return this.rows > 0 && this.cols > 1;
    };

    /**
     * @param {string=} typename
     */
    glsBuiltinPrecisionTests.Traits.traitsFactory = function(typename) {
        switch (typename) {
            case 'boolean' : return new glsBuiltinPrecisionTests.TraitsBool();
            case 'float' : case 'vec2' : case 'vec3' : case 'vec4' :
            case 'mat2' : case 'mat2x3' : case 'mat2x4' :
            case 'mat3x2' : case 'mat3' : case 'mat3x4' :
            case 'mat4x2' : case 'mat4x3' : case 'mat4' :
                return new glsBuiltinPrecisionTests.TraitsFloat(typename);
            case 'int' : return new glsBuiltinPrecisionTests.TraitsInt();
            case 'void' : return new glsBuiltinPrecisionTests.TraitsVoid();
            default:
                throw new Error('Invalid typename:' + typename);
        }
    };

    glsBuiltinPrecisionTests.round = function(typename, fmt, value) {
        var traits = glsBuiltinPrecisionTests.Traits.traitsFactory(typename);

        if (value instanceof Array) {
            var ret = [];
            for (var i = 0; i < value.length; i++)
                ret.push(traits.doRound(fmt, value[i]));
            return ret;
        }

        if (value instanceof tcuMatrix.Matrix) {
            var ret = new tcuMatrix.Matrix(value.rows, value.cols);
            for (var i = 0; i < value.rows; i++)
                for (var j = 0; j < value.cols; j++)
                    ret.set(i, j, traits.doRound(fmt, value.get(i, j)));
            return ret;
        }

        return traits.doRound(fmt, value);
    };

    /**
     * cast the input typed array to correct type
     * @param {string} typename
     * @param {goog.TypedArray} input
     * @return {goog.TypedArray}
     */
    glsBuiltinPrecisionTests.cast = function(typename, input) {
        var traits = glsBuiltinPrecisionTests.Traits.traitsFactory(typename);
        return traits.doCast(input);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Traits}
     */
    glsBuiltinPrecisionTests.TraitsVoid = function() {
        glsBuiltinPrecisionTests.Traits.call(this, 'void');
    };

    setParentClass(glsBuiltinPrecisionTests.TraitsVoid, glsBuiltinPrecisionTests.Traits);

    /**
     * @param {*} value
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.TraitsVoid.prototype.doMakeIVal = function(value) {
        return new tcuInterval.Interval();
    };

    /**
     * @param {*} value1
     * @param {*} value2
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.TraitsVoid.prototype.doUnion = function(value1, value2) {
        return new tcuInterval.Interval();
    };

    /**
     * @param {*} value
     * @return {boolean}
     */
    glsBuiltinPrecisionTests.TraitsVoid.prototype.doContains = function(value) {
        return true;
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {tcuInterval.Interval} ival
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.TraitsVoid.prototype.doConvert = function(fmt, ival) {
        return new tcuInterval.Interval();
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {*} ival
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.TraitsVoid.prototype.doRound = function(fmt, ival) {
        return new tcuInterval.Interval();
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {*} ival
     */
    glsBuiltinPrecisionTests.TraitsVoid.prototype.doPrintIVal = function(fmt, ival) {
        return '()';
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {*} value
     */
    glsBuiltinPrecisionTests.TraitsVoid.prototype.doPrintValue = function(fmt, value) {
        return '()';
    };

    glsBuiltinPrecisionTests.dataTypeSize = function(detailedType) {
        var size = [1, 1];
        switch (detailedType) {
            case 'vec2' : size[0] = 2; break;
            case 'vec3' : size[0] = 3; break;
            case 'vec4' : size[0] = 4; break;
            case 'mat2' : size = [2 , 2]; break;
            case 'mat2x3' : size = [3 , 2]; break;
            case 'mat2x4' : size = [4 , 2]; break;

            case 'mat3x2' : size = [2 , 3]; break;
            case 'mat3' : size = [3 , 3]; break;
            case 'mat3x4' : size = [4 , 3]; break;

            case 'mat4x2' : size = [2 , 4]; break;
            case 'mat4x3' : size = [3 , 4]; break;
            case 'mat4' : size = [4 , 4]; break;
        }
        return size;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Traits}
     * @param {string} typename
     * @param {string=} detailedType
     */
    glsBuiltinPrecisionTests.ScalarTraits = function(typename, detailedType) {
        glsBuiltinPrecisionTests.Traits.call(this, typename);
        var size = glsBuiltinPrecisionTests.dataTypeSize(detailedType);
        this.rows = size[0];
        this.cols = size[1];

        /** type{tcuInterval.Interval} */ this.iVal;
    };

    glsBuiltinPrecisionTests.ScalarTraits.prototype = Object.create(glsBuiltinPrecisionTests.Traits.prototype);
    glsBuiltinPrecisionTests.ScalarTraits.prototype.constructor = glsBuiltinPrecisionTests.ScalarTraits;

    /**
     * @param {*} value
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.ScalarTraits.prototype.doMakeIVal = function(value) {
        // Thankfully all scalar types have a well-defined conversion to `double`,
        // hence Interval can represent their ranges without problems.
        return new tcuInterval.Interval(/** @type {number} */ (value));
    };

    /**
     * @param {tcuInterval.Interval} a
     * @param {tcuInterval.Interval} b
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.ScalarTraits.prototype.doUnion = function(a, b) {
        return a.operatorOrBinary(b);
    };

    /**
     * @param {tcuInterval.Interval} a
     * @param {number} value
     * @return {boolean}
     */
    glsBuiltinPrecisionTests.ScalarTraits.prototype.doContains = function(a, value) {
        return a.contains(new tcuInterval.Interval(value));
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {tcuInterval.Interval} ival
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.ScalarTraits.prototype.doConvert = function(fmt, ival) {
        return fmt.convert(ival);
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {number} value
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.ScalarTraits.prototype.doRound = function(fmt, value) {
        return fmt.roundOut(new tcuInterval.Interval(value), false);//TODO cast to double
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.ScalarTraits}
     * @param {string} detailedType
     */
    glsBuiltinPrecisionTests.TraitsFloat = function(detailedType) {
        glsBuiltinPrecisionTests.ScalarTraits.call(this, 'float', detailedType);
    };

    glsBuiltinPrecisionTests.TraitsFloat.prototype = Object.create(glsBuiltinPrecisionTests.ScalarTraits.prototype);
    glsBuiltinPrecisionTests.TraitsFloat.prototype.constructor = glsBuiltinPrecisionTests.TraitsFloat;

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {tcuInterval.Interval} ival
     */
    glsBuiltinPrecisionTests.TraitsFloat.prototype.doPrintIVal = function(fmt, ival) {
        return fmt.intervalToHex(ival);
    };

    /**
     * @param {goog.TypedArray} input
     * @return {goog.TypedArray}
     */
    glsBuiltinPrecisionTests.TraitsFloat.prototype.doCast = function(input) {
        return new Float32Array(input.buffer);
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {number} value
     */
    glsBuiltinPrecisionTests.TraitsFloat.prototype.doPrintValue = function(fmt, value) {
        return fmt.floatToHex(value);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.ScalarTraits}
     */
    glsBuiltinPrecisionTests.TraitsBool = function() {
        glsBuiltinPrecisionTests.ScalarTraits.call(this, 'boolean');
    };

    glsBuiltinPrecisionTests.TraitsBool.prototype = Object.create(glsBuiltinPrecisionTests.ScalarTraits.prototype);
    glsBuiltinPrecisionTests.TraitsBool.prototype.constructor = glsBuiltinPrecisionTests.TraitsBool;

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {tcuInterval.Interval} ival
     */
    glsBuiltinPrecisionTests.TraitsBool.prototype.doPrintIVal = function(fmt, ival) {
        /** type{string} */ var os = '{';
        var ifalse = new tcuInterval.Interval(0);
        var itrue = new tcuInterval.Interval(1);
        if (ival.contains(ifalse))
            os += 'false';
        if (ival.contains(ifalse) && ival.contains(itrue))
            os += ', ';
        if (ival.contains(itrue))
            os += 'true';
        os += '}';
        return os;
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {boolean} value
     */
    glsBuiltinPrecisionTests.TraitsBool.prototype.doPrintValue = function(fmt, value) {
        return value ? 'true' : 'false';
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.ScalarTraits}
     */
    glsBuiltinPrecisionTests.TraitsInt = function() {
        glsBuiltinPrecisionTests.ScalarTraits.call(this, 'int');
    };

    glsBuiltinPrecisionTests.TraitsInt.prototype = Object.create(glsBuiltinPrecisionTests.ScalarTraits.prototype);
    glsBuiltinPrecisionTests.TraitsInt.prototype.constructor = glsBuiltinPrecisionTests.TraitsInt;

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {tcuInterval.Interval} ival
     */
    glsBuiltinPrecisionTests.TraitsInt.prototype.doPrintIVal = function(fmt, ival) {
        return '[' + (ival.lo()) + ', ' + (ival.hi()) + ']';
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {number} value
     */
    glsBuiltinPrecisionTests.TraitsInt.prototype.doPrintValue = function(fmt, value) {
        return value.toString(10);
    };

    /**
     * @constructor
     */
    glsBuiltinPrecisionTests.Statement = function() {

    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     */
    glsBuiltinPrecisionTests.Statement.prototype.execute = function(ctx) {
        this.doExecute(ctx);
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Statement.prototype.print = function() {
        return this.doPrint();
    };

    glsBuiltinPrecisionTests.Statement.prototype.toString = function() {
        return this.print();
    };

    /**
     * Output the functions that this expression refers to
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     *
     */
    glsBuiltinPrecisionTests.Statement.prototype.getUsedFuncs = function(dst) {
        this.doGetUsedFuncs(dst);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     */
    glsBuiltinPrecisionTests.Statement.prototype.doExecute = function(ctx) {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Statement.prototype.doPrint = function() {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * Output the functions that this expression refers to
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     *
     */
    glsBuiltinPrecisionTests.Statement.prototype.doGetUsedFuncs = function(dst) {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Statement}
     * @param {glsBuiltinPrecisionTests.Variable} variable
     * @param {glsBuiltinPrecisionTests.Expr} value
     * @param {boolean} isDeclaration
     */
    glsBuiltinPrecisionTests.VariableStatement = function(variable, value, isDeclaration) {
        this.m_variable = variable;
        this.m_value = value;
        this.m_isDeclaration = isDeclaration;

    };

    setParentClass(glsBuiltinPrecisionTests.VariableStatement, glsBuiltinPrecisionTests.Statement);

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     */
    glsBuiltinPrecisionTests.VariableStatement.prototype.doExecute = function(ctx) {
        ctx.env.bind(this.m_variable, this.m_value.evaluate(ctx));
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.VariableStatement.prototype.doPrint = function() {
        var v = this.m_variable;
        var os = '';
        if (this.m_isDeclaration)
            os += gluVarType.declareVariable(gluVarType.getVarTypeOf(v.typename),
                        v.getName());
        else
            os += v.getName();

        os += ' = ' + this.m_value.printExpr() + ';\n';

        return os;
    };

    /**
     * Output the functions that this expression refers to
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     *
     */
    glsBuiltinPrecisionTests.VariableStatement.prototype.doGetUsedFuncs = function(dst) {
        this.m_value.getUsedFuncs(dst);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Variable} variable
     * @param {glsBuiltinPrecisionTests.Expr} definiens
     * @return {glsBuiltinPrecisionTests.VariableStatement}
     */
    glsBuiltinPrecisionTests.variableDeclaration = function(variable, definiens) {
        return new glsBuiltinPrecisionTests.VariableStatement(variable, definiens, true);
    };

    /**
     * @param {string} typename
     * @param {string} name
     * @param {glsBuiltinPrecisionTests.ExpandContext} ctx
     * @param {glsBuiltinPrecisionTests.Expr} expr
     * @return {glsBuiltinPrecisionTests.Variable}
     */
    glsBuiltinPrecisionTests.bindExpression = function(typename, name, ctx, expr) {
        var variable = ctx.genSym(typename, name);
        ctx.addStatement(glsBuiltinPrecisionTests.variableDeclaration(variable, expr));
        return variable;
    };

    /**
     * Common base class for all expressions regardless of their type.
     * @constructor
     */
    glsBuiltinPrecisionTests.ExprBase = function() {};

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.ExprBase.prototype.printExpr = function() {
        return this.doPrintExpr();
    };

    glsBuiltinPrecisionTests.ExprBase.prototype.toString = function() {
        return this.printExpr();
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.ExprBase.prototype.doPrintExpr = function() {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * Output the functions that this expression refers to
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     *
     */
    glsBuiltinPrecisionTests.ExprBase.prototype.getUsedFuncs = function(/*FuncSet&*/ dst) {
        this.doGetUsedFuncs(dst);
    };

    /**
     * Output the functions that this expression refers to
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     *
     */
    glsBuiltinPrecisionTests.ExprBase.prototype.doGetUsedFuncs = function(/*FuncSet&*/ dst) {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * Type-specific operations for an expression representing type typename.
     * @constructor
     * @extends {glsBuiltinPrecisionTests.ExprBase}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     */
    glsBuiltinPrecisionTests.Expr = function(typename) {
        glsBuiltinPrecisionTests.ExprBase.call(this);
        this.typename = typename;
    };

    setParentClass(glsBuiltinPrecisionTests.Expr, glsBuiltinPrecisionTests.ExprBase);

    /**
     * Type-specific operations for an expression representing type typename.
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     */
    glsBuiltinPrecisionTests.Expr.prototype.evaluate = function(ctx) {
        return this.doEvaluate(ctx);
    };

    /**
     * Type-specific operations for an expression representing type typename.
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     */
    glsBuiltinPrecisionTests.Expr.prototype.doEvaluate = function(ctx) {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Expr}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     * @param {string=} name
     */
    glsBuiltinPrecisionTests.Variable = function(typename, name) {
        glsBuiltinPrecisionTests.Expr.call(this, typename);
        /** @type {string} */ this.m_name = name || '<undefined>';
    };

    setParentClass(glsBuiltinPrecisionTests.Variable, glsBuiltinPrecisionTests.Expr);

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Variable.prototype.getName = function() {
        return this.m_name;
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Variable.prototype.doPrintExpr = function() {
        return this.m_name;
    };

    glsBuiltinPrecisionTests.Variable.prototype.toString = function() {
        return this.doPrintExpr();
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @return {*}
     */
    glsBuiltinPrecisionTests.Variable.prototype.doEvaluate = function(ctx) {
        return ctx.env.lookup(this);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Variable}
     * @param {*=} t
     */
    glsBuiltinPrecisionTests.Void = function(t) {
        glsBuiltinPrecisionTests.Variable.call(this, 'void');
    };

    setParentClass(glsBuiltinPrecisionTests.Void, glsBuiltinPrecisionTests.Variable);

    glsBuiltinPrecisionTests.Void.prototype.doEvaluate = function(ctx) {
        return undefined;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Variable}
     * @param {number} value
     */
    glsBuiltinPrecisionTests.Constant = function(value) {
        glsBuiltinPrecisionTests.Variable.call(this, 'float');
        this.m_value = value;
    };

    setParentClass(glsBuiltinPrecisionTests.Constant, glsBuiltinPrecisionTests.Variable);

    glsBuiltinPrecisionTests.Constant.prototype.doEvaluate = function(ctx) {
        return new tcuInterval.Interval(this.m_value);
    };

    /**
     * @constructor
     * @param {*} typename
     */
    glsBuiltinPrecisionTests.DefaultSampling = function(typename) {
        this.typename = typename;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Expr}
     * @param {glsBuiltinPrecisionTests.Variable} vector
     * @param {number} index
     */
    glsBuiltinPrecisionTests.VectorVariable = function(vector, index) {
        glsBuiltinPrecisionTests.Expr.call(this, vector.typename);
        this.m_vector = vector;
        this.m_index = index;
    };

    setParentClass(glsBuiltinPrecisionTests.VectorVariable, glsBuiltinPrecisionTests.Expr);

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.VectorVariable.prototype.doEvaluate = function(ctx) {
        var tmp = this.m_vector.doEvaluate(ctx);
        return tmp[this.m_index];
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Expr}
     * @param {glsBuiltinPrecisionTests.Variable} matrix
     * @param {number} row
     * @param {number} col
     */
    glsBuiltinPrecisionTests.MatrixVariable = function(matrix, row, col) {
        glsBuiltinPrecisionTests.Expr.call(this, matrix.typename);
        this.m_matrix = matrix;
        this.m_row = row;
        this.m_col = col;
    };

    setParentClass(glsBuiltinPrecisionTests.MatrixVariable, glsBuiltinPrecisionTests.Expr);

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.MatrixVariable.prototype.doEvaluate = function(ctx) {
        var tmp = this.m_matrix.doEvaluate(ctx);
        return tmp.get(this.m_row, this.m_col);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Expr}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     * @param {glsBuiltinPrecisionTests.Func} func
     * @param {glsBuiltinPrecisionTests.Expr=} arg0
     * @param {glsBuiltinPrecisionTests.Expr=} arg1
     * @param {glsBuiltinPrecisionTests.Expr=} arg2
     * @param {glsBuiltinPrecisionTests.Expr=} arg3
     */
    glsBuiltinPrecisionTests.Apply = function(typename, func, arg0, arg1, arg2, arg3) {
        glsBuiltinPrecisionTests.Expr.call(this, typename);
        this.m_func = func;
        /** @type {glsBuiltinPrecisionTests.Tuple4} */ this.m_args;
        if (arg0 instanceof glsBuiltinPrecisionTests.Tuple4)
            this.m_args = /** @type {glsBuiltinPrecisionTests.Tuple4} */ (arg0);
        else {
            this.m_args = new glsBuiltinPrecisionTests.Tuple4(arg0 || new glsBuiltinPrecisionTests.Void(),
                                                              arg1 || new glsBuiltinPrecisionTests.Void(),
                                                              arg2 || new glsBuiltinPrecisionTests.Void(),
                                                              arg3 || new glsBuiltinPrecisionTests.Void());
        }
    };

    setParentClass(glsBuiltinPrecisionTests.Apply, glsBuiltinPrecisionTests.Expr);

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Apply.prototype.doPrintExpr = function() {
        var args = [this.m_args.a, this.m_args.b, this.m_args.c, this.m_args.d];
        return this.m_func.print(args);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @return {glsBuiltinPrecisionTests.Intervals}
     */
    glsBuiltinPrecisionTests.Apply.prototype.doEvaluate = function(ctx) {
        var debug = false;

        if (debug) {
            glsBuiltinPrecisionTests.Apply.prototype.doEvaluate.level = glsBuiltinPrecisionTests.Apply.prototype.doEvaluate.level || 0;
            var level = glsBuiltinPrecisionTests.Apply.prototype.doEvaluate.level;
            glsBuiltinPrecisionTests.Apply.prototype.doEvaluate.level++;
            var name = this.m_func.constructor.toString();
            name = name.replace(/[\s\S]*glsBuiltinPrecisionTests\./m, '').replace(/\.call[\s\S]*/m, '');
            if (this.m_func.getName)
                name += ' ' + this.m_func.getName();
            console.log('<' + level + '> Function ' + name);
        }

        var a = this.m_args.a.evaluate(ctx);
        var b = this.m_args.b.evaluate(ctx);
        var c = this.m_args.c.evaluate(ctx);
        var d = this.m_args.d.evaluate(ctx);
        var retVal = this.m_func.applyFunction(ctx, a, b, c, d);

        if (debug) {
            console.log('<' + level + '> a: ' + a);
            console.log('<' + level + '> b: ' + b);
            console.log('<' + level + '> returning: ' + retVal);
            glsBuiltinPrecisionTests.Apply.prototype.doEvaluate.level--;
        }
        return retVal;
    };

    /**
     * @param {glsBuiltinPrecisionTests.Func} func
     * @param {glsBuiltinPrecisionTests.Expr=} arg0
     * @param {glsBuiltinPrecisionTests.Expr=} arg1
     * @param {glsBuiltinPrecisionTests.Expr=} arg2
     * @param {glsBuiltinPrecisionTests.Expr=} arg3
     */
    var app = function(func, arg0, arg1, arg2, arg3) {
        return new glsBuiltinPrecisionTests.Apply('float', func, arg0, arg1, arg2, arg3);
    };

    /**
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     */
    glsBuiltinPrecisionTests.Apply.prototype.doGetUsedFuncs = function(dst) {
        this.m_func.getUsedFuncs(dst);
        this.m_args.a.getUsedFuncs(dst);
        this.m_args.b.getUsedFuncs(dst);
        this.m_args.c.getUsedFuncs(dst);
        this.m_args.d.getUsedFuncs(dst);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Apply}
     * @param {glsBuiltinPrecisionTests.Func} func
     * @param {glsBuiltinPrecisionTests.Expr=} arg0
     * @param {glsBuiltinPrecisionTests.Expr=} arg1
     * @param {glsBuiltinPrecisionTests.Expr=} arg2
     * @param {glsBuiltinPrecisionTests.Expr=} arg3
     */
    glsBuiltinPrecisionTests.ApplyScalar = function(func, arg0, arg1, arg2, arg3) {
        glsBuiltinPrecisionTests.Apply.call(this, 'float', func, arg0, arg1, arg2, arg3);
    };

    setParentClass(glsBuiltinPrecisionTests.ApplyScalar, glsBuiltinPrecisionTests.Apply);

    glsBuiltinPrecisionTests.ApplyScalar.prototype.doEvaluate = function(ctx) {
        var debug = false;

        if (debug) {
            glsBuiltinPrecisionTests.ApplyScalar.prototype.doEvaluate.level = glsBuiltinPrecisionTests.ApplyScalar.prototype.doEvaluate.level || 0;
            var level = glsBuiltinPrecisionTests.ApplyScalar.prototype.doEvaluate.level;
            glsBuiltinPrecisionTests.ApplyScalar.prototype.doEvaluate.level++;
            var name = this.m_func.constructor.toString();
            name = name.replace(/[\s\S]*glsBuiltinPrecisionTests\./m, '').replace(/\.call[\s\S]*/m, '');
            if (this.m_func.getName)
                name += ' ' + this.m_func.getName();
            console.log('scalar<' + level + '> Function ' + name);
        }

        var a = this.m_args.a.evaluate(ctx);
        var b = this.m_args.b.evaluate(ctx);
        var c = this.m_args.c.evaluate(ctx);
        var d = this.m_args.d.evaluate(ctx);
        if (a instanceof Array) {
            var ret = [];
            for (var i = 0; i < a.length; i++) {
                var p0 = a instanceof Array ? a[i] : a;
                var p1 = b instanceof Array ? b[i] : b;
                var p2 = c instanceof Array ? c[i] : c;
                var p3 = d instanceof Array ? d[i] : d;
                ret.push(this.m_func.applyFunction(ctx, p0, p1, p2, p3));
            }
            return ret;
        }

        var retVal = this.m_func.applyFunction(ctx, a, b, c, d);

        if (debug) {
            console.log('scalar<' + level + '> a: ' + a);
            console.log('scalar<' + level + '> b: ' + b);
            console.log('scalar<' + level + '> return1: ' + ret);
            console.log('scalar<' + level + '> return2: ' + retVal);
            glsBuiltinPrecisionTests.Apply.prototype.doEvaluate.level--;
        }

        return retVal;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Apply}
     */
    glsBuiltinPrecisionTests.ApplyVar = function(typename, func, arg0, arg1, arg2, arg3) {
        glsBuiltinPrecisionTests.Apply.call(this, typename, func, arg0, arg1, arg2, arg3);
    };

    setParentClass(glsBuiltinPrecisionTests.ApplyVar, glsBuiltinPrecisionTests.Apply);

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @return {glsBuiltinPrecisionTests.Intervals}
     */
    glsBuiltinPrecisionTests.ApplyVar.prototype.doEvaluate = function(ctx) {
        return this.m_func.applyFunction(ctx,
                    ctx.env.lookup(this.m_args.a), ctx.env.lookup(this.m_args.b),
                    ctx.env.lookup(this.m_args.c), ctx.env.lookup(this.m_args.d),
                    [this.m_args.a.getName(), this.m_args.b.getName(),
                    this.m_args.c.getName(), this.m_args.d.getName()]);
    };

    /**
     * @constructor
     */
    glsBuiltinPrecisionTests.FuncBase = function() {};

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.FuncBase.prototype.getName = function() {
        return '';
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.FuncBase.prototype.getRequiredExtension = function() {
        return '';
    };

    /**
     * @param {Array<glsBuiltinPrecisionTests.ExprBase>} args
     * @return {string}
     */
    glsBuiltinPrecisionTests.FuncBase.prototype.print = function(args) {
        return '';
    };

    /**
     * Index of output parameter, or -1 if none of the parameters is output.
     * @return {number}
     */
    glsBuiltinPrecisionTests.FuncBase.prototype.getOutParamIndex = function() {
        return -1;
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.FuncBase.prototype.printDefinition = function() {
        return this.doPrintDefinition();
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.FuncBase.prototype.doPrintDefinition = function() {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * typedef set<const FuncBase*> FuncSet;
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     */
    glsBuiltinPrecisionTests.FuncBase.prototype.getUsedFuncs = function(dst) {
        this.doGetUsedFuncs(dst);
    };

    /**
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     */
    glsBuiltinPrecisionTests.FuncBase.prototype.doGetUsedFuncs = function(dst) {};

    /*************************************/
    /**
     * \brief Function objects.
     *
     * Each Func object represents a GLSL function. It can be applied to interval
     * arguments, and it returns the an interval that is a conservative
     * approximation of the image of the GLSL function over the argument
     * intervals. That is, it is given a set of possible arguments and it returns
     * the set of possible values.
     *
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FuncBase}
     * @param {glsBuiltinPrecisionTests.Signature} Sig_ template <typename Sig_>
     */
    glsBuiltinPrecisionTests.Func = function(Sig_) {
        glsBuiltinPrecisionTests.FuncBase.call(this);
        this.Sig = Sig_;
        this.Ret = this.Sig.Ret;
        this.Arg0 = this.Sig.Arg0;
        this.Arg1 = this.Sig.Arg1;
        this.Arg2 = this.Sig.Arg2;
        this.Arg3 = this.Sig.Arg3;
    };

    glsBuiltinPrecisionTests.Func.prototype = Object.create(glsBuiltinPrecisionTests.FuncBase.prototype);
    glsBuiltinPrecisionTests.Func.prototype.constructor = glsBuiltinPrecisionTests.Func;

    /**
     * @param {Array<glsBuiltinPrecisionTests.ExprBase>} args
     * @return {string}
     */
    glsBuiltinPrecisionTests.Func.prototype.print = function(args) {
        return this.doPrint(args);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Intervals=} Iarg0
     * @param {glsBuiltinPrecisionTests.Intervals=} Iarg1
     * @param {glsBuiltinPrecisionTests.Intervals=} Iarg2
     * @param {glsBuiltinPrecisionTests.Intervals=} Iarg3
     * @return {glsBuiltinPrecisionTests.Intervals}
     */
    glsBuiltinPrecisionTests.Func.prototype.applyFunction = function(ctx, Iarg0, Iarg1, Iarg2, Iarg3, variablenames) {
        return this.applyArgs(ctx, new glsBuiltinPrecisionTests.Tuple4(Iarg0, Iarg1, Iarg2, Iarg3), variablenames);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Tuple4} args
     * @return {glsBuiltinPrecisionTests.Intervals}
     */
    glsBuiltinPrecisionTests.Func.prototype.applyArgs = function(ctx, args, variablenames) {
        return this.doApply(ctx, args, variablenames);
    };

    /**
     * @return {glsBuiltinPrecisionTests.ParamNames}
     */
    glsBuiltinPrecisionTests.Func.prototype.getParamNames = function() {
        return this.doGetParamNames();
    };

    /**
     * @param {Array<glsBuiltinPrecisionTests.ExprBase>} args
     * @return {string}
     */
    glsBuiltinPrecisionTests.Func.prototype.doPrint = function(args) {
        /** type{string} */ var os = this.getName() + '(';

        // TODO: fix the generics
        for (var i = 0; i < args.length; i++)
            if (glsBuiltinPrecisionTests.isTypeValid(args[i].typename)) {
                if (i != 0)
                    os += ', ';
                os += args[i];
            }

        os += ')';

        return os;
    };

    /**
     * @return {glsBuiltinPrecisionTests.ParamNames} args
     */
    glsBuiltinPrecisionTests.Func.prototype.doGetParamNames = function() {
        /** @type {glsBuiltinPrecisionTests.ParamNames} */ var names = new glsBuiltinPrecisionTests.Tuple4('a', 'b', 'c', 'd');
        return names;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Func}
     * @param {glsBuiltinPrecisionTests.Signature} Sig template <typename Sig>
     *
     */
    glsBuiltinPrecisionTests.PrimitiveFunc = function(Sig) {
        glsBuiltinPrecisionTests.Func.call(this, Sig);
        this.Ret = Sig.Ret;
    };

    glsBuiltinPrecisionTests.PrimitiveFunc.prototype = Object.create(glsBuiltinPrecisionTests.Func.prototype);
    glsBuiltinPrecisionTests.PrimitiveFunc.prototype.constructor = glsBuiltinPrecisionTests.PrimitiveFunc;

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     * @param {string} typename
     *
     */
    glsBuiltinPrecisionTests.Cond = function(typename) {
        var sig = new glsBuiltinPrecisionTests.Signature(typename, 'boolean', typename, typename);
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Cond, glsBuiltinPrecisionTests.PrimitiveFunc);

    glsBuiltinPrecisionTests.Cond.prototype.getName = function() {
        return '_cond';
    };

    glsBuiltinPrecisionTests.Cond.prototype.doPrint = function(args) {
        var str = '(' + args[0] + ' ? ' + args[1] + ' : ' + args[2] + ')';
        return str;
    };

    glsBuiltinPrecisionTests.Cond.prototype.doApply = function(ctx, iargs) {
        var ret;
        if (glsBuiltinPrecisionTests.contains(this.Sig.Arg0, iargs.a, 1))
            ret = iargs.b;
        if (glsBuiltinPrecisionTests.contains(this.Sig.Arg0, iargs.a, 0)) {
            if (ret)
                ret = glsBuiltinPrecisionTests.union(this.Sig.Ret, ret, iargs.c);
            else
                ret = iargs.c;
        }
        if (ret)
            return ret;
        return new tcuInterval.Interval();
    };

    /**
     * If multipleInputs is false, GenVec duplicates first input to proper size
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     * @param {number} size
     * @param {boolean=} multipleInputs
     */
    glsBuiltinPrecisionTests.GenVec = function(size, multipleInputs) {
        var vecName = glsBuiltinPrecisionTests.sizeToName(size);
        var p = [
            size >= 1 ? 'float' : undefined,
            size >= 2 ? 'float' : undefined,
            size >= 3 ? 'float' : undefined,
            size >= 4 ? 'float' : undefined
        ];
        var sig = new glsBuiltinPrecisionTests.Signature(vecName, p[0], p[1], p[2], p[3]);
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
        this.size = size;
        this.vecName = vecName;
        this.multipleInputs = multipleInputs || false;
    };

    setParentClass(glsBuiltinPrecisionTests.GenVec, glsBuiltinPrecisionTests.PrimitiveFunc);

    glsBuiltinPrecisionTests.GenVec.prototype.getName = function() {
        return this.vecName;
    };

    glsBuiltinPrecisionTests.GenVec.prototype.doApply = function(ctx, iargs) {
        if (this.size == 1)
            return iargs.a;

        var ret = this.multipleInputs ?
                        [iargs.a, iargs.b, iargs.c, iargs.d] :
                        [iargs.a, iargs.a, iargs.a, iargs.a];

        return ret.slice(0, this.size);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     * @param {number} rows
     * @param {number} cols
     */
    glsBuiltinPrecisionTests.GenMat = function(rows, cols) {
        var name = glsBuiltinPrecisionTests.dataTypeNameOfMatrix('float', rows, cols);
        var vecName = glsBuiltinPrecisionTests.sizeToName(rows);
        var p = [
            cols >= 1 ? vecName : undefined,
            cols >= 2 ? vecName : undefined,
            cols >= 3 ? vecName : undefined,
            cols >= 4 ? vecName : undefined
        ];
        var sig = new glsBuiltinPrecisionTests.Signature(name, p[0], p[1], p[2], p[3]);
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
        this.rows = rows;
        this.cols = cols;
        this.name = name;
        this.vecName = vecName;
    };

    setParentClass(glsBuiltinPrecisionTests.GenMat, glsBuiltinPrecisionTests.PrimitiveFunc);

    glsBuiltinPrecisionTests.GenMat.prototype.getName = function() {
        return this.name;
    };

    glsBuiltinPrecisionTests.GenMat.prototype.doApply = function(ctx, iargs) {
        var ret = new tcuMatrix.Matrix(this.rows, this.cols);
        var inputs = [iargs.a, iargs.b, iargs.c, iargs.d];

        for (var i = 0; i < this.rows; i++)
            for (var j = 0; j < this.cols; j++)
                ret.set(i, j, inputs[j][i]);
        return ret;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     * @param {string} typename
     *
     */
    glsBuiltinPrecisionTests.CompareOperator = function(typename) {
        var sig = new glsBuiltinPrecisionTests.Signature('boolean', typename, typename);
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.CompareOperator, glsBuiltinPrecisionTests.PrimitiveFunc);

    glsBuiltinPrecisionTests.CompareOperator.prototype.doPrint = function(args) {
        var str = '(' + args[0] + this.getSymbol() + args[1] + ')';
        return str;
    };

    glsBuiltinPrecisionTests.CompareOperator.prototype.doApply = function(ctx, iargs) {
        var arg0 = iargs.a;
        var arg1 = iargs.b;

        var ret = new tcuInterval.Interval();

        if (this.canSucceed(arg0, arg1))
            ret = new tcuInterval.Interval(1);
        if (this.canFail(arg0, arg1))
            ret.operatorOrAssignBinary(new tcuInterval.Interval(0));

        return ret;
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.CompareOperator.prototype.getSymbol = function() {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @param {tcuInterval.Interval} arg0
     * @param {tcuInterval.Interval} arg1
     * @return {boolean}
     */
    glsBuiltinPrecisionTests.CompareOperator.prototype.canSucceed = function(arg0, arg1) {
        throw new Error('Virtual function. Please override.');
    };
    /**
     * @param {tcuInterval.Interval} arg0
     * @param {tcuInterval.Interval} arg1
     * @return {boolean}
     */
    glsBuiltinPrecisionTests.CompareOperator.prototype.canFail = function(arg0, arg1) {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CompareOperator}
     * @param {string} typename
     *
     */
    glsBuiltinPrecisionTests.LessThan = function(typename) {
        glsBuiltinPrecisionTests.CompareOperator.call(this, typename);
    };

    setParentClass(glsBuiltinPrecisionTests.LessThan, glsBuiltinPrecisionTests.CompareOperator);

    glsBuiltinPrecisionTests.LessThan.prototype.getSymbol = function() {
        return '<';
    };

    glsBuiltinPrecisionTests.LessThan.prototype.canSucceed = function(a, b) {
       return (a.lo() < b.hi());
    };

    glsBuiltinPrecisionTests.LessThan.prototype.canFail = function(a, b) {
        return !(a.hi() < b.lo());
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     *
     */
    glsBuiltinPrecisionTests.FloatFunc1 = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
    };

    glsBuiltinPrecisionTests.FloatFunc1.prototype = Object.create(glsBuiltinPrecisionTests.PrimitiveFunc.prototype);
    glsBuiltinPrecisionTests.FloatFunc1.prototype.constructor = glsBuiltinPrecisionTests.FloatFunc1;

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Tuple4} iargs
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc1.prototype.doApply = function(ctx, iargs) {
        var a = /** @type {tcuInterval.Interval} */ (iargs.a);
        return this.applyMonotone(ctx, a);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {tcuInterval.Interval} iarg0
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc1.prototype.applyMonotone = function(ctx, iarg0) {
        /** @type {tcuInterval.Interval} */ var ret = new tcuInterval.Interval();

        /**
         * @param {number=} x
         * @param {number=} y
         * @return {tcuInterval.Interval}
         */
        var body = function(x, y) {
            x = x || 0;
            return this.applyPoint(ctx, x);
        };
        ret = tcuInterval.applyMonotone1(iarg0, body.bind(this));

        ret.operatorOrAssignBinary(this.innerExtrema(ctx, iarg0));

        ret.operatorAndAssignBinary(this.getCodomain().operatorOrBinary(new tcuInterval.Interval(NaN)));

        return ctx.format.convert(ret);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {tcuInterval.Interval} iargs
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc1.prototype.innerExtrema = function(ctx, iargs) {
        return new tcuInterval.Interval(); // empty interval, i.e. no extrema
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {number} arg0
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc1.prototype.applyPoint = function(ctx, arg0) {
        var exact = this.applyExact(arg0);
        var prec = this.precision(ctx, exact, arg0);

        var a = new tcuInterval.Interval(exact);
        var b = tcuInterval.withNumbers(-prec, prec);
        return tcuInterval.Interval.operatorSum(a, b);
    };

    /**
     * @param {number} x
     * @return {number}
     */
    glsBuiltinPrecisionTests.FloatFunc1.prototype.applyExact = function(x) {
        throw new Error('Internal error. Cannot apply');
    };

    /**
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc1.prototype.getCodomain = function() {
        return tcuInterval.unbounded(true);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {number} x
     * @param {number} y
     * @return {number}
     */
    glsBuiltinPrecisionTests.FloatFunc1.prototype.precision = function(ctx, x, y) {
        return 0;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FloatFunc1}
     */
    glsBuiltinPrecisionTests.Negate = function() {
        glsBuiltinPrecisionTests.FloatFunc1.call(this);
    };

    setParentClass(glsBuiltinPrecisionTests.Negate, glsBuiltinPrecisionTests.FloatFunc1);

    glsBuiltinPrecisionTests.Negate.prototype.getName = function() {
        return '_negate';
    };

    glsBuiltinPrecisionTests.Negate.prototype.doPrint = function(args) {
        return '-' + args[0];
    };

    glsBuiltinPrecisionTests.Negate.prototype.precision = function(ctx, ret, x) {
        return 0;
    };
    glsBuiltinPrecisionTests.Negate.prototype.applyExact = function(x) {
        return -x;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FloatFunc1}
     */
    glsBuiltinPrecisionTests.InverseSqrt = function() {
        glsBuiltinPrecisionTests.FloatFunc1.call(this);
    };

    setParentClass(glsBuiltinPrecisionTests.InverseSqrt, glsBuiltinPrecisionTests.FloatFunc1);

    glsBuiltinPrecisionTests.InverseSqrt.prototype.getName = function() {
        return 'inversesqrt';
    };

    glsBuiltinPrecisionTests.InverseSqrt.prototype.precision = function(ctx, ret, x) {
        if (x <= 0)
            return NaN;
        return ctx.format.ulp(ret, 2.0);
    };

    glsBuiltinPrecisionTests.InverseSqrt.prototype.applyExact = function(x) {
        return 1 / Math.sqrt(x);
    };

    glsBuiltinPrecisionTests.InverseSqrt.prototype.getCodomain = function() {
        return tcuInterval.withNumbers(0, Infinity);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FloatFunc1}
     */
    glsBuiltinPrecisionTests.Round = function() {
        glsBuiltinPrecisionTests.FloatFunc1.call(this);
    };

    setParentClass(glsBuiltinPrecisionTests.Round, glsBuiltinPrecisionTests.FloatFunc1);

    glsBuiltinPrecisionTests.Round.prototype.getName = function() {
        return 'round';
    };

    glsBuiltinPrecisionTests.Round.prototype.precision = function(ctx, ret, x) {
        return 0;
    };

    glsBuiltinPrecisionTests.Round.prototype.applyPoint = function(ctx, x) {
        var truncated = Math.trunc(x);
        var fract = x - truncated;
        var ret = new tcuInterval.Interval();

        // When x is inf or -inf, truncated would be inf or -inf too. Then fract
        // would be NaN (inf - inf). While in native c code, it would be 0 (inf) or -0 (-inf).
        // This behavior in JS differs from that in native c code.
        if (Math.abs(fract) <= 0.5 || isNaN(fract))
            ret.operatorOrAssignBinary(new tcuInterval.Interval(truncated));
        if (Math.abs(fract) >= 0.5)
            ret.operatorOrAssignBinary(new tcuInterval.Interval(truncated + deMath.deSign(fract)));

        return ret;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FloatFunc1}
     * @param {string} name
     * @param {tcuInterval.DoubleFunc1} func
     */
    glsBuiltinPrecisionTests.CFloatFunc1 = function(name, func) {
        glsBuiltinPrecisionTests.FloatFunc1.call(this);
        /** @type {string} */ this.m_name = name;
        /** @type {tcuInterval.DoubleFunc1} */this.m_func = func;
    };

    glsBuiltinPrecisionTests.CFloatFunc1.prototype = Object.create(glsBuiltinPrecisionTests.FloatFunc1.prototype);
    glsBuiltinPrecisionTests.CFloatFunc1.prototype.constructor = glsBuiltinPrecisionTests.CFloatFunc1;

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.CFloatFunc1.prototype.getName = function() {
        return this.m_name;
    };

    /**
     * @param {number} x
     * @return {number}
     */
    glsBuiltinPrecisionTests.CFloatFunc1.prototype.applyExact = function(x) {
        return this.m_func(x);
    };

    /**
     * PrimitiveFunc<Signature<float, float, float> >
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     */
    glsBuiltinPrecisionTests.FloatFunc2 = function() {
        /** @type {glsBuiltinPrecisionTests.Signature} */ var Sig = new glsBuiltinPrecisionTests.Signature('float', 'float', 'float');
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, Sig);
    };

    glsBuiltinPrecisionTests.FloatFunc2.prototype = Object.create(glsBuiltinPrecisionTests.PrimitiveFunc.prototype);
    glsBuiltinPrecisionTests.FloatFunc2.prototype.constructor = glsBuiltinPrecisionTests.FloatFunc2;

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Tuple4} iargs
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc2.prototype.doApply = function(ctx, iargs) {
        var a = /** @type {tcuInterval.Interval} */ (iargs.a);
        var b = /** @type {tcuInterval.Interval} */ (iargs.b);
        return this.applyMonotone(ctx, a, b);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {tcuInterval.Interval} xi
     * @param {tcuInterval.Interval} yi
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc2.prototype.applyMonotone = function(ctx, xi, yi) {
        /** @type {tcuInterval.Interval} */ var ret = new tcuInterval.Interval();

        /**
         * @param {number=} x
         * @param {number=} y
         * @return {tcuInterval.Interval}
         */
        var body = function(x, y) {
            x = x || 0;
            y = y || 0;
            return this.applyPoint(ctx, x, y);
        };
        ret = tcuInterval.applyMonotone2(xi, yi, body.bind(this));

        ret.operatorOrAssignBinary(this.innerExtrema(ctx, xi, yi));

        ret.operatorAndAssignBinary(this.getCodomain().operatorOrBinary(new tcuInterval.Interval(NaN)));

        return ctx.format.convert(ret);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {tcuInterval.Interval} xi
     * @param {tcuInterval.Interval} yi
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc2.prototype.innerExtrema = function(ctx, xi, yi) {
        return new tcuInterval.Interval(); // empty interval, i.e. no extrema
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {number} x
     * @param {number} y
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc2.prototype.applyPoint = function(ctx, x, y) {
        /** @type {number} */ var exact = this.applyExact(x, y);
        var prec = this.precision(ctx, exact, x, y);

        var a = new tcuInterval.Interval(exact);
        var b = tcuInterval.withNumbers(-prec, prec);
        return tcuInterval.Interval.operatorSum(a, b);
    };

    /**
     * @param {number} x
     * @param {number} y
     * @return {number}
     */
    glsBuiltinPrecisionTests.FloatFunc2.prototype.applyExact = function(x, y) {
        throw new Error('Virtual function. Please override');
    };

    /**
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc2.prototype.getCodomain = function() {
        return tcuInterval.unbounded(true);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {number} ret
     * @param {number} x
     * @param {number} y
     * @return {number}
     */
    glsBuiltinPrecisionTests.FloatFunc2.prototype.precision = function(ctx, ret, x, y) {
        throw new Error('Virtual function. Please override');
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FloatFunc2}
     * @param {string} name
     * @param {tcuInterval.DoubleFunc2} func
     */
    glsBuiltinPrecisionTests.CFloatFunc2 = function(name, func) {
        glsBuiltinPrecisionTests.FloatFunc2.call(this);
        /** @type {string} */ this.m_name = name;
        /** @type {tcuInterval.DoubleFunc2} */ this.m_func = func;
    };

    glsBuiltinPrecisionTests.CFloatFunc2.prototype = Object.create(glsBuiltinPrecisionTests.FloatFunc2.prototype);
    glsBuiltinPrecisionTests.CFloatFunc2.prototype.constructor = glsBuiltinPrecisionTests.CFloatFunc2;

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.CFloatFunc2.prototype.getName = function() {
        return this.m_name;
    };

    /**
     * @param {number} x
     * @param {number} y
     * @return {number}
     */
    glsBuiltinPrecisionTests.CFloatFunc2.prototype.applyExact = function(x, y) {
        return this.m_func(x, y);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FloatFunc2}
     */
    glsBuiltinPrecisionTests.InfixOperator = function() {
        glsBuiltinPrecisionTests.FloatFunc2.call(this);
    };

    glsBuiltinPrecisionTests.InfixOperator.prototype = Object.create(glsBuiltinPrecisionTests.FloatFunc2.prototype);
    glsBuiltinPrecisionTests.InfixOperator.prototype.constructor = glsBuiltinPrecisionTests.InfixOperator;

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.InfixOperator.prototype.getSymbol = function() {
        glsBuiltinPrecisionTests.FloatFunc2.call(this);
        return '';
    };

    /**
     * @param {Array<glsBuiltinPrecisionTests.ExprBase>} args
     * @return {string}
     */
    glsBuiltinPrecisionTests.InfixOperator.prototype.doPrint = function(args) {
        return '(' + args[0] + ' ' + this.getSymbol() + ' ' + args[1] + ')';
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {number} x
     * @param {number} y
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.InfixOperator.prototype.applyPoint = function(ctx, x, y) {
        /** @type {number} */ var exact = this.applyExact(x, y);

        // Allow either representable number on both sides of the exact value,
        // but require exactly representable values to be preserved.
        return ctx.format.roundOut(new tcuInterval.Interval(exact), isFinite(x) && isFinite(y));
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @return {number}
     */
    glsBuiltinPrecisionTests.InfixOperator.prototype.precision = function(ctx, x, y, z) {
        return 0;
    };

    /**
     * Signature<float, float, float, float>
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     */
    glsBuiltinPrecisionTests.FloatFunc3 = function() {
        /** @type {glsBuiltinPrecisionTests.Signature} */ var sig = new glsBuiltinPrecisionTests.Signature('float', 'float', 'float', 'float');
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
    };

    glsBuiltinPrecisionTests.FloatFunc3.prototype = Object.create(glsBuiltinPrecisionTests.PrimitiveFunc.prototype);
    glsBuiltinPrecisionTests.FloatFunc3.prototype.constructor = glsBuiltinPrecisionTests.FloatFunc3;

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Tuple4} iargs
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc3.prototype.doApply = function(ctx, iargs) {
        var a = /** @type {tcuInterval.Interval} */ (iargs.a);
        var b = /** @type {tcuInterval.Interval} */ (iargs.b);
        var c = /** @type {tcuInterval.Interval} */ (iargs.c);
        var retVal = this.applyMonotone(ctx, a, b, c);
        return retVal;
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {tcuInterval.Interval} xi
     * @param {tcuInterval.Interval} yi
     * @param {tcuInterval.Interval} zi
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc3.prototype.applyMonotone = function(ctx, xi, yi, zi) {
        /**
         * @param {number=} x
         * @param {number=} y
         * @param {number=} z
         * @return {tcuInterval.Interval}
         */
        var body = function(x, y, z) {
            x = x || 0;
            y = y || 0;
            z = z || 0;
            return this.applyPoint(ctx, x, y, z);
        };
        var ret = tcuInterval.applyMonotone3(xi, yi, zi, body.bind(this));
        var retVal;

        ret.operatorOrAssignBinary(this.innerExtrema(ctx, xi, yi, zi));

        ret.operatorAndAssignBinary(this.getCodomain().operatorOrBinary(new tcuInterval.Interval(NaN)));

        retVal = ctx.format.convert(ret);
        return retVal;
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {tcuInterval.Interval} xi
     * @param {tcuInterval.Interval} yi
     * @param {tcuInterval.Interval} zi
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc3.prototype.innerExtrema = function(ctx, xi, yi, zi) {
        return new tcuInterval.Interval(); // empty interval, i.e. no extrema
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc3.prototype.applyPoint = function(ctx, x, y, z) {
        /** @type {number} */ var exact = this.applyExact(x, y, z);
        /** @type {number} */ var prec = this.precision(ctx, exact, x, y, z);

        var a = new tcuInterval.Interval(exact);
        var b = tcuInterval.withNumbers(-prec, prec);
        return tcuInterval.Interval.operatorSum(a, b);
    };

    /**
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @return {number}
     */
    glsBuiltinPrecisionTests.FloatFunc3.prototype.applyExact = function(x, y, z) {
        throw new Error('Virtual function. Please override');
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {number} result
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @return {number}
     */
    glsBuiltinPrecisionTests.FloatFunc3.prototype.precision = function(ctx, result, x, y, z) {
        throw new Error('Virtual function. Please override');
    };

    /**
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.FloatFunc3.prototype.getCodomain = function() {
        return tcuInterval.unbounded(true);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FloatFunc3}
     */
    glsBuiltinPrecisionTests.Clamp = function() {
        glsBuiltinPrecisionTests.FloatFunc3.call(this);
    };

    setParentClass(glsBuiltinPrecisionTests.Clamp, glsBuiltinPrecisionTests.FloatFunc3);

    glsBuiltinPrecisionTests.Clamp.prototype.getName = function() {
        return 'clamp';
    };

    glsBuiltinPrecisionTests.Clamp.prototype.applyExact = function(x, minVal, maxVal) {
        var debug = false;
        var retVal;

        retVal = deMath.clamp(x, minVal, maxVal);
        if (debug) {
            console.log('> minVal: ' + minVal);
            console.log('> maxVal: ' + maxVal);
            console.log('> x: ' + x);
            console.log('> ret: ' + retVal);
        }
        return retVal;

    };

    glsBuiltinPrecisionTests.Clamp.prototype.precision = function(ctx, result, x, minVal, maxVal) {
        var debug = false;
        var retVal;

        retVal = minVal > maxVal ? NaN : 0;

        if (debug) {
            console.log('precision> minVal: ' + minVal);
            console.log('precision> maxVal: ' + maxVal);
            console.log('precision> x: ' + x);
            console.log('precision> ret: ' + retVal);
        }

        return retVal;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.InfixOperator}
     */
    glsBuiltinPrecisionTests.Add = function() {
        glsBuiltinPrecisionTests.InfixOperator.call(this);
    };

    glsBuiltinPrecisionTests.Add.prototype = Object.create(glsBuiltinPrecisionTests.InfixOperator.prototype);
    glsBuiltinPrecisionTests.Add.prototype.constructor = glsBuiltinPrecisionTests.Add;

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Add.prototype.getName = function() {
        return 'add';
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Add.prototype.getSymbol = function() {
        return '+';
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Tuple4} iargs
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.Add.prototype.doApply = function(ctx, iargs) {
        var a = /** @type {tcuInterval.Interval} */ (iargs.a);
        var b = /** @type {tcuInterval.Interval} */ (iargs.b);
        // Fast-path for common case
        if (iargs.a.isOrdinary() && iargs.b.isOrdinary()) {
            /** type{tcuInterval.Interval} */ var ret;
            ret = tcuInterval.setIntervalBounds(
                function(dummy) {
                    return iargs.a.lo() + iargs.b.lo();
                },
                function(dummy) {
                    return iargs.a.hi() + iargs.b.hi();
                });
            return ctx.format.convert(ctx.format.roundOut(ret, true));
        }
        return this.applyMonotone(ctx, a, b);
    };

    /**
     * @param {number} x
     * @param {number} y
     * @return {number}
     */
    glsBuiltinPrecisionTests.Add.prototype.applyExact = function(x, y) {
        return x + y;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.InfixOperator}
     */
    glsBuiltinPrecisionTests.Sub = function() {
        glsBuiltinPrecisionTests.InfixOperator.call(this);
    };

    glsBuiltinPrecisionTests.Sub.prototype = Object.create(glsBuiltinPrecisionTests.InfixOperator.prototype);
    glsBuiltinPrecisionTests.Sub.prototype.constructor = glsBuiltinPrecisionTests.Sub;

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Sub.prototype.getName = function() {
        return 'sub';
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Sub.prototype.getSymbol = function() {
        return '-';
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Tuple4} iargs
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.Sub.prototype.doApply = function(ctx, iargs) {
        var a = /** @type {tcuInterval.Interval} */ (iargs.a);
        var b = /** @type {tcuInterval.Interval} */ (iargs.b);
        var retVal;

        // Fast-path for common case
        if (iargs.a.isOrdinary() && iargs.b.isOrdinary()) {
            /** type{tcuInterval.Interval} */ var ret;
            ret = tcuInterval.setIntervalBounds(
                function(dummy) {
                    return iargs.a.lo() - iargs.b.hi();
                },
                function(dummy) {
                    return iargs.a.hi() - iargs.b.lo();
                });
            return ctx.format.convert(ctx.format.roundOut(ret, true));
        }
        retVal = this.applyMonotone(ctx, a, b);
        return retVal;
    };

    /**
     * @param {number} x
     * @param {number} y
     * @return {number}
     */
    glsBuiltinPrecisionTests.Sub.prototype.applyExact = function(x, y) {
        return x - y;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.InfixOperator}
     */
    glsBuiltinPrecisionTests.Mul = function() {
        glsBuiltinPrecisionTests.InfixOperator.call(this);
    };

    glsBuiltinPrecisionTests.Mul.prototype = Object.create(glsBuiltinPrecisionTests.InfixOperator.prototype);
    glsBuiltinPrecisionTests.Mul.prototype.constructor = glsBuiltinPrecisionTests.Mul;

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Mul.prototype.getName = function() {
        return 'mul';
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Mul.prototype.getSymbol = function() {
        return '*';
    };

    glsBuiltinPrecisionTests.isNegative = function(n) {
        return ((n = +n) || 1 / n) < 0;
    };

   /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Tuple4} iargs
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.Mul.prototype.doApply = function(ctx, iargs) {
        var a = /** @type {tcuInterval.Interval} */ (iargs.a);
        var b = /** @type {tcuInterval.Interval} */ (iargs.b);
        // Fast-path for common case
        if (iargs.a.isOrdinary() && iargs.b.isOrdinary()) {
            /** type{tcuInterval.Interval} */ var ret = new tcuInterval.Interval();
            if (glsBuiltinPrecisionTests.isNegative(a.hi())) {
                a = a.operatorNegative();
                b = b.operatorNegative();
            }
            if (a.lo() >= 0 && b.lo() >= 0) {
                ret = tcuInterval.setIntervalBounds(
                    function(dummy) {
                        return iargs.a.lo() * iargs.b.lo();
                    },
                    function(dummy) {
                        return iargs.a.hi() * iargs.b.hi();
                    });
                return ctx.format.convert(ctx.format.roundOut(ret, true));
            }
            if (a.lo() >= 0 && b.hi() <= 0) {
                ret = tcuInterval.setIntervalBounds(
                    function(dummy) {
                        return iargs.a.hi() * iargs.b.lo();
                    },
                    function(dummy) {
                        return iargs.a.lo() * iargs.b.hi();
                    });
                return ctx.format.convert(ctx.format.roundOut(ret, true));
            }
        }

        return this.applyMonotone(ctx, a, b);
    };

    /**
     * @param {number} x
     * @param {number} y
     * @return {number}
     */
    glsBuiltinPrecisionTests.Mul.prototype.applyExact = function(x, y) {
        return x * y;
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {tcuInterval.Interval} xi
     * @param {tcuInterval.Interval} yi
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.Mul.prototype.innerExtrema = function(ctx, xi, yi) {
        if (((xi.contains(tcuInterval.NEGATIVE_INFINITY) || xi.contains(tcuInterval.POSITIVE_INFINITY)) && yi.contains(tcuInterval.ZERO)) ||
            ((yi.contains(tcuInterval.NEGATIVE_INFINITY) || yi.contains(tcuInterval.POSITIVE_INFINITY)) && xi.contains(tcuInterval.ZERO)))
            return new tcuInterval.Interval(NaN);

        return new tcuInterval.Interval(); // empty interval, i.e. no extrema
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.InfixOperator}
     */
    glsBuiltinPrecisionTests.Div = function() {
        glsBuiltinPrecisionTests.InfixOperator.call(this);
    };

    glsBuiltinPrecisionTests.Div.prototype = Object.create(glsBuiltinPrecisionTests.InfixOperator.prototype);
    glsBuiltinPrecisionTests.Div.prototype.constructor = glsBuiltinPrecisionTests.Div;

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Div.prototype.getName = function() {
        return 'div';
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Div.prototype.getSymbol = function() {
        return '/';
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {tcuInterval.Interval} nom
     * @param {tcuInterval.Interval} den
     * @return {tcuInterval.Interval}
     */
    glsBuiltinPrecisionTests.Div.prototype.innerExtrema = function(ctx, nom, den) {
        var ret = new tcuInterval.Interval();
        if (den.contains(tcuInterval.ZERO)) {
            if (nom.contains(tcuInterval.ZERO))
                ret.operatorOrAssignBinary(tcuInterval.NAN);
            if (nom.lo() < 0 || nom.hi() > 0.0)
                ret.operatorOrAssignBinary(tcuInterval.unbounded());
        }

        return ret;
    };

    glsBuiltinPrecisionTests.Div.prototype.precision = function(ctx, ret, nom, den) {
        var fmt = ctx.format;

        // \todo [2014-03-05 lauri] Check that the limits in GLSL 3.10 are actually correct.
        // For now, we assume that division's precision is 2.5 ULP when the value is within
        // [2^MINEXP, 2^MAXEXP-1]

        if (den === 0)
            return 0; // Result must be exactly inf
        else if (deMath.deInBounds32(Math.abs(den),
                              deMath.deLdExp(1, fmt.getMinExp()),
                              deMath.deLdExp(1, fmt.getMaxExp() - 1)))
            return fmt.ulp(ret, 2.5);
        else
            return Infinity; // Can be any number, but must be a number.
    };

    /**
     * @param {number} x
     * @param {number} y
     * @return {number}
     */
    glsBuiltinPrecisionTests.Div.prototype.applyExact = function(x, y) {
        return x / y;
    };

    glsBuiltinPrecisionTests.Div.prototype.applyPoint = function(ctx, x, y) {
        var ret = glsBuiltinPrecisionTests.FloatFunc2.prototype.applyPoint.call(this, ctx, x, y);
        if (isFinite(x) && isFinite(y) && y != 0) {
            var dst = ctx.format.convert(ret);
            if (dst.contains(tcuInterval.NEGATIVE_INFINITY)) {
                ret.operatorOrAssignBinary(-ctx.format.getMaxValue());
            }
            if (dst.contains(tcuInterval.POSITIVE_INFINITY)) {
                ret.operatorOrAssignBinary(+ctx.format.getMaxValue());
            }
        }
        return ret;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     */
    glsBuiltinPrecisionTests.CompWiseFunc = function(typename, Sig) {
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, Sig);
        this.typename = typename;
    };

    setParentClass(glsBuiltinPrecisionTests.CompWiseFunc, glsBuiltinPrecisionTests.PrimitiveFunc);

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.CompWiseFunc.prototype.getName = function() {
        return this.doGetScalarFunc().getName();
    };

    /**
     * @param {Array<glsBuiltinPrecisionTests.ExprBase>} args
     * @return {string}
     */
    glsBuiltinPrecisionTests.CompWiseFunc.prototype.doPrint = function(args) {
        return this.doGetScalarFunc().print(args);
    };

    /**
     * @return {glsBuiltinPrecisionTests.Func}
     */
    glsBuiltinPrecisionTests.CompWiseFunc.prototype.doGetScalarFunc = function() {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CompWiseFunc}
     * @param {number} rows
     * @param {number} cols
     */
    glsBuiltinPrecisionTests.CompMatFuncBase = function(rows, cols) {
        var name = glsBuiltinPrecisionTests.dataTypeNameOfMatrix('float', rows, cols);
        glsBuiltinPrecisionTests.CompWiseFunc.call(this, 'float', new glsBuiltinPrecisionTests.Signature(name, name, name));
        this.rows = rows;
        this.cols = cols;
    };

    setParentClass(glsBuiltinPrecisionTests.CompMatFuncBase, glsBuiltinPrecisionTests.CompWiseFunc);

    glsBuiltinPrecisionTests.CompMatFuncBase.prototype.doApply = function(ctx, iargs) {
        var ret = new tcuMatrix.Matrix(this.rows, this.cols);
        var fun = this.doGetScalarFunc();

        for (var row = 0; row < this.rows; ++row)
            for (var col = 0; col < this.cols; ++col)
                ret.set(row, col, fun.applyFunction(ctx,
                                                  iargs.a.get(row, col),
                                                  iargs.b.get(row, col)));

        return ret;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CompMatFuncBase}
     * @param {function(new:glsBuiltinPrecisionTests.Func)} F
     * @param {number} rows
     * @param {number} cols
     */
    glsBuiltinPrecisionTests.CompMatFunc = function(F, rows, cols) {
        glsBuiltinPrecisionTests.CompMatFuncBase.call(this, rows, cols);
        this.m_function = F;
    };

    setParentClass(glsBuiltinPrecisionTests.CompMatFunc, glsBuiltinPrecisionTests.CompMatFuncBase);

    /**
     * @return {glsBuiltinPrecisionTests.Func}
     */
    glsBuiltinPrecisionTests.CompMatFunc.prototype.doGetScalarFunc = function() {
        return new this.m_function();
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Mul}
     */
    glsBuiltinPrecisionTests.ScalarMatrixCompMult = function() {
       glsBuiltinPrecisionTests.Mul.call(this);
    };

    setParentClass(glsBuiltinPrecisionTests.ScalarMatrixCompMult, glsBuiltinPrecisionTests.Mul);

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.ScalarMatrixCompMult.prototype.getName = function() {
        return 'matrixCompMult';
    };

    /**
     * @param {Array<glsBuiltinPrecisionTests.ExprBase>} args
     * @return {string}
     */
    glsBuiltinPrecisionTests.ScalarMatrixCompMult.prototype.doPrint = function(args) {
        return glsBuiltinPrecisionTests.Func.prototype.doPrint.call(this, args);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CompMatFunc}
     * @param {number} rows
     * @param {number} cols
     */
    glsBuiltinPrecisionTests.MatrixCompMult = function(rows, cols) {
        glsBuiltinPrecisionTests.CompMatFunc.call(this, glsBuiltinPrecisionTests.ScalarMatrixCompMult, rows, cols);
    };

    setParentClass(glsBuiltinPrecisionTests.MatrixCompMult, glsBuiltinPrecisionTests.CompMatFunc);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     * @param {number} rows
     * @param {number} cols
     */
    glsBuiltinPrecisionTests.OuterProduct = function(rows, cols) {
        var name = glsBuiltinPrecisionTests.dataTypeNameOfMatrix('float', rows, cols);
        var sig = new glsBuiltinPrecisionTests.Signature(name, 'vec' + rows, 'vec' + cols);
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
        this.rows = rows;
        this.cols = cols;
    };

    setParentClass(glsBuiltinPrecisionTests.OuterProduct, glsBuiltinPrecisionTests.PrimitiveFunc);

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.OuterProduct.prototype.getName = function() {
        return 'outerProduct';
    };

    glsBuiltinPrecisionTests.OuterProduct.prototype.doApply = function(ctx, iargs) {
        var ret = new tcuMatrix.Matrix(this.rows, this.cols);
        var mul = new glsBuiltinPrecisionTests.Mul();

        for (var row = 0; row < this.rows; ++row) {
            for (var col = 0; col < this.cols; ++col)
                ret.set(row, col, mul.applyFunction(ctx, iargs.a[row], iargs.b[col]));
        }

        return ret;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     * @param {number} rows
     * @param {number} cols
     */
    glsBuiltinPrecisionTests.Transpose = function(rows, cols) {
        var nameRet = glsBuiltinPrecisionTests.dataTypeNameOfMatrix('float', rows, cols);
        var nameParam = glsBuiltinPrecisionTests.dataTypeNameOfMatrix('float', cols, rows);
        var sig = new glsBuiltinPrecisionTests.Signature(nameRet, nameParam);
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
        this.rows = rows;
        this.cols = cols;
    };

    setParentClass(glsBuiltinPrecisionTests.Transpose, glsBuiltinPrecisionTests.PrimitiveFunc);

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.Transpose.prototype.getName = function() {
        return 'transpose';
    };

    glsBuiltinPrecisionTests.Transpose.prototype.doApply = function(ctx, iargs) {
        var ret = new tcuMatrix.Matrix(this.rows, this.cols);

        for (var row = 0; row < this.rows; ++row)
            for (var col = 0; col < this.cols; ++col)
                ret.set(row, col, iargs.a.get(col, row));

        return ret;
    };

    /**
     * @constructor
     * @param {*} In
     */
    glsBuiltinPrecisionTests.Inputs = function(In) {
        // vector<typename In::In0> in0;
        // vector<typename In::In1> in1;
        // vector<typename In::In2> in2;
        // vector<typename In::In3> in3;
        this.in0 = [];
        this.in1 = [];
        this.in2 = [];
        this.in3 = [];
    };

    /**
     * @constructor
     * @param {number} size
     * @param {*} Out
     */
    glsBuiltinPrecisionTests.Outputs = function(size, Out) {
        // Outputs (size_t size) : out0(size), out1(size) {}
        this.out0 = [];
        this.out1 = [];
    };

    /**
     * @constructor
     * @param {*} In
     * @param {*} Out
     */
     glsBuiltinPrecisionTests.Variables = function(In, Out) {
        this.in0 = new glsBuiltinPrecisionTests.Variable(In.In0);
        this.in1 = new glsBuiltinPrecisionTests.Variable(In.In1);
        this.in2 = new glsBuiltinPrecisionTests.Variable(In.In2);
        this.in3 = new glsBuiltinPrecisionTests.Variable(In.In3);
        this.out0 = new glsBuiltinPrecisionTests.Variable(Out.Out0);
        this.out1 = new glsBuiltinPrecisionTests.Variable(Out.Out1);
    };

    /**
     * @constructor
     * @param {function(new:glsBuiltinPrecisionTests.Func)} F
     * @return {glsBuiltinPrecisionTests.GenFuncs}
     */
    glsBuiltinPrecisionTests.makeVectorizedFuncs = function(F) {
        return new glsBuiltinPrecisionTests.GenFuncs(
                new F(),
                new glsBuiltinPrecisionTests.VectorizedFunc(new F(), 2),
                new glsBuiltinPrecisionTests.VectorizedFunc(new F(), 3),
                new glsBuiltinPrecisionTests.VectorizedFunc(new F(), 4));
    };

    /**
     * @constructor
     * @param {glsBuiltinPrecisionTests.Typename} typename
     */
    glsBuiltinPrecisionTests.Sampling = function(typename) {
        this.typename = typename;
    };

    /**
     * @param {glsBuiltinPrecisionTests.Typename} typename
     * @param {number=} size
     * @return {glsBuiltinPrecisionTests.Sampling}
     */
    glsBuiltinPrecisionTests.SamplingFactory = function(typename, size) {
        if (size > 1)
            return new glsBuiltinPrecisionTests.DefaultSamplingVector(typename, size);
        switch (typename) {
            case 'vec4' : return new glsBuiltinPrecisionTests.DefaultSamplingVector('float', 4);
            case 'vec3' : return new glsBuiltinPrecisionTests.DefaultSamplingVector('float', 3);
            case 'vec2' : return new glsBuiltinPrecisionTests.DefaultSamplingVector('float', 2);
            case 'boolean' : return new glsBuiltinPrecisionTests.DefaultSamplingBool(typename);
            case 'float' : return new glsBuiltinPrecisionTests.DefaultSamplingFloat(typename);
            case 'mat2': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 2, 2);
            case 'mat2x3': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 3, 2);
            case 'mat2x4': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 4, 2);
            case 'mat3x2': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 2, 3);
            case 'mat3': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 3, 3);
            case 'mat3x4': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 4, 3);
            case 'mat4x2': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 2, 4);
            case 'mat4x3': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 3, 4);
            case 'mat4': return new glsBuiltinPrecisionTests.DefaultSamplingMatrix('float', 4, 4);
            case 'int' : return new glsBuiltinPrecisionTests.DefaultSamplingInt(typename);
        }
        return new glsBuiltinPrecisionTests.DefaultSamplingVoid(typename);
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {Array<*>} arr
     */
    glsBuiltinPrecisionTests.Sampling.prototype.genFixeds = function(fmt, arr) {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {gluShaderUtil.precision} precision
     * @param {deRandom.Random} random
     * @return {*}
     */
    glsBuiltinPrecisionTests.Sampling.prototype.genRandom = function(fmt, precision, random) {
        return 0;
    };

    /**
     * @return {number}
     */
    glsBuiltinPrecisionTests.Sampling.prototype.getWeight = function() {
        return 0;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Sampling}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     */
     glsBuiltinPrecisionTests.DefaultSamplingVoid = function(typename) {
         glsBuiltinPrecisionTests.Sampling.call(this, typename);
     };

     glsBuiltinPrecisionTests.DefaultSamplingVoid.prototype = Object.create(glsBuiltinPrecisionTests.Sampling.prototype);
     glsBuiltinPrecisionTests.DefaultSamplingVoid.prototype.constructor = glsBuiltinPrecisionTests.DefaultSamplingVoid;

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {Array<number>} dst
     */
    glsBuiltinPrecisionTests.DefaultSamplingVoid.prototype.genFixeds = function(fmt, dst) {
        dst.push(NaN);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Sampling}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     */
    glsBuiltinPrecisionTests.DefaultSamplingBool = function(typename) {
        glsBuiltinPrecisionTests.Sampling.call(this, typename);
    };

    glsBuiltinPrecisionTests.DefaultSamplingBool.prototype = Object.create(glsBuiltinPrecisionTests.Sampling.prototype);
    glsBuiltinPrecisionTests.DefaultSamplingBool.prototype.constructor = glsBuiltinPrecisionTests.DefaultSamplingBool;

    /**
     * @param {tcuFloatFormat.FloatFormat} fmt
     * @param {Array<Boolean>} dst
     */
    glsBuiltinPrecisionTests.DefaultSamplingBool.prototype.genFixeds = function(fmt, dst) {
        dst.push(true);
        dst.push(false);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Sampling}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     */
    glsBuiltinPrecisionTests.DefaultSamplingInt = function(typename) {
        glsBuiltinPrecisionTests.Sampling.call(this, typename);
    };

    glsBuiltinPrecisionTests.DefaultSamplingInt.prototype = Object.create(glsBuiltinPrecisionTests.Sampling.prototype);
    glsBuiltinPrecisionTests.DefaultSamplingInt.prototype.constructor = glsBuiltinPrecisionTests.DefaultSamplingInt;

    glsBuiltinPrecisionTests.DefaultSamplingInt.prototype.genRandom = function(fmt, prec, rnd) {
        /** @type {number} */ var exp = rnd.getInt(0, this.getNumBits(prec) - 2);
        /** @type {number} */ var sign = rnd.getBool() ? -1 : 1;

        return sign * rnd.getInt(0, 1 << exp);
    };

    glsBuiltinPrecisionTests.DefaultSamplingInt.prototype.genFixeds = function(fmt, dst) {
        dst.push(0);
        dst.push(-1);
        dst.push(1);
    };

    glsBuiltinPrecisionTests.DefaultSamplingInt.prototype.getWeight = function() {
        return 1.0;
    };

    /**
     * @param {gluShaderUtil.precision} prec
     * @return {number}
     */
    glsBuiltinPrecisionTests.DefaultSamplingInt.prototype.getNumBits = function(prec) {
        switch (prec) {
            case gluShaderUtil.precision.PRECISION_LOWP: return 8;
            case gluShaderUtil.precision.PRECISION_MEDIUMP: return 16;
            case gluShaderUtil.precision.PRECISION_HIGHP: return 32;
            default:
                throw new Error('Invalid precision: ' + prec);
        }
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Sampling}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     */
    glsBuiltinPrecisionTests.DefaultSamplingFloat = function(typename) {
        glsBuiltinPrecisionTests.Sampling.call(this, typename);
    };

    glsBuiltinPrecisionTests.DefaultSamplingFloat.prototype = Object.create(glsBuiltinPrecisionTests.Sampling.prototype);
    glsBuiltinPrecisionTests.DefaultSamplingFloat.prototype.constructor = glsBuiltinPrecisionTests.DefaultSamplingFloat;

    glsBuiltinPrecisionTests.DefaultSamplingFloat.prototype.genRandom = function(format, prec, rnd) {
        /** type{number} */ var minExp = format.getMinExp();
        /** type{number} */ var maxExp = format.getMaxExp();
        /** type{boolean} */ var haveSubnormal = format.hasSubnormal() != tcuFloatFormat.YesNoMaybe.NO;

        // Choose exponent so that the cumulative distribution is cubic.
        // This makes the probability distribution quadratic, with the peak centered on zero.
        /** type{number} */ var minRoot = deMath.deCbrt(minExp - 0.5 - (haveSubnormal ? 1.0 : 0.0));
        /** type{number} */ var maxRoot = deMath.deCbrt(maxExp + 0.5);
        /** type{number} */ var fractionBits = format.getFractionBits();
        /** type{number} */ var exp = deMath.rint(Math.pow(rnd.getFloat(minRoot, maxRoot),
                                                                3.0));
        /** type{number} */ var base = 0.0; // integral power of two
        /** type{number} */ var quantum = 0.0; // smallest representable difference in the binade
        /** type{number} */ var significand = 0.0; // Significand.

        // DE_ASSERT(fractionBits < std::numeric_limits<float>::digits);

        // Generate some occasional special numbers
        switch (rnd.getInt(0, 64)) {
            case 0: return 0;
            case 1: return Number.POSITIVE_INFINITY;
            case 2: return Number.NEGATIVE_INFINITY;
            case 3: return NaN;
            default: break;
        }

        if (exp >= minExp) {
            // Normal number
            base = deMath.deFloatLdExp(1.0, exp);
            quantum = deMath.deFloatLdExp(1.0, exp - fractionBits);
        } else {
            // Subnormal
            base = 0.0;
            quantum = deMath.deFloatLdExp(1.0, minExp - fractionBits);
        }

        switch (rnd.getInt(0, 16)) {
            // The highest number in this binade, significand is all bits one.
            case 0:
                significand = base - quantum;
                break;
            // Significand is one.
            case 1:
                significand = quantum;
                break;
            // Significand is zero.
            case 2:
                significand = 0.0;
                break;
            // Random (evenly distributed) significand.
            default: {
                /** type{number} */ var intFraction = rnd.getInt() & ((1 << fractionBits) - 1);
                significand = intFraction * quantum;
            }
        }

        // Produce positive numbers more often than negative.
        return (rnd.getInt(0, 3) == 0 ? -1.0 : 1.0) * (base + significand);
    };

    glsBuiltinPrecisionTests.DefaultSamplingFloat.prototype.genFixeds = function(format, dst) {
        /** @type {number} */ var minExp = format.getMinExp();
        /** @type {number} */ var maxExp = format.getMaxExp();
        /** @type {number} */ var fractionBits = format.getFractionBits();
        /** @type {number} */ var minQuantum = deMath.deFloatLdExp(1.0, minExp - fractionBits);
        /** @type {number} */ var minNormalized = deMath.deFloatLdExp(1.0, minExp);
        /** @type {number} */ var maxQuantum = deMath.deFloatLdExp(1.0, maxExp - fractionBits);

        // If unit testing is enabled, include exact numbers
        if (enableUnittests) {
            dst.push(0.2);
            dst.push(0.5);
        }

        // NaN
        dst.push(NaN);
        // Zero
        dst.push(0.0);

        for (var sign = -1; sign <= 1; sign += 2) {
            // Smallest subnormal
            dst.push(sign * minQuantum);

            // Largest subnormal
            dst.push(sign * (minNormalized - minQuantum));

            // Smallest normalized
            dst.push(sign * minNormalized);

            // Next smallest normalized
            dst.push(sign * (minNormalized + minQuantum));

            dst.push(sign * 0.5);
            dst.push(sign * 1.0);
            dst.push(sign * 2.0);

            // Largest number
            dst.push(sign * (deMath.deFloatLdExp(1.0, maxExp) +
                                  (deMath.deFloatLdExp(1.0, maxExp) - maxQuantum)));

            dst.push(sign * Number.POSITIVE_INFINITY);
        }
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Sampling}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     * @param {number} size
     */
    glsBuiltinPrecisionTests.DefaultSamplingVector = function(typename, size) {
        glsBuiltinPrecisionTests.Sampling.call(this, typename);
        this.size = size;
    };

    glsBuiltinPrecisionTests.DefaultSamplingVector.prototype = Object.create(glsBuiltinPrecisionTests.Sampling.prototype);
    glsBuiltinPrecisionTests.DefaultSamplingVector.prototype.constructor = glsBuiltinPrecisionTests.DefaultSamplingVector;

    glsBuiltinPrecisionTests.DefaultSamplingVector.prototype.genRandom = function(fmt, prec, rnd) {
        /** @type {Array<*>} */ var ret = [];

        for (var ndx = 0; ndx < this.size; ++ndx)
            ret[ndx] = glsBuiltinPrecisionTests.SamplingFactory(this.typename).genRandom(fmt, prec, rnd);

        return ret;
    };

    glsBuiltinPrecisionTests.DefaultSamplingVector.prototype.genFixeds = function(fmt, dst) {
        /** @type {Array<*>} */ var scalars = [];

        glsBuiltinPrecisionTests.SamplingFactory(this.typename).genFixeds(fmt, scalars);

        for (var scalarNdx = 0; scalarNdx < scalars.length; ++scalarNdx) {
            var value = [];
            for (var i = 0; i < this.size; i++)
                value[i] = scalars[scalarNdx];
            dst.push(value);
        }
    };

    glsBuiltinPrecisionTests.DefaultSamplingVector.prototype.getWeight = function() {
        return Math.pow(glsBuiltinPrecisionTests.SamplingFactory(this.typename).getWeight(), this.size);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Sampling}
     * @param {glsBuiltinPrecisionTests.Typename} typename
     * @param {number} rows
     * @param {number} cols
     */
    glsBuiltinPrecisionTests.DefaultSamplingMatrix = function(typename, rows, cols) {
        glsBuiltinPrecisionTests.Sampling.call(this, typename);
        this.rows = rows;
        this.cols = cols;
    };

    glsBuiltinPrecisionTests.DefaultSamplingMatrix.prototype = Object.create(glsBuiltinPrecisionTests.Sampling.prototype);
    glsBuiltinPrecisionTests.DefaultSamplingMatrix.prototype.constructor = glsBuiltinPrecisionTests.DefaultSamplingMatrix;

    glsBuiltinPrecisionTests.DefaultSamplingMatrix.prototype.genRandom = function(fmt, prec, rnd) {
        /** @type {tcuMatrix.Matrix} */ var ret = new tcuMatrix.Matrix(this.rows, this.cols);
        var sampler = glsBuiltinPrecisionTests.SamplingFactory(this.typename);

        for (var rowNdx = 0; rowNdx < this.rows; ++rowNdx)
            for (var colNdx = 0; colNdx < this.cols; ++colNdx)
                ret.set(rowNdx, colNdx, sampler.genRandom(fmt, prec, rnd));

        return ret;
    };

    glsBuiltinPrecisionTests.DefaultSamplingMatrix.prototype.genFixeds = function(fmt, dst) {
        /** @type {Array<number>} */ var scalars = [];

        glsBuiltinPrecisionTests.SamplingFactory(this.typename).genFixeds(fmt, scalars);

        for (var scalarNdx = 0; scalarNdx < scalars.length; ++scalarNdx)
            dst.push(new tcuMatrix.Matrix(this.rows, this.cols, scalars[scalarNdx]));

        if (this.cols == this.rows) {
            var mat = new tcuMatrix.Matrix(this.rows, this.cols, 0);
            var x = 1;
            mat.set(0, 0, x);
            for (var ndx = 0; ndx < this.cols; ++ndx) {
                mat.set(this.cols - 1 - ndx, ndx, x);
                x *= 2;
            }
            dst.push(mat);
        }
    };

    glsBuiltinPrecisionTests.DefaultSamplingMatrix.prototype.getWeight = function() {
        return Math.pow(glsBuiltinPrecisionTests.SamplingFactory(this.typename).getWeight(), this.rows * this.cols);
    };

    /**
     * @constructor
     * @param {number=} size
     * @param {glsBuiltinPrecisionTests.InTypes} In
     */
     glsBuiltinPrecisionTests.Samplings = function(In, size) {
        this.in0 = glsBuiltinPrecisionTests.SamplingFactory(In.In0, size);
        this.in1 = glsBuiltinPrecisionTests.SamplingFactory(In.In1, size);
        this.in2 = glsBuiltinPrecisionTests.SamplingFactory(In.In2, size);
        this.in3 = glsBuiltinPrecisionTests.SamplingFactory(In.In3, size);
    };

    /**
     * @param {glsBuiltinPrecisionTests.InTypes} In
     * @param {number=} size
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Samplings}
     */
     glsBuiltinPrecisionTests.DefaultSamplings = function(In, size) {
        glsBuiltinPrecisionTests.Samplings.call(this, In, size);
    };

    /**
     * @constructor
     * @extends {tcuTestCase.DeqpTest}
     * @param {glsBuiltinPrecisionTests.Context} context
     * @param {string} name
     * @param {string} extension
     */
    glsBuiltinPrecisionTests.PrecisionCase = function(context, name, extension) {
        /** @type {string} */ this.m_extension = extension === undefined ? '' : extension;
        /** @type {glsBuiltinPrecisionTests.Context} */ this.m_ctx = context;
        /** @type {*} */ this.m_status;
        /** @type {deRandom.Random} */ this.m_rnd = new deRandom.Random(1234); //    (0xdeadbeefu + context.testContext.getCommandLine().getBaseSeed())
        tcuTestCase.DeqpTest.call(this, name, extension);
    };

    glsBuiltinPrecisionTests.PrecisionCase.prototype = Object.create(tcuTestCase.DeqpTest.prototype);
    glsBuiltinPrecisionTests.PrecisionCase.prototype.constructor = glsBuiltinPrecisionTests.PrecisionCase;

    /**
     * @return {tcuFloatFormat.FloatFormat}
     */
    glsBuiltinPrecisionTests.PrecisionCase.prototype.getFormat = function() {
        return this.m_ctx.floatFormat;
    };

    /**
     * Return an output value extracted from flat array
     * @param {goog.NumberArray} output
     * @param {number} index Index of the element to extract
     * @param {*} reference Reference for type informaion
     * @return {glsBuiltinPrecisionTests.Value}
     */
    glsBuiltinPrecisionTests.getOutput = function(output, index, reference) {
        if (reference instanceof Array) {
            var ret = [];
            var size = reference.length;
            for (var i = 0; i < size; i++)
                ret[i] = output[size * index + i];
            return ret;
        }
        if (reference instanceof tcuMatrix.Matrix) {
            var ret = new tcuMatrix.Matrix(reference.rows, reference.cols);
            var size = reference.rows * reference.cols;
            for (var i = 0; i < reference.rows; i++)
                for (var j = 0; j < reference.cols; j++)
                    ret.set(i, j, output[size * index + j * reference.rows + i]);
            return ret;
        }

        return output[index];
    };
    /**
     * @param {glsBuiltinPrecisionTests.Variables} variables Variables<In, Out>
     * @param {glsBuiltinPrecisionTests.Inputs} inputs Inputs<In>
     * @param {glsBuiltinPrecisionTests.Statement} stmt
     */
    glsBuiltinPrecisionTests.PrecisionCase.prototype.testStatement = function(variables, inputs, stmt) {
        /** @type {tcuFloatFormat.FloatFormat} */ var fmt = this.getFormat();
        /** @type {number} */ var inCount = glsBuiltinPrecisionTests.numInputs(this.In);
        /** @type {number} */ var outCount = glsBuiltinPrecisionTests.numOutputs(this.Out);
        /** @type {number} */ var numValues = (inCount > 0) ? inputs.in0.length : 1;
        /** @type {tcuFloatFormat.FloatFormat} */ var highpFmt = this.m_ctx.highpFormat;
        var outputs = [];
        /** @type {number} */ var maxMsgs = 100;
        /** @type {number} */ var numErrors = 0;
        /** @type {glsShaderExecUtil.ShaderSpec} */ var spec = new glsShaderExecUtil.ShaderSpec();
        /** @type {glsBuiltinPrecisionTests.Environment} */ var env = new glsBuiltinPrecisionTests.Environment(); // Hoisted out of the inner loop for optimization.

        switch (inCount) {
            case 4: DE_ASSERT(inputs.in3.length == numValues);
            case 3: DE_ASSERT(inputs.in2.length == numValues);
            case 2: DE_ASSERT(inputs.in1.length == numValues);
            case 1: DE_ASSERT(inputs.in0.length == numValues);
            default: break;
        }
        if (enableUnittests)
            numValues = 2;

        // TODO: Fix logging
        //Print out the statement and its definitions
        // bufferedLogToConsole("Statement: " + stmt);
        // var funcInfo = ''
        // var funcs = {};
        // stmt.getUsedFuncs(funcs);
        // for (var key in funcs) {
        //     var func = funcs[key];
        //     funcInfo += func.printDefinition();
        // };
        // if (funcInfo.length > 0)
        //     bufferedLogToConsole('Reference definitions:' + funcInfo);

        // Initialize ShaderSpec from precision, variables and statement.

        spec.globalDeclarations = 'precision ' + gluShaderUtil.getPrecisionName(this.m_ctx.precision) + ' float;\n';

        if (this.m_extension.length > 0)
            spec.globalDeclarations += '#extension ' + this.m_extension + ' : require\n';

        spec.inputs = [];

        switch (inCount) {
            case 4: spec.inputs[3] = this.makeSymbol(variables.in3);
            case 3: spec.inputs[2] = this.makeSymbol(variables.in2);
            case 2: spec.inputs[1] = this.makeSymbol(variables.in1);
            case 1: spec.inputs[0] = this.makeSymbol(variables.in0);
            default: break;
        }

        spec.outputs = [];

        switch (outCount) {
            case 2: spec.outputs[1] = this.makeSymbol(variables.out1);
            case 1: spec.outputs[0] = this.makeSymbol(variables.out0);
            default: break;
        }

        spec.source = stmt;

        if (enableUnittests == false) {
            // Run the shader with inputs.
            /** @type {glsShaderExecUtil.ShaderExecutor} */
            var executor = glsShaderExecUtil.createExecutor(this.m_ctx.shaderType, spec);
            /** @type {Array<*>} */ var inputArr =
            [
            tcuMatrixUtil.flatten(inputs.in0), tcuMatrixUtil.flatten(inputs.in1), tcuMatrixUtil.flatten(inputs.in2), tcuMatrixUtil.flatten(inputs.in3)
            ];

            // executor.log(log());
            if (!executor.isOk())
                testFailed('Shader compilation failed');

            executor.useProgram();
            var outputArray = executor.execute(numValues, inputArr);

            switch (outCount) {
                case 2:
                    outputs[1] = glsBuiltinPrecisionTests.cast(this.Out.Out1, outputArray[1]);
                case 1:
                    outputs[0] = glsBuiltinPrecisionTests.cast(this.Out.Out0, outputArray[0]);
                default: break;
            }
        }

        // Initialize environment with dummy values so we don't need to bind in inner loop.

        var in0 = new tcuInterval.Interval();
        var in1 = new tcuInterval.Interval();
        var in2 = new tcuInterval.Interval();
        var in3 = new tcuInterval.Interval();
        var reference0 = new tcuInterval.Interval();
        var reference1 = new tcuInterval.Interval();

        env.bind(variables.in0, in0);
        env.bind(variables.in1, in1);
        env.bind(variables.in2, in2);
        env.bind(variables.in3, in3);
        env.bind(variables.out0, reference0);
        env.bind(variables.out1, reference1);

        // For each input tuple, compute output reference interval and compare
        // shader output to the reference.
        for (var valueNdx = 0; valueNdx < numValues; valueNdx++) {
            /** @type {boolean} */ var result = true;
            var value0, value1;
            var msg = '';

            var in0_ = glsBuiltinPrecisionTests.convert(this.Arg0, fmt, glsBuiltinPrecisionTests.round(this.Arg0, fmt, inputs.in0[valueNdx]));
            var in1_ = glsBuiltinPrecisionTests.convert(this.Arg1, fmt, glsBuiltinPrecisionTests.round(this.Arg1, fmt, inputs.in1[valueNdx]));
            var in2_ = glsBuiltinPrecisionTests.convert(this.Arg2, fmt, glsBuiltinPrecisionTests.round(this.Arg2, fmt, inputs.in2[valueNdx]));
            var in3_ = glsBuiltinPrecisionTests.convert(this.Arg3, fmt, glsBuiltinPrecisionTests.round(this.Arg3, fmt, inputs.in3[valueNdx]));

            env.bind(variables.in0, in0_);
            env.bind(variables.in1, in1_);
            env.bind(variables.in2, in2_);
            env.bind(variables.in3, in3_);

            stmt.execute(new glsBuiltinPrecisionTests.EvalContext(fmt, this.m_ctx.precision, env));

            switch (outCount) {
                case 2:
                    reference1 = glsBuiltinPrecisionTests.convert(this.Out.Out1, highpFmt, env.lookup(variables.out1));
                    if (enableUnittests)
                        result = referenceComparison(reference1, valueNdx + outCount - 1, this.m_ctx.floatFormat);
                    else {
                        value1 = glsBuiltinPrecisionTests.getOutput(outputs[1], valueNdx, reference1);
                        if (!glsBuiltinPrecisionTests.contains(this.Out.Out1, reference1, value1)) {
                        msg = 'Shader output 1 (' + value1 + ') is outside acceptable range: ' + reference1;
                            result = false;
                        }
                    }
                case 1:
                    reference0 = glsBuiltinPrecisionTests.convert(this.Out.Out0, highpFmt, env.lookup(variables.out0));
                    if (enableUnittests)
                        result = referenceComparison(reference0, valueNdx + outCount - 1, this.m_ctx.floatFormat);
                    else {
                        value0 = glsBuiltinPrecisionTests.getOutput(outputs[0], valueNdx, reference0);
                        if (!glsBuiltinPrecisionTests.contains(this.Out.Out0, reference0, value0)) {
                        msg = 'Shader output 0 (' + value0 + ') is outside acceptable range: ' + reference0;
                            result = false;
                        }
                    }
                default: break;
            }

            if (!result)
                ++numErrors;

            if (!result && numErrors <= maxMsgs) {
                /** @type {string} */ var builder = '';

                builder += (result ? 'Passed' : 'Failed') + '\n' + msg + '\n sample:\n' + valueNdx;

                if (inCount > 0) {
                    builder += '\t' + variables.in0.getName() + ' = ' +
                            inputs.in0[valueNdx] + '\n';
                }

                if (inCount > 1) {
                    builder += '\t' + variables.in1.getName() + ' = ' +
                            inputs.in1[valueNdx] + '\n';
                }

                if (inCount > 2) {
                    builder += '\t' + variables.in2.getName() + ' = ' +
                            inputs.in2[valueNdx] + '\n';
                }

                if (inCount > 3) {
                    builder += '\t' + variables.in3.getName() + ' = ' +
                            inputs.in3[valueNdx] + '\n';
                }

                if (enableUnittests == false) {
                    if (outCount > 0) {
                        builder += '\t' + variables.out0.getName() + ' = ' +
                            value0 + '\n' +
                                '\tExpected range: ' +
                                reference0 + '\n';
                    }

                    if (outCount > 1) {
                        builder += '\t' + variables.out1.getName() + ' = ' +
                            value1 + '\n' +
                                '\tExpected range: ' +
                                reference1 + '\n';
                    }
                }
                bufferedLogToConsole(builder);
            }
        }

        if (numErrors > maxMsgs) {
            bufferedLogToConsole('(Skipped ' + (numErrors - maxMsgs) + ' messages.)');
        }

        if (numErrors == 0) {
            testPassed('All ' + numValues + ' inputs passed.');
        } else {
            testFailed('' + numErrors + '/' + numValues + ' inputs failed.');
        }
    };

    /**
     * @param {glsBuiltinPrecisionTests.Variable} variable Variable<typename>
     * @return {glsShaderExecUtil.Symbol}
     */
    glsBuiltinPrecisionTests.PrecisionCase.prototype.makeSymbol = function(variable) {
        var v = variable;
        return new glsShaderExecUtil.Symbol(v.getName(), gluVarType.getVarTypeOf(v.typename, this.m_size, this.m_ctx.precision));
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Tuple4}
     * @param {*} in0
     * @param {*} in1
     * @param {*} in2
     * @param {*} in3
     */
    glsBuiltinPrecisionTests.InTuple = function(in0, in1, in2, in3) {
        glsBuiltinPrecisionTests.Tuple4.call(this, in0, in1, in2, in3);
    };

    glsBuiltinPrecisionTests.InTuple.prototype = Object.create(glsBuiltinPrecisionTests.Tuple4.prototype);
    glsBuiltinPrecisionTests.InTuple.prototype.constructor = glsBuiltinPrecisionTests.InTuple;

    /**
     * @param {*} In
     * @param {glsBuiltinPrecisionTests.Samplings} samplings Samplings<In>
     * @param {tcuFloatFormat.FloatFormat} floatFormat
     * @param {gluShaderUtil.precision} intPrecision
     * @param {number} numSamples
     * @param {deRandom.Random} rnd
     * @return {glsBuiltinPrecisionTests.Inputs}
     */
    glsBuiltinPrecisionTests.generateInputs = function(In, samplings, floatFormat, intPrecision, numSamples, rnd) {
        /*Inputs<In>*/ var ret = new glsBuiltinPrecisionTests.Inputs(In);
        /*Inputs<In>*/ var fixedInputs = new glsBuiltinPrecisionTests.Inputs(In);
        // set<InTuple<In>, InputLess<InTuple<In> > > seenInputs;
        /** @type {Array<glsBuiltinPrecisionTests.InTuple,glsBuiltinPrecisionTests.InputLess>} */
        var seenInputs = [];

        samplings.in0.genFixeds(floatFormat, fixedInputs.in0);
        samplings.in1.genFixeds(floatFormat, fixedInputs.in1);
        samplings.in2.genFixeds(floatFormat, fixedInputs.in2);
        samplings.in3.genFixeds(floatFormat, fixedInputs.in3);

        for (var ndx0 = 0; ndx0 < fixedInputs.in0.length; ++ndx0) {
            for (var ndx1 = 0; ndx1 < fixedInputs.in1.length; ++ndx1) {
                for (var ndx2 = 0; ndx2 < fixedInputs.in2.length; ++ndx2) {
                    for (var ndx3 = 0; ndx3 < fixedInputs.in3.length; ++ndx3) {
                        var tuple = new glsBuiltinPrecisionTests.InTuple(fixedInputs.in0[ndx0],
                                                     fixedInputs.in1[ndx1],
                                                     fixedInputs.in2[ndx2],
                                                     fixedInputs.in3[ndx3]);

                        seenInputs.push(tuple);
                        ret.in0.push(tuple.a);
                        ret.in1.push(tuple.b);
                        ret.in2.push(tuple.c);
                        ret.in3.push(tuple.d);
                    }
                }
            }
        }

        for (var ndx = 0; ndx < numSamples; ++ndx) {
            var in0 = samplings.in0.genRandom(floatFormat, intPrecision, rnd);
            var in1 = samplings.in1.genRandom(floatFormat, intPrecision, rnd);
            var in2 = samplings.in2.genRandom(floatFormat, intPrecision, rnd);
            var in3 = samplings.in3.genRandom(floatFormat, intPrecision, rnd);
            var tuple = new glsBuiltinPrecisionTests.InTuple(in0, in1, in2, in3);

            // if (de::contains(seenInputs, tuple))
            //     continue;

            seenInputs.push(tuple);
            ret.in0.push(in0);
            ret.in1.push(in1);
            ret.in2.push(in2);
            ret.in3.push(in3);
        }

        return ret;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrecisionCase}
     * @param {string} name
     * @param {glsBuiltinPrecisionTests.FuncBase} func
     */
    glsBuiltinPrecisionTests.FuncCaseBase = function(context, name, func) {
        glsBuiltinPrecisionTests.PrecisionCase.call(this, context, name, func.getRequiredExtension());
    };

    glsBuiltinPrecisionTests.FuncCaseBase.prototype = Object.create(glsBuiltinPrecisionTests.PrecisionCase.prototype);
    glsBuiltinPrecisionTests.FuncCaseBase.prototype.constructor = glsBuiltinPrecisionTests.FuncCaseBase;

    glsBuiltinPrecisionTests.FuncCaseBase.prototype.iterate = function() {

        assertMsgOptions(!(this.m_extension !== undefined && this.m_extension.trim() !== '') &&
            !sglrGLContext.isExtensionSupported(gl, this.m_extension),
                'Unsupported extension: ' + this.m_extension, false, true);

        this.runTest();

        // m_status.setTestContextResult(m_testCtx);
        return tcuTestCase.IterateResult.STOP;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FuncCaseBase}
     * @param {glsBuiltinPrecisionTests.Context} context
     * @param {string} name
     * @param {glsBuiltinPrecisionTests.Func} func
     */
    glsBuiltinPrecisionTests.InOutFuncCase = function(context, name, func) {
        glsBuiltinPrecisionTests.FuncCaseBase.call(this, context, name, func);
        this.Sig = func.Sig;
        this.m_func = func;
        this.Ret = func.Sig.Ret;
        this.Arg0 = func.Sig.Arg0;
        this.Arg1 = func.Sig.Arg1;
        this.Arg2 = func.Sig.Arg2;
        this.Arg3 = func.Sig.Arg3;
        this.In = new glsBuiltinPrecisionTests.InTypes(this.Arg0, this.Arg2, this.Arg3);
        this.Out = new glsBuiltinPrecisionTests.OutTypes(this.Ret, this.Arg1);
        this.m_size = this.m_func.m_size;
    };

    glsBuiltinPrecisionTests.InOutFuncCase.prototype = Object.create(glsBuiltinPrecisionTests.FuncCaseBase.prototype);
    glsBuiltinPrecisionTests.InOutFuncCase.prototype.constructor = glsBuiltinPrecisionTests.InOutFuncCase;

    /**
     * Samplings<In>
     * @return {glsBuiltinPrecisionTests.Samplings}
     */
    glsBuiltinPrecisionTests.InOutFuncCase.prototype.getSamplings = function() {
        return new glsBuiltinPrecisionTests.DefaultSamplings(this.In, this.m_size);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Signature} Sig_
     */
    glsBuiltinPrecisionTests.InOutFuncCase.prototype.runTest = function(Sig_) {
        /** @type {glsBuiltinPrecisionTests.Inputs} */ var inputs = (glsBuiltinPrecisionTests.generateInputs(
                                                    this.In,
                                                    this.getSamplings(),
                                                    this.m_ctx.floatFormat,
                                                    this.m_ctx.precision,
                                                    this.m_ctx.numRandoms,
                                                    this.m_rnd));

        var variables = new glsBuiltinPrecisionTests.Variables(this.In, this.Out);
        // Variables<In, Out> variables;
        //
        variables.out0 = new glsBuiltinPrecisionTests.Variable(this.Out.Out0, 'out0');
        variables.out1 = new glsBuiltinPrecisionTests.Variable(this.Arg1, 'out1');
        variables.in0 = new glsBuiltinPrecisionTests.Variable(this.Arg0, 'in0');
        variables.in1 = new glsBuiltinPrecisionTests.Variable(this.Arg2, 'in1');
        variables.in2 = new glsBuiltinPrecisionTests.Variable(this.Arg3, 'in2');
        variables.in3 = new glsBuiltinPrecisionTests.Variable('void', 'in3');

        var expr = glsBuiltinPrecisionTests.applyVar(this.m_func,
                                       variables.in0, variables.out1,
                                       variables.in1, variables.in2);
        var stmt = glsBuiltinPrecisionTests.variableAssignment(variables.out0, expr);

        this.testStatement(variables, inputs, stmt);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FuncCaseBase}
     * @param {glsBuiltinPrecisionTests.Context} context
     * @param {string} name
     * @param {glsBuiltinPrecisionTests.Func} func
     */
    glsBuiltinPrecisionTests.FuncCase = function(context, name, func) {
        glsBuiltinPrecisionTests.FuncCaseBase.call(this, context, name, func);
        this.Sig = func.Sig;
        this.m_func = func;
        this.Ret = func.Sig.Ret;
        this.Arg0 = func.Sig.Arg0;
        this.Arg1 = func.Sig.Arg1;
        this.Arg2 = func.Sig.Arg2;
        this.Arg3 = func.Sig.Arg3;
        this.In = new glsBuiltinPrecisionTests.InTypes(this.Arg0, this.Arg1, this.Arg2, this.Arg3);
        this.Out = new glsBuiltinPrecisionTests.OutTypes(this.Ret);
        this.m_size = this.m_func.m_size;
    };

    glsBuiltinPrecisionTests.FuncCase.prototype = Object.create(glsBuiltinPrecisionTests.FuncCaseBase.prototype);
    glsBuiltinPrecisionTests.FuncCase.prototype.constructor = glsBuiltinPrecisionTests.FuncCase;

    /**
     * Samplings<In>
     * @return {glsBuiltinPrecisionTests.Samplings}
     */
    glsBuiltinPrecisionTests.FuncCase.prototype.getSamplings = function() {
        return new glsBuiltinPrecisionTests.DefaultSamplings(this.In, this.m_size);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Signature} Sig_
     */
    glsBuiltinPrecisionTests.FuncCase.prototype.runTest = function(Sig_) {
        /** @type {glsBuiltinPrecisionTests.Inputs} */ var inputs = (glsBuiltinPrecisionTests.generateInputs(
                                                    this.In,
                                                    this.getSamplings(),
                                                    this.m_ctx.floatFormat,
                                                    this.m_ctx.precision,
                                                    this.m_ctx.numRandoms,
                                                    this.m_rnd));

        var variables = new glsBuiltinPrecisionTests.Variables(this.In, this.Out);
        // Variables<In, Out> variables;
        //
        variables.out0 = new glsBuiltinPrecisionTests.Variable(this.Ret, 'out0');
        variables.out1 = new glsBuiltinPrecisionTests.Variable('void', 'out1');
        variables.in0 = new glsBuiltinPrecisionTests.Variable(this.Arg0, 'in0');
        variables.in1 = new glsBuiltinPrecisionTests.Variable(this.Arg1, 'in1');
        variables.in2 = new glsBuiltinPrecisionTests.Variable(this.Arg2, 'in2');
        variables.in3 = new glsBuiltinPrecisionTests.Variable(this.Arg3, 'in3');

        var expr = glsBuiltinPrecisionTests.applyVar(this.m_func,
                                       variables.in0, variables.in1,
                                       variables.in2, variables.in3);
        var stmt = glsBuiltinPrecisionTests.variableAssignment(variables.out0, expr);

        this.testStatement(variables, inputs, stmt);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Func} func
     * @param {glsBuiltinPrecisionTests.Variable} arg0
     * @param {glsBuiltinPrecisionTests.Variable} arg1
     * @param {glsBuiltinPrecisionTests.Variable} arg2
     * @param {glsBuiltinPrecisionTests.Variable} arg3
     * @return {glsBuiltinPrecisionTests.ApplyVar}
     */
    glsBuiltinPrecisionTests.applyVar = function(func, arg0, arg1, arg2, arg3) {
        return new glsBuiltinPrecisionTests.ApplyVar(func.Sig, func, arg0, arg1, arg2, arg3);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Variable} variable
     * @param {glsBuiltinPrecisionTests.ApplyVar} value
     * @param {boolean} isDeclaration
     */
    glsBuiltinPrecisionTests.variableStatement = function(variable, value, isDeclaration) {
        return new glsBuiltinPrecisionTests.VariableStatement(variable, value, isDeclaration);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Variable} variable
     * @param {glsBuiltinPrecisionTests.ApplyVar} value
     */
    glsBuiltinPrecisionTests.variableAssignment = function(variable, value) {
        return glsBuiltinPrecisionTests.variableStatement(variable, value, false);
    };

    /**
     * @constructor
     */
    glsBuiltinPrecisionTests.CaseFactories = function() {};

    /**
     * @return {Array<glsBuiltinPrecisionTests.CaseFactory>}
     */
    glsBuiltinPrecisionTests.CaseFactories.prototype.getFactories = function() {};

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CaseFactories}
     */
    glsBuiltinPrecisionTests.BuiltinFuncs = function() {
        /** @type {Array<glsBuiltinPrecisionTests.CaseFactory>} */ this.m_factories = [];
    };

    glsBuiltinPrecisionTests.BuiltinFuncs.prototype = Object.create(glsBuiltinPrecisionTests.CaseFactories.prototype);
    glsBuiltinPrecisionTests.BuiltinFuncs.prototype.constructor = glsBuiltinPrecisionTests.BuiltinFuncs;

    /**
     * @return {Array<glsBuiltinPrecisionTests.CaseFactory>}
     */
    glsBuiltinPrecisionTests.BuiltinFuncs.prototype.getFactories = function() {
        return this.m_factories.slice();
    };

    /**
     * @param {glsBuiltinPrecisionTests.CaseFactory} fact
     */
    glsBuiltinPrecisionTests.BuiltinFuncs.prototype.addFactory = function(fact) {
        this.m_factories.push(fact);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Context} context
     * @param {string} name
     * @param {glsBuiltinPrecisionTests.Func} func
     * @return {glsBuiltinPrecisionTests.PrecisionCase}
     */
    glsBuiltinPrecisionTests.createFuncCase = function(context, name, func) {
        switch (func.getOutParamIndex()) {
            case -1:
                return new glsBuiltinPrecisionTests.FuncCase(context, name, func);
            case 1:
                return new glsBuiltinPrecisionTests.InOutFuncCase(context, name, func);
            default:
                throw new Error(!'Impossible');
        }
    };

    /**
     * @constructor
     */
    glsBuiltinPrecisionTests.CaseFactory = function() {};

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.CaseFactory.prototype.getName = function() {
        return '';
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.CaseFactory.prototype.getDesc = function() {
        return '';
    };

    /**
     * @param {glsBuiltinPrecisionTests.Context} ctx
     */
    glsBuiltinPrecisionTests.CaseFactory.prototype.createCase = function(ctx) {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CaseFactory}
     * @param {glsBuiltinPrecisionTests.Func} func
     */
    glsBuiltinPrecisionTests.SimpleFuncCaseFactory = function(func) {
        glsBuiltinPrecisionTests.CaseFactory.call(this);
        this.m_func = func;
    };

    setParentClass(glsBuiltinPrecisionTests.SimpleFuncCaseFactory, glsBuiltinPrecisionTests.CaseFactory);

    glsBuiltinPrecisionTests.SimpleFuncCaseFactory.prototype.getName = function() {
        return this.m_func.getName().toLowerCase();
    };

    glsBuiltinPrecisionTests.SimpleFuncCaseFactory.prototype.getDesc = function() {
        return "Function '" + this.getName() + "'";
    };

    glsBuiltinPrecisionTests.SimpleFuncCaseFactory.prototype.createCase = function(ctx) {
        return glsBuiltinPrecisionTests.createFuncCase(ctx, ctx.name, this.m_func);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CaseFactory}
     */
    glsBuiltinPrecisionTests.FuncCaseFactory = function() {
        glsBuiltinPrecisionTests.CaseFactory.call(this);
    };

    setParentClass(glsBuiltinPrecisionTests.FuncCaseFactory, glsBuiltinPrecisionTests.CaseFactory);

    glsBuiltinPrecisionTests.FuncCaseFactory.prototype.getFunc = function() {
        throw new Error('Virtual function. Please override.');
    };

    glsBuiltinPrecisionTests.FuncCaseFactory.prototype.getName = function() {
        return this.getFunc().getName().toLowerCase();
    };

    glsBuiltinPrecisionTests.FuncCaseFactory.prototype.getDesc = function() {
        return "Function '" + this.getFunc().getName() + "'";
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FuncCaseFactory}
     */
    glsBuiltinPrecisionTests.TemplateFuncCaseFactory = function(genF) {
        glsBuiltinPrecisionTests.FuncCaseFactory.call(this);
        this.m_genF = genF;
    };

    setParentClass(glsBuiltinPrecisionTests.TemplateFuncCaseFactory, glsBuiltinPrecisionTests.FuncCaseFactory);

    glsBuiltinPrecisionTests.TemplateFuncCaseFactory.prototype.getFunc = function() {
        return new this.m_genF(1);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Context} ctx
     */
    glsBuiltinPrecisionTests.TemplateFuncCaseFactory.prototype.createCase = function(ctx) {
        var group = tcuTestCase.newTest(ctx.name, ctx.name);
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'scalar', new this.m_genF(1)));
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'vec2', new this.m_genF(2)));
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'vec3', new this.m_genF(3)));
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'vec4', new this.m_genF(4)));

        return group;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FuncCaseFactory}
     */
    glsBuiltinPrecisionTests.MatrixFuncCaseFactory = function(genF) {
        glsBuiltinPrecisionTests.FuncCaseFactory.call(this);
        this.m_genF = genF;
    };

    setParentClass(glsBuiltinPrecisionTests.MatrixFuncCaseFactory, glsBuiltinPrecisionTests.FuncCaseFactory);

    glsBuiltinPrecisionTests.MatrixFuncCaseFactory.prototype.getFunc = function() {
        return new this.m_genF(2, 2);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Context} ctx
     */
    glsBuiltinPrecisionTests.MatrixFuncCaseFactory.prototype.createCase = function(ctx) {
        var group = tcuTestCase.newTest(ctx.name, ctx.name);
        this.addCase(ctx, group, 2, 2);
        this.addCase(ctx, group, 3, 2);
        this.addCase(ctx, group, 4, 2);
        this.addCase(ctx, group, 2, 3);
        this.addCase(ctx, group, 3, 3);
        this.addCase(ctx, group, 4, 3);
        this.addCase(ctx, group, 2, 4);
        this.addCase(ctx, group, 3, 4);
        this.addCase(ctx, group, 4, 4);

        return group;
    };

   /**
     * @param {glsBuiltinPrecisionTests.Context} ctx
     * @param {tcuTestCase.DeqpTest} group
     * @param {number} rows
     * @param {number} cols
     */
    glsBuiltinPrecisionTests.MatrixFuncCaseFactory.prototype.addCase = function(ctx, group, rows, cols) {
        var name = glsBuiltinPrecisionTests.dataTypeNameOfMatrix('float', rows, cols);
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, name, new this.m_genF(rows, cols)));
    };

    glsBuiltinPrecisionTests.dataTypeNameOfMatrix = function(typename, rows, cols) {
        switch (typename) {
            case 'float':
                if (rows === cols)
                    return 'mat' + rows;
                else
                    return 'mat' + cols + 'x' + rows;
        }
        throw new Error('Invalid arguments (' + typename + ', ' + rows + ', ' + cols + ')');
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.FuncCaseFactory}
     */
    glsBuiltinPrecisionTests.SquareMatrixFuncCaseFactory = function(genF) {
        glsBuiltinPrecisionTests.FuncCaseFactory.call(this);
        this.m_genF = genF;
    };

    setParentClass(glsBuiltinPrecisionTests.SquareMatrixFuncCaseFactory, glsBuiltinPrecisionTests.FuncCaseFactory);

    glsBuiltinPrecisionTests.SquareMatrixFuncCaseFactory.prototype.getFunc = function() {
        return new this.m_genF(2);
    };

    /**
     * @param {glsBuiltinPrecisionTests.Context} ctx
     */
    glsBuiltinPrecisionTests.SquareMatrixFuncCaseFactory.prototype.createCase = function(ctx) {
        var group = tcuTestCase.newTest(ctx.name, ctx.name);

        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'mat2', new this.m_genF(2)));
        return group;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     * @param {glsBuiltinPrecisionTests.Func} scalarFunc
     * @param {number=} size
     */
    glsBuiltinPrecisionTests.GenFunc = function(scalarFunc, size) {
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, scalarFunc.Sig);
        this.m_func = scalarFunc;
        this.m_size = size;
    };

    glsBuiltinPrecisionTests.GenFunc.prototype = Object.create(glsBuiltinPrecisionTests.PrimitiveFunc.prototype);
    glsBuiltinPrecisionTests.GenFunc.prototype.constructor = glsBuiltinPrecisionTests.GenFunc;

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.GenFunc.prototype.getName = function() {
       return this.m_func.getName();
    };

    /**
     * @return {number}
     */
    glsBuiltinPrecisionTests.GenFunc.prototype.getOutParamIndex = function() {
       return this.m_func.getOutParamIndex();
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.GenFunc.prototype.getRequiredExtension = function() {
       return this.m_func.getRequiredExtension();
    };

    /**
     * @param {Array<glsBuiltinPrecisionTests.ExprBase>} args
     */
    glsBuiltinPrecisionTests.GenFunc.prototype.doPrint = function(args) {
       return this.m_func.print(args);
    };

    /**
     * @param {glsBuiltinPrecisionTests.EvalContext} ctx
     * @param {glsBuiltinPrecisionTests.Tuple4} iargs
     * @return {*}
     */
    glsBuiltinPrecisionTests.GenFunc.prototype.doApply = function(ctx, iargs) {
        /** @type {Array<*>} */ var ret = [];

        if (this.m_size > 1) {
            for (var ndx = 0; ndx < this.m_size; ++ndx) {
                var a = iargs.a === undefined ? undefined : iargs.a[ndx];
                var b = iargs.b === undefined ? undefined : iargs.b[ndx];
                var c = iargs.c === undefined ? undefined : iargs.c[ndx];
                var d = iargs.d === undefined ? undefined : iargs.d[ndx];
                ret[ndx] = this.m_func.applyFunction(ctx, a, b, c, d);
            }
        } else
            ret[0] = this.m_func.applyFunction(ctx, iargs.a, iargs.b, iargs.c, iargs.d);

        return ret;
    };

    /**
     * @param {glsBuiltinPrecisionTests.FuncSet} dst
     */
    glsBuiltinPrecisionTests.GenFunc.prototype.doGetUsedFuncs = function(dst) {
        this.m_func.getUsedFuncs(dst);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.GenFunc}
     * @param {glsBuiltinPrecisionTests.Func} func
     * @param {number} size
     */
     glsBuiltinPrecisionTests.VectorizedFunc = function(func, size) {
         glsBuiltinPrecisionTests.GenFunc.call(this, func, size);
    };

    glsBuiltinPrecisionTests.VectorizedFunc.prototype = Object.create(glsBuiltinPrecisionTests.GenFunc.prototype);
    glsBuiltinPrecisionTests.VectorizedFunc.prototype.constructor = glsBuiltinPrecisionTests.VectorizedFunc;

    /**
     * @constructor
     * @param {glsBuiltinPrecisionTests.Func} func_
     * @param {glsBuiltinPrecisionTests.GenFunc} func2_
     * @param {glsBuiltinPrecisionTests.GenFunc} func3_
     * @param {glsBuiltinPrecisionTests.GenFunc} func4_
     */
    glsBuiltinPrecisionTests.GenFuncs = function(func_, func2_, func3_, func4_) {
        this.func = func_;
        this.func2 = func2_;
        this.func3 = func3_;
        this.func4 = func4_;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CaseFactory}
     * @param {glsBuiltinPrecisionTests.GenFuncs} funcs
     * @param {string} name
     */
    glsBuiltinPrecisionTests.GenFuncCaseFactory = function(funcs, name) {
        glsBuiltinPrecisionTests.CaseFactory.call(this);
        this.m_funcs = funcs;
        this.m_name = name;
    };

    glsBuiltinPrecisionTests.GenFuncCaseFactory.prototype = Object.create(glsBuiltinPrecisionTests.CaseFactory.prototype);
    glsBuiltinPrecisionTests.GenFuncCaseFactory.prototype.constructor = glsBuiltinPrecisionTests.GenFuncCaseFactory;

    /**
     * @param {glsBuiltinPrecisionTests.Context} ctx
     * @return {tcuTestCase.DeqpTest}
     */
    glsBuiltinPrecisionTests.GenFuncCaseFactory.prototype.createCase = function(ctx) {
        /** @type {tcuTestCase.DeqpTest} */
        var group = tcuTestCase.newTest(ctx.name, ctx.name);
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'scalar', this.m_funcs.func));
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'vec2', this.m_funcs.func2));
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'vec3', this.m_funcs.func3));
        group.addChild(glsBuiltinPrecisionTests.createFuncCase(ctx, 'vec4', this.m_funcs.func4));

        return group;
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.GenFuncCaseFactory.prototype.getName = function() {
        return this.m_name;
    };

    /**
     * @return {string}
     */
    glsBuiltinPrecisionTests.GenFuncCaseFactory.prototype.getDesc = function() {
        return "Function '" + this.m_funcs.func.getName() + "'";
    };

    /**
     * @constructor
     * @param {string} name_
     * @param {tcuFloatFormat.FloatFormat} floatFormat_
     * @param {tcuFloatFormat.FloatFormat} highpFormat_
     * @param {gluShaderUtil.precision} precision_
     * @param {gluShaderProgram.shaderType} shaderType_
     * @param {number} numRandoms_
     */
    glsBuiltinPrecisionTests.Context = function(name_, floatFormat_, highpFormat_, precision_, shaderType_, numRandoms_) {
        /** @type {string} */ this.name = name_;
        /** @type {tcuFloatFormat.FloatFormat} */ this.floatFormat = floatFormat_;
        /** @type {tcuFloatFormat.FloatFormat} */ this.highpFormat = highpFormat_;
        /** @type {gluShaderUtil.precision} */ this.precision = precision_;
        /** @type {gluShaderProgram.shaderType} */ this.shaderType = shaderType_;
        /** @type {number} */ this.numRandoms = numRandoms_;
    };

    /**
     * @constructor
     * @param {tcuFloatFormat.FloatFormat} highp_
     * @param {tcuFloatFormat.FloatFormat} mediump_
     * @param {tcuFloatFormat.FloatFormat} lowp_
     * @param {Array<gluShaderProgram.shaderType>} shaderTypes_
     * @param {number} numRandoms_
     */
    glsBuiltinPrecisionTests.PrecisionTestContext = function(highp_, mediump_, lowp_, shaderTypes_, numRandoms_) {
        /** @type {Array<gluShaderProgram.shaderType>} */ this.shaderTypes = shaderTypes_;
        /** @type {Array<tcuFloatFormat.FloatFormat>} */ this.formats = [];
        this.formats[gluShaderUtil.precision.PRECISION_HIGHP] = highp_;
        this.formats[gluShaderUtil.precision.PRECISION_MEDIUMP] = mediump_;
        this.formats[gluShaderUtil.precision.PRECISION_LOWP] = lowp_;
        /** @type {number} */ this.numRandoms = numRandoms_;
    };

    /**
     * \brief Simple incremental counter.
     *
     * This is used to make sure that different ExpandContexts will not produce
     * overlapping temporary names.
     * @constructor
     *
     */
    glsBuiltinPrecisionTests.Counter = function() {
        this.m_count = 0;
    };

    glsBuiltinPrecisionTests.Counter.prototype.get = function() {
        return this.m_count++;
    };

    /**
     * @constructor
     */
    glsBuiltinPrecisionTests.ExpandContext = function(counter) {
        this.m_counter = counter;
        this.m_statements = [];
    };

    /**
     * @param {string} typename
     * @param {string} baseName
     * @return {glsBuiltinPrecisionTests.Variable}
     */
    glsBuiltinPrecisionTests.ExpandContext.prototype.genSym = function(typename, baseName) {
        return new glsBuiltinPrecisionTests.Variable(typename, baseName + this.m_counter.get());
    };

    glsBuiltinPrecisionTests.ExpandContext.prototype.addStatement = function(/*const StatementP&*/ stmt) {
        this.m_statements.push(stmt);
    };

    glsBuiltinPrecisionTests.ExpandContext.prototype.getStatements = function() {
        return this.m_statements;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.Func}
     * @param {glsBuiltinPrecisionTests.Signature} Sig_ template <typename Sig_>
     */
    glsBuiltinPrecisionTests.DerivedFunc = function(Sig_) {
        glsBuiltinPrecisionTests.Func.call(this, Sig_);
    };

    setParentClass(glsBuiltinPrecisionTests.DerivedFunc, glsBuiltinPrecisionTests.Func);

    glsBuiltinPrecisionTests.DerivedFunc.prototype.doPrintDefinition = function() {
        var os = '';
        var paramNames = this.getParamNames();

        this.initialize();

        os += this.Ret + ' ' + this.getName() +
            '(';
        if (glsBuiltinPrecisionTests.isTypeValid(this.Arg0))
            os += this.Arg0 + ' ' + paramNames.a;
        if (glsBuiltinPrecisionTests.isTypeValid(this.Arg1))
            os += ', ' + this.Arg1 + ' ' + paramNames.b;
        if (glsBuiltinPrecisionTests.isTypeValid(this.Arg2))
            os += ', ' + this.Arg2 + ' ' + paramNames.c;
        if (glsBuiltinPrecisionTests.isTypeValid(this.Arg3))
            os += ', ' + this.Arg3 + ' ' + paramNames.d;
        os += ')\n{\n';

        for (var ndx = 0; ndx < this.m_body.length; ++ndx)
            os += this.m_body[ndx];
        os += 'return ' + this.m_ret + ';\n';
        os += '}\n';

        return os;
    };

    glsBuiltinPrecisionTests.DerivedFunc.prototype.doApply = function(ctx, args) {
        var funEnv = new glsBuiltinPrecisionTests.Environment();
        this.initialize();

        funEnv.bind(this.m_var0, args.a);
        funEnv.bind(this.m_var1, args.b);
        funEnv.bind(this.m_var2, args.c);
        funEnv.bind(this.m_var3, args.d);

        var funCtx = new glsBuiltinPrecisionTests.EvalContext(ctx.format, ctx.floatPrecision, funEnv, ctx.callDepth);

        for (var ndx = 0; ndx < this.m_body.length; ++ndx)
            this.m_body[ndx].execute(funCtx);

        var ret = this.m_ret.evaluate(funCtx);
        if (this.m_ret_alternative !== undefined) {
            var ret_alternative = this.m_ret_alternative.evaluate(funCtx);
            ret = tcuInterval.withIntervals(ret, ret_alternative);
        }

        // \todo [lauri] Store references instead of values in environment
        args.a = funEnv.lookup(this.m_var0);
        args.b = funEnv.lookup(this.m_var1);
        args.c = funEnv.lookup(this.m_var2);
        args.d = funEnv.lookup(this.m_var3);

        return ret;
    };

    glsBuiltinPrecisionTests.DerivedFunc.prototype.initialize = function() {
        if (!this.m_ret) {
            var paramNames = this.getParamNames();
            var symCounter = new glsBuiltinPrecisionTests.Counter();
            var ctx = new glsBuiltinPrecisionTests.ExpandContext(symCounter);

            this.m_var0 = new glsBuiltinPrecisionTests.Variable(this.Arg0, paramNames.a);
            this.m_var1 = new glsBuiltinPrecisionTests.Variable(this.Arg1, paramNames.b);
            this.m_var2 = new glsBuiltinPrecisionTests.Variable(this.Arg2, paramNames.c);
            this.m_var3 = new glsBuiltinPrecisionTests.Variable(this.Arg3, paramNames.d);
            var args = new glsBuiltinPrecisionTests.Tuple4(this.m_var0,
                this.m_var1, this.m_var2, this.m_var3);

            this.m_ret = this.doExpand(ctx, args);
            if (this.doExpandAlternative !== undefined) {
                this.m_ret_alternative = this.doExpandAlternative(ctx, args);
            }
            this.m_body = ctx.getStatements();
        }
    };

    glsBuiltinPrecisionTests.sizeToName = function(size) {
        switch (size) {
            case 4: return 'vec4';
            case 3: return 'vec3';
            case 2: return 'vec2';
        }
        return 'float';
    };

    /**
     * @constructor
     * @param {number} size
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Dot = function(size) {
        var name = glsBuiltinPrecisionTests.sizeToName(size);
        var sig = new glsBuiltinPrecisionTests.Signature('float', name, name);
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
        this.m_inputSize = size;
    };

    setParentClass(glsBuiltinPrecisionTests.Dot, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Dot.prototype.getName = function() {
        return 'dot';
    };

    glsBuiltinPrecisionTests.Dot.prototype.doExpand = function(ctx, args) {
        if (this.m_inputSize > 1) {
            var val = app(new glsBuiltinPrecisionTests.Mul(),
                new glsBuiltinPrecisionTests.VectorVariable(args.a, 0), new glsBuiltinPrecisionTests.VectorVariable(args.b, 0));
            for (var i = 1; i < this.m_inputSize; i++) {
                var tmp = new glsBuiltinPrecisionTests.Apply('float', new glsBuiltinPrecisionTests.Mul(),
                    new glsBuiltinPrecisionTests.VectorVariable(args.a, i), new glsBuiltinPrecisionTests.VectorVariable(args.b, i));
                val = app(new glsBuiltinPrecisionTests.Add(), val, tmp);
            }
            return val;
        } else {
            // args.a * args.b
            var ret = app(new glsBuiltinPrecisionTests.Mul(), args.a, args.b);
            return ret;
        }
    };

    /**
     * @constructor
     * @param {number} size
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Length = function(size) {
        var name = glsBuiltinPrecisionTests.sizeToName(size);
        var sig = new glsBuiltinPrecisionTests.Signature('float', name);
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
        this.m_inputSize = size;
    };

    setParentClass(glsBuiltinPrecisionTests.Length, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Length.prototype.getName = function() {
        return 'length';
    };

    glsBuiltinPrecisionTests.Length.prototype.doExpand = function(ctx, args) {
        //sqrt(dot(args.a, args.a));
        var v0 = app(new glsBuiltinPrecisionTests.Dot(this.m_inputSize), args.a, args.a);
        var v1 = app(new glsBuiltinPrecisionTests.Sqrt(), v0);
        return v1;
    };

    /**
     * @constructor
     * @param {number} size
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Distance = function(size) {
        var name = glsBuiltinPrecisionTests.sizeToName(size);
        var sig = new glsBuiltinPrecisionTests.Signature('float', name, name);
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
        this.m_inputSize = size;
    };

    setParentClass(glsBuiltinPrecisionTests.Distance, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Distance.prototype.getName = function() {
        return 'distance';
    };

    glsBuiltinPrecisionTests.Distance.prototype.doExpand = function(ctx, args) {
        //length(args.a - args.b);
        var v0 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Sub(), args.a, args.b);
        var v1 = app(new glsBuiltinPrecisionTests.Length(this.m_inputSize), v0);
        return v1;
    };

   /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Cross = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('vec3', 'vec3', 'vec3');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
        this.m_inputSize = 3;
    };

    setParentClass(glsBuiltinPrecisionTests.Cross, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Cross.prototype.getName = function() {
        return 'cross';
    };

    glsBuiltinPrecisionTests.Cross.prototype.doExpand = function(ctx, args) {
        // vec3(x.a[1] * x.b[2] - x.b[1] * x.a[2],
        //      x.a[2] * x.b[0] - x.b[2] * x.a[0],
        //      x.a[0] * x.b[1] - x.b[0] * x.a[1]);
        var a = [], b = [];
        for (var i = 0; i < this.m_inputSize; i++) {
            a[i] = new glsBuiltinPrecisionTests.VectorVariable(args.a, i);
            b[i] = new glsBuiltinPrecisionTests.VectorVariable(args.b, i);
        }
        var v0 = app(new glsBuiltinPrecisionTests.Mul(), a[1], b[2]);
        var v1 = app(new glsBuiltinPrecisionTests.Mul(), b[1], a[2]);
        var v2 = app(new glsBuiltinPrecisionTests.Sub(), v0, v1);

        var v3 = app(new glsBuiltinPrecisionTests.Mul(), a[2], b[0]);
        var v4 = app(new glsBuiltinPrecisionTests.Mul(), b[2], a[0]);
        var v5 = app(new glsBuiltinPrecisionTests.Sub(), v3, v4);

        var v6 = app(new glsBuiltinPrecisionTests.Mul(), a[0], b[1]);
        var v7 = app(new glsBuiltinPrecisionTests.Mul(), b[0], a[1]);
        var v8 = app(new glsBuiltinPrecisionTests.Sub(), v6, v7);

        var v9 = app(new glsBuiltinPrecisionTests.GenVec(this.m_inputSize, true), v2, v5, v8);
        return v9;
    };

    /**
     * @constructor
     * @param {number} size
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Normalize = function(size) {
        var name = glsBuiltinPrecisionTests.sizeToName(size);
        var sig = new glsBuiltinPrecisionTests.Signature(name, name);
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
        this.m_inputSize = size;
    };

    setParentClass(glsBuiltinPrecisionTests.Normalize, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Normalize.prototype.getName = function() {
        return 'normalize';
    };

    glsBuiltinPrecisionTests.Normalize.prototype.doExpand = function(ctx, args) {
        //args.a / length<Size>(args.a);
        var v0 = app(new glsBuiltinPrecisionTests.Length(this.m_inputSize), args.a);
        var v1 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Div(), args.a, v0);
        return v1;
    };

    /**
     * @constructor
     * @param {number} size
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.FaceForward = function(size) {
        var name = glsBuiltinPrecisionTests.sizeToName(size);
        var sig = new glsBuiltinPrecisionTests.Signature(name, name, name, name);
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
        this.m_inputSize = size;
        this.typename = name;
    };

    setParentClass(glsBuiltinPrecisionTests.FaceForward, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.FaceForward.prototype.getName = function() {
        return 'faceforward';
    };

    glsBuiltinPrecisionTests.FaceForward.prototype.doExpand = function(ctx, args) {
        //cond(dot(args.c, args.b) < constant(0.0f), args.a, -args.a);
        var zero = new glsBuiltinPrecisionTests.Constant(0);
        var v0 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Negate(), args.a);
        var v1 = app(new glsBuiltinPrecisionTests.Dot(this.m_inputSize), args.c, args.b);
        var v2 = app(new glsBuiltinPrecisionTests.LessThan('float'), v1, zero);
        var v3 = app(new glsBuiltinPrecisionTests.Cond(this.typename), v2, args.a, v0);
        return v3;
    };

    /**
     * @constructor
     * @param {number} size
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Reflect = function(size) {
        var name = glsBuiltinPrecisionTests.sizeToName(size);
        var sig = new glsBuiltinPrecisionTests.Signature(name, name, name);
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
        this.m_inputSize = size;
    };

    setParentClass(glsBuiltinPrecisionTests.Reflect, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Reflect.prototype.getName = function() {
        return 'reflect';
    };

    glsBuiltinPrecisionTests.Reflect.prototype.doExpand = function(ctx, args) {
        //args.a - (args.b * dot(args.b, args.a) * constant(2.0f));
        var two = new glsBuiltinPrecisionTests.Constant(2);
        var v0 = app(new glsBuiltinPrecisionTests.Dot(this.m_inputSize), args.b, args.a);
        var v1 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Mul(), args.b, v0);
        var v2 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Mul(), v1, two);
        var v3 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Sub(), args.a, v2);
        return v3;
    };

    /**
     * @constructor
     * @param {number} size
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Refract = function(size) {
        var name = glsBuiltinPrecisionTests.sizeToName(size);
        var sig = new glsBuiltinPrecisionTests.Signature(name, name, name, 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
        this.m_inputSize = size;
        this.typename = name;
    };

    setParentClass(glsBuiltinPrecisionTests.Refract, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Refract.prototype.getName = function() {
        return 'refract';
    };

    glsBuiltinPrecisionTests.Refract.prototype.doExpand = function(ctx, args) {
        var i = args.a;
        var n = args.b;
        var eta = args.c;
        var zero = new glsBuiltinPrecisionTests.Constant(0);
        var one = new glsBuiltinPrecisionTests.Constant(1);
        // dotNI = dot(n, i)
        var v0 = app(new glsBuiltinPrecisionTests.Dot(this.m_inputSize), n, i);
        var dotNI = glsBuiltinPrecisionTests.bindExpression('float', 'dotNI', ctx, v0);
        // k = 1 - eta * eta * (1 - dotNI * dotNI)
        var v1 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Mul(), dotNI, dotNI);
        var v2 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Sub(), one, v1);
        var v3 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Mul(), eta, eta);
        var v4 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Mul(), v3, v2);
        var v5 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Sub(), one, v4);
        var k = glsBuiltinPrecisionTests.bindExpression('float', 'k', ctx, v5);

        // i * eta - n * (eta * dotNI + sqrt(k))
        var v6 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Mul(), eta, dotNI);
        var v7 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Sqrt(), k);
        var v8 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Add(), v6, v7);
        var v9 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Mul(), n, v8);
        var v10 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Mul(), i, eta);
    var v11 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.Sub(), v10, v9);

        var v12 = new glsBuiltinPrecisionTests.ApplyScalar(new glsBuiltinPrecisionTests.LessThan('float'), k, zero);

        var zeroVector = app(new glsBuiltinPrecisionTests.GenVec(this.m_inputSize), zero);
        var v13 = app(new glsBuiltinPrecisionTests.Cond(this.typename), v12, zeroVector, v11);
        return v13;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Radians = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Radians, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Radians.prototype.getName = function() {
        return 'radians';
    };

    glsBuiltinPrecisionTests.Radians.prototype.doExpand = function(ctx, args) {
        var val = app(new glsBuiltinPrecisionTests.Div(),
                                                      new glsBuiltinPrecisionTests.Constant(Math.PI),
                                                      new glsBuiltinPrecisionTests.Constant(180));
        return new glsBuiltinPrecisionTests.Apply('float',
                                                  new glsBuiltinPrecisionTests.Mul(),
                                                  val,
                                                  args.a);

    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Degrees = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Degrees, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Degrees.prototype.getName = function() {
        return 'degrees';
    };

    glsBuiltinPrecisionTests.Degrees.prototype.doExpand = function(ctx, args) {
        var val = app(new glsBuiltinPrecisionTests.Div(),
                                                      new glsBuiltinPrecisionTests.Constant(180),
                                                      new glsBuiltinPrecisionTests.Constant(Math.PI));
        return new glsBuiltinPrecisionTests.Apply('float',
                                                  new glsBuiltinPrecisionTests.Mul(),
                                                  val,
                                                  args.a);

    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Sinh = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Sinh, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Sinh.prototype.getName = function() {
        return 'sinh';
    };

    glsBuiltinPrecisionTests.Sinh.prototype.doExpand = function(ctx, args) {
        // (exp(x) - exp(-x)) / constant(2.0f)
        var x = args.a;
        var v0 = app(new glsBuiltinPrecisionTests.Exp(), x);
        var v1 = app(new glsBuiltinPrecisionTests.Negate(), x);
        var v2 = app(new glsBuiltinPrecisionTests.Exp(), v1);
        var v3 = app(new glsBuiltinPrecisionTests.Sub(), v0, v2);
        var v4 = new glsBuiltinPrecisionTests.Constant(2);
        var v5 = new glsBuiltinPrecisionTests.Apply('float', new glsBuiltinPrecisionTests.Div, v3, v4);
        return v5;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Cosh = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Cosh, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Cosh.prototype.getName = function() {
        return 'cosh';
    };

    glsBuiltinPrecisionTests.Cosh.prototype.doExpand = function(ctx, args) {
        // (exp(x) + exp(-x)) / constant(2.0f)
        var x = args.a;
        var v0 = app(new glsBuiltinPrecisionTests.Exp(), x);
        var v1 = app(new glsBuiltinPrecisionTests.Negate(), x);
        var v2 = app(new glsBuiltinPrecisionTests.Exp(), v1);
        var v3 = app(new glsBuiltinPrecisionTests.Add(), v0, v2);
        var v4 = new glsBuiltinPrecisionTests.Constant(2);
        var v5 = new glsBuiltinPrecisionTests.Apply('float', new glsBuiltinPrecisionTests.Div, v3, v4);
        return v5;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Tanh = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Tanh, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Tanh.prototype.getName = function() {
        return 'tanh';
    };

    glsBuiltinPrecisionTests.Tanh.prototype.doExpand = function(ctx, args) {
        // sinh(x) / cosh(x)
        var x = args.a;
        var v0 = app(new glsBuiltinPrecisionTests.Sinh(), x);
        var v1 = app(new glsBuiltinPrecisionTests.Cosh(), x);
        var v2 = new glsBuiltinPrecisionTests.Apply('float', new glsBuiltinPrecisionTests.Div, v0, v1);
        return v2;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.ASinh = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.ASinh, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.ASinh.prototype.getName = function() {
        return 'asinh';
    };

    glsBuiltinPrecisionTests.ASinh.prototype.doExpand = function(ctx, args) {
        // log(x + sqrt(x * x + constant(1.0f)))
        var x = args.a;
        var v0 = app(new glsBuiltinPrecisionTests.Mul(), x, x);
        var v1 = new glsBuiltinPrecisionTests.Constant(1);
        var v2 = app(new glsBuiltinPrecisionTests.Add(), v0, v1);
        var v3 = app(new glsBuiltinPrecisionTests.Sqrt(), v2);
        var v4 = app(new glsBuiltinPrecisionTests.Add(), x, v3);
        var v5 = app(new glsBuiltinPrecisionTests.Log(), v4);
        return v5;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.ACosh = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.ACosh, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.ACosh.prototype.getName = function() {
        return 'acosh';
    };

    glsBuiltinPrecisionTests.ACosh.prototype.doExpand = function(ctx, args) {
        // log(x + sqrt((x + constant(1.0f)) * (x - constant(1.0f))))
        var x = args.a;
        var one = new glsBuiltinPrecisionTests.Constant(1);
        var v0 = app(new glsBuiltinPrecisionTests.Add(), x, one);
        var v1 = app(new glsBuiltinPrecisionTests.Sub(), x, one);
        var v2 = app(new glsBuiltinPrecisionTests.Mul(), v0, v1);
        var v3 = app(new glsBuiltinPrecisionTests.Sqrt(), v2);
        var v4 = app(new glsBuiltinPrecisionTests.Add(), x, v3);
        var v5 = app(new glsBuiltinPrecisionTests.Log(), v4);
        return v5;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.ATanh = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.ATanh, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.ATanh.prototype.getName = function() {
        return 'atanh';
    };

    glsBuiltinPrecisionTests.ATanh.prototype.doExpand = function(ctx, args) {
        // constant(0.5f) * log((constant(1.0f) + x) / (constant(1.0f) - x))
        var x = args.a;
        var one = new glsBuiltinPrecisionTests.Constant(1);
        var half = new glsBuiltinPrecisionTests.Constant(0.5);
        var v0 = app(new glsBuiltinPrecisionTests.Add(), one, x);
        var v1 = app(new glsBuiltinPrecisionTests.Sub(), one, x);
        var v2 = app(new glsBuiltinPrecisionTests.Div(), v0, v1);
        var v3 = app(new glsBuiltinPrecisionTests.Log(), v2);
        var v4 = app(new glsBuiltinPrecisionTests.Mul(), half, v3);
        return v4;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Sqrt = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Sqrt, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Sqrt.prototype.getName = function() {
        return 'sqrt';
    };

    glsBuiltinPrecisionTests.Sqrt.prototype.doExpand = function(ctx, args) {
        // constant(1.0f) / app<InverseSqrt>(x)
        var x = args.a;
        var one = new glsBuiltinPrecisionTests.Constant(1);
        var v0 = app(new glsBuiltinPrecisionTests.InverseSqrt(), x);
        var v1 = app(new glsBuiltinPrecisionTests.Div(), one, v0);
        return v1;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Fract = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Fract, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Fract.prototype.getName = function() {
        return 'fract';
    };

    glsBuiltinPrecisionTests.Fract.prototype.doExpand = function(ctx, args) {
        // x - floor(x)
        var x = args.a;
        var v0 = app(new glsBuiltinPrecisionTests.Floor(), x);
        var v1 = app(new glsBuiltinPrecisionTests.Sub(), x, v0);
        return v1;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Mod = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Mod, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Mod.prototype.getName = function() {
        return 'mod';
    };

    glsBuiltinPrecisionTests.Mod.prototype.doExpand = function(ctx, args) {
        // x - y * floor(x/y)
        var x = args.a;
        var y = args.b;
        var v0 = app(new glsBuiltinPrecisionTests.Div(), x, y);
        var v1 = app(new glsBuiltinPrecisionTests.Floor(), v0);
        var v2 = app(new glsBuiltinPrecisionTests.Mul(), y, v1);
        var v3 = app(new glsBuiltinPrecisionTests.Sub(), x, v2);
        return v3;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PrimitiveFunc}
     */
    glsBuiltinPrecisionTests.Modf = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float', 'float');
        glsBuiltinPrecisionTests.PrimitiveFunc.call(this, sig);
    };
    setParentClass(glsBuiltinPrecisionTests.Modf, glsBuiltinPrecisionTests.PrimitiveFunc);

    glsBuiltinPrecisionTests.Modf.prototype.getName = function() {
        return 'modf';
    };

    glsBuiltinPrecisionTests.Modf.prototype.doApply = function(ctx, iargs, variablenames) {
        var intPart;
        var func1 = function(x) {
            intPart = Math.trunc(x);
            return x - intPart;
        };
        var func2 = function(x) {
            return Math.trunc(x);
        };

        var fracIV = tcuInterval.applyMonotone1p(func1, iargs.a);
        var wholeIV = tcuInterval.applyMonotone1p(func2, iargs.a);

        if (!iargs.a.isFinite()) {
            // Behavior on modf(Inf) not well-defined, allow anything as a fractional part
            // See Khronos bug 13907
            fracIV.operatorOrAssignBinary(tcuInterval.NAN);
        }

        ctx.env.m_map[variablenames[1]] = wholeIV;
        return fracIV;
    };

    glsBuiltinPrecisionTests.Modf.prototype.getOutParamIndex = function() {
        return 1;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Mix = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float', 'float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Mix, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Mix.prototype.getName = function() {
        return 'mix';
    };

    glsBuiltinPrecisionTests.Mix.prototype.doExpand = function(ctx, args) {
        // (x * (constant(1.0f) - a)) + y * a
        var x = args.a;
        var y = args.b;
        var a = args.c;
        var one = new glsBuiltinPrecisionTests.Constant(1);
        var v0 = app(new glsBuiltinPrecisionTests.Sub(), one, a);
        var v1 = app(new glsBuiltinPrecisionTests.Mul(), x, v0);
        var v2 = app(new glsBuiltinPrecisionTests.Mul(), y, a);
        var v3 = app(new glsBuiltinPrecisionTests.Add(), v1, v2);
        return v3;
    };

    glsBuiltinPrecisionTests.Mix.prototype.doExpandAlternative = function(ctx, args) {
        // x + (y - x) * a
        var x = args.a;
        var y = args.b;
        var a = args.c;
        var v0 = app(new glsBuiltinPrecisionTests.Sub(), y, x);
        var v1 = app(new glsBuiltinPrecisionTests.Mul(), a, v0);
        var v2 = app(new glsBuiltinPrecisionTests.Add(), x, v1);
        return v2;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.SmoothStep = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float', 'float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.SmoothStep, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.SmoothStep.prototype.getName = function() {
        return 'smoothstep';
    };

    glsBuiltinPrecisionTests.SmoothStep.prototype.doExpand = function(ctx, args) {
        var edge0 = args.a;
        var edge1 = args.b;
        var x = args.c;
        var zero = new glsBuiltinPrecisionTests.Constant(0);
        var one = new glsBuiltinPrecisionTests.Constant(1);
        //clamp((x - edge0) / (edge1 - edge0), constant(0.0f), constant(1.0f));
        var v0 = app(new glsBuiltinPrecisionTests.Sub(), x, edge0);
        var v1 = app(new glsBuiltinPrecisionTests.Sub(), edge1, edge0);
        var v2 = app(new glsBuiltinPrecisionTests.Div(), v0, v1);
        var v3 = app(new glsBuiltinPrecisionTests.Clamp(), v2, zero, one);
        var t = glsBuiltinPrecisionTests.bindExpression('float', 't', ctx, v3);
        //(t * t * (constant(3.0f) - constant(2.0f) * t))
        var two = new glsBuiltinPrecisionTests.Constant(2);
        var three = new glsBuiltinPrecisionTests.Constant(3);
        var v4 = app(new glsBuiltinPrecisionTests.Mul(), v3, v3);
        var v5 = app(new glsBuiltinPrecisionTests.Mul(), two, v3);
        var v6 = app(new glsBuiltinPrecisionTests.Sub(), three, v5);
        var v7 = app(new glsBuiltinPrecisionTests.Mul(), v4, v6);
        return v7;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Pow = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Pow, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Pow.prototype.getName = function() {
        return 'pow';
    };

    glsBuiltinPrecisionTests.Pow.prototype.doExpand = function(ctx, args) {
        // exp2(y * log2(x))
        var x = args.a;
        var y = args.b;
        var v0 = app(new glsBuiltinPrecisionTests.Log2(), x);
        var v1 = app(new glsBuiltinPrecisionTests.Mul(), y, v0);
        var v2 = app(new glsBuiltinPrecisionTests.Exp2(), v1);
        return v2;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CFloatFunc1}
     */
    glsBuiltinPrecisionTests.ExpFunc = function(name, func) {
        glsBuiltinPrecisionTests.CFloatFunc1.call(this, name, func);
    };

    setParentClass(glsBuiltinPrecisionTests.ExpFunc, glsBuiltinPrecisionTests.CFloatFunc1);

    glsBuiltinPrecisionTests.ExpFunc.prototype.getCodomain = function() {
        return tcuInterval.withNumbers(0, Infinity);
    };

    glsBuiltinPrecisionTests.ExpFunc.prototype.precision = function(ctx, ret, x) {
        switch (ctx.floatPrecision) {
            case gluShaderUtil.precision.PRECISION_HIGHP:
                return ctx.format.ulp(ret, 3.0 + 2.0 * Math.abs(x));
            case gluShaderUtil.precision.PRECISION_MEDIUMP:
                return ctx.format.ulp(ret, 2.0 + 2.0 * Math.abs(x));
            case gluShaderUtil.precision.PRECISION_LOWP:
                return ctx.format.ulp(ret, 2.0);
            default:
                throw new Error(!'Impossible');
        }
    };

   /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.ExpFunc}
     */
    glsBuiltinPrecisionTests.Exp = function() {
        glsBuiltinPrecisionTests.ExpFunc.call(this, 'exp', Math.exp);
    };

    setParentClass(glsBuiltinPrecisionTests.Exp, glsBuiltinPrecisionTests.ExpFunc);

   /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.ExpFunc}
     */
    glsBuiltinPrecisionTests.Exp2 = function() {
        /**
         * @param {number} x
         * @return {number}
         */
        var exp2 = function(x) {
            return Math.exp(x * Math.LN2);
        };
        glsBuiltinPrecisionTests.ExpFunc.call(this, 'exp2', exp2);
    };

    setParentClass(glsBuiltinPrecisionTests.Exp2, glsBuiltinPrecisionTests.ExpFunc);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CFloatFunc1}
     */
    glsBuiltinPrecisionTests.LogFunc = function(name, func) {
        glsBuiltinPrecisionTests.CFloatFunc1.call(this, name, func);
    };

    setParentClass(glsBuiltinPrecisionTests.LogFunc, glsBuiltinPrecisionTests.CFloatFunc1);

    glsBuiltinPrecisionTests.LogFunc.prototype.precision = function(ctx, ret, x) {
        if (x <= 0)
            return NaN;
        switch (ctx.floatPrecision) {
            case gluShaderUtil.precision.PRECISION_HIGHP:
                return (0.5 <= x && x <= 2.0) ? deMath.deLdExp(1.0, -21) : ctx.format.ulp(ret, 3.0);
            case gluShaderUtil.precision.PRECISION_MEDIUMP:
                return (0.5 <= x && x <= 2.0) ? deMath.deLdExp(1.0, -7) : ctx.format.ulp(ret, 2.0);
            case gluShaderUtil.precision.PRECISION_LOWP:
                return ctx.format.ulp(ret, 2.0);
            default:
                throw new Error(!'Impossible');
        }
    };

   /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.LogFunc}
     */
    glsBuiltinPrecisionTests.Log = function() {
        glsBuiltinPrecisionTests.LogFunc.call(this, 'log', Math.log);
    };

    setParentClass(glsBuiltinPrecisionTests.Log, glsBuiltinPrecisionTests.LogFunc);

   /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.LogFunc}
     */
    glsBuiltinPrecisionTests.Log2 = function() {
        glsBuiltinPrecisionTests.LogFunc.call(this, 'log2', Math.log2);
    };

    setParentClass(glsBuiltinPrecisionTests.Log2, glsBuiltinPrecisionTests.LogFunc);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CFloatFunc1}
     */
    glsBuiltinPrecisionTests.PreciseFunc1 = function(name, func) {
        glsBuiltinPrecisionTests.CFloatFunc1.call(this, name, func);
    };

    setParentClass(glsBuiltinPrecisionTests.PreciseFunc1, glsBuiltinPrecisionTests.CFloatFunc1);

    glsBuiltinPrecisionTests.PreciseFunc1.prototype.precision = function(ctx, ret, x) {
        return 0;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc1}
     */
    glsBuiltinPrecisionTests.Abs = function() {
        glsBuiltinPrecisionTests.PreciseFunc1.call(this, 'abs', Math.abs);
    };
    setParentClass(glsBuiltinPrecisionTests.Abs, glsBuiltinPrecisionTests.PreciseFunc1);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc1}
     */
    glsBuiltinPrecisionTests.Sign = function() {
        glsBuiltinPrecisionTests.PreciseFunc1.call(this, 'sign', Math.sign);
    };
    setParentClass(glsBuiltinPrecisionTests.Sign, glsBuiltinPrecisionTests.PreciseFunc1);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc1}
     */
    glsBuiltinPrecisionTests.Floor = function() {
        glsBuiltinPrecisionTests.PreciseFunc1.call(this, 'floor', Math.floor);
    };
    setParentClass(glsBuiltinPrecisionTests.Floor, glsBuiltinPrecisionTests.PreciseFunc1);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc1}
     */
    glsBuiltinPrecisionTests.RoundEven = function() {
        glsBuiltinPrecisionTests.PreciseFunc1.call(this, 'roundEven', deMath.rint);
    };
    setParentClass(glsBuiltinPrecisionTests.RoundEven, glsBuiltinPrecisionTests.PreciseFunc1);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc1}
     */
    glsBuiltinPrecisionTests.Ceil = function() {
        glsBuiltinPrecisionTests.PreciseFunc1.call(this, 'ceil', Math.ceil);
    };
    setParentClass(glsBuiltinPrecisionTests.Ceil, glsBuiltinPrecisionTests.PreciseFunc1);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc1}
     */
    glsBuiltinPrecisionTests.Trunc = function() {
        glsBuiltinPrecisionTests.PreciseFunc1.call(this, 'trunc', Math.trunc);
    };
    setParentClass(glsBuiltinPrecisionTests.Trunc, glsBuiltinPrecisionTests.PreciseFunc1);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CFloatFunc2}
     */
    glsBuiltinPrecisionTests.PreciseFunc2 = function(name, func) {
        glsBuiltinPrecisionTests.CFloatFunc2.call(this, name, func);
    };

    setParentClass(glsBuiltinPrecisionTests.PreciseFunc2, glsBuiltinPrecisionTests.CFloatFunc2);

    glsBuiltinPrecisionTests.PreciseFunc2.prototype.precision = function(ctx, ret, x, y) {
        return 0;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc2}
     */
    glsBuiltinPrecisionTests.Min = function() {
        glsBuiltinPrecisionTests.PreciseFunc2.call(this, 'min', Math.min);
    };
    setParentClass(glsBuiltinPrecisionTests.Min, glsBuiltinPrecisionTests.PreciseFunc2);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc2}
     */
    glsBuiltinPrecisionTests.Max = function() {
        glsBuiltinPrecisionTests.PreciseFunc2.call(this, 'max', Math.max);
    };
    setParentClass(glsBuiltinPrecisionTests.Max, glsBuiltinPrecisionTests.PreciseFunc2);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.PreciseFunc2}
     */
    glsBuiltinPrecisionTests.Step = function() {
        /**
         * @param {number} edge
         * @param {number} x
         * return number
         */
        var step = function(edge, x) {
            return x < edge ? 0.0 : 1.0;
        };
        glsBuiltinPrecisionTests.PreciseFunc2.call(this, 'step', step);
    };
    setParentClass(glsBuiltinPrecisionTests.Step, glsBuiltinPrecisionTests.PreciseFunc2);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CFloatFunc1}
     */
    glsBuiltinPrecisionTests.TrigFunc = function(name, func, loEx, hiEx) {
        glsBuiltinPrecisionTests.CFloatFunc1.call(this, name, func);
        this.m_loExtremum = loEx;
        this.m_hiExtremum = hiEx;
    };

    setParentClass(glsBuiltinPrecisionTests.TrigFunc, glsBuiltinPrecisionTests.CFloatFunc1);

    glsBuiltinPrecisionTests.TrigFunc.prototype.innerExtrema = function(ctx, angle) {
        var lo = angle.lo();
        var hi = angle.hi();
        var loSlope = this.doGetSlope(lo);
        var hiSlope = this.doGetSlope(hi);

        // Detect the high and low values the function can take between the
        // interval endpoints.
        if (angle.length() >= 2.0 * Math.PI) {
            // The interval is longer than a full cycle, so it must get all possible values.
            return this.m_hiExtremum.operatorOrBinary(this.m_loExtremum);
        } else if (loSlope == 1 && hiSlope == -1) {
            // The slope can change from positive to negative only at the maximum value.
            return this.m_hiExtremum;
        } else if (loSlope == -1 && hiSlope == 1) {
            // The slope can change from negative to positive only at the maximum value.
            return this.m_loExtremum;
        } else if (loSlope == hiSlope &&
                 deMath.deSign(this.applyExact(hi) - this.applyExact(lo)) * loSlope == -1) {
            // The slope has changed twice between the endpoints, so both extrema are included.
            return this.m_hiExtremum.operatorOrBinary(this.m_loExtremum);
        }

        return new tcuInterval.Interval();
    };

    glsBuiltinPrecisionTests.TrigFunc.prototype.getCodomain = function() {
        // Ensure that result is always within [-1, 1], or NaN (for +-inf)
        var v = tcuInterval.withIntervals(new tcuInterval.Interval(-1), new tcuInterval.Interval(1));
        return v.operatorOrBinary(tcuInterval.NAN);
    };

    glsBuiltinPrecisionTests.TrigFunc.prototype.precision = function(ctx, ret, arg) {
        if (ctx.floatPrecision == gluShaderUtil.precision.PRECISION_HIGHP) {
            // Use precision from OpenCL fast relaxed math
            if (-Math.PI <= arg && arg <= Math.PI) {
                return deMath.deLdExp(1.0, -11);
            } else {
                // "larger otherwise", let's pick |x| * 2^-12 , which is slightly over
                // 2^-11 at x == pi.
                return deMath.deLdExp(Math.abs(arg), -12);
            }
        } else if (ctx.floatPrecision == gluShaderUtil.precision.PRECISION_MEDIUMP) {
            if (-Math.PI <= arg && arg <= Math.PI) {
                // from OpenCL half-float extension specification
                return ctx.format.ulp(ret, 2.0);
            } else {
                // |x| * 2^-10 , slightly larger than 2 ULP at x == pi
                return deMath.deLdExp(Math.abs(arg), -10);
            }
        } else {
            // from OpenCL half-float extension specification
            return ctx.format.ulp(ret, 2.0);
        }
    };

    /**
     * @param {number} angle
     * @return number
     */
    glsBuiltinPrecisionTests.TrigFunc.prototype.doGetSlope = function(angle) {
        throw new Error('Virtual function. Please override.');
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.TrigFunc}
     */
    glsBuiltinPrecisionTests.Sin = function() {
        glsBuiltinPrecisionTests.TrigFunc.call(this, 'sin', Math.sin, new tcuInterval.Interval(-1), new tcuInterval.Interval(1));
    };

    setParentClass(glsBuiltinPrecisionTests.Sin, glsBuiltinPrecisionTests.TrigFunc);

    glsBuiltinPrecisionTests.Sin.prototype.doGetSlope = function(angle) {
        return deMath.deSign(Math.cos(angle));
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.TrigFunc}
     */
    glsBuiltinPrecisionTests.Cos = function() {
        glsBuiltinPrecisionTests.TrigFunc.call(this, 'cos', Math.cos, new tcuInterval.Interval(-1), new tcuInterval.Interval(1));
    };

    setParentClass(glsBuiltinPrecisionTests.Cos, glsBuiltinPrecisionTests.TrigFunc);

    glsBuiltinPrecisionTests.Cos.prototype.doGetSlope = function(angle) {
        return -deMath.deSign(Math.sin(angle));
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Tan = function() {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'float');
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Tan, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Tan.prototype.getName = function() {
        return 'tan';
    };

    glsBuiltinPrecisionTests.Tan.prototype.doExpand = function(ctx, args) {
        //  sin(x) * (constant(1.0f) / cos(x)
        var x = args.a;
        var sin = app(new glsBuiltinPrecisionTests.Sin(), x);
        var cos = app(new glsBuiltinPrecisionTests.Cos(), x);
        var expr = app(new glsBuiltinPrecisionTests.Div(),
                                                      new glsBuiltinPrecisionTests.Constant(1),
                                                      cos);

        expr = new glsBuiltinPrecisionTests.Apply('float', new glsBuiltinPrecisionTests.Mul(),
                                                    sin,
                                                    expr);
        return expr;
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CFloatFunc1}
     */
    glsBuiltinPrecisionTests.ASin = function() {
        glsBuiltinPrecisionTests.CFloatFunc1.call(this, 'asin', Math.asin);
    };

    setParentClass(glsBuiltinPrecisionTests.ASin, glsBuiltinPrecisionTests.CFloatFunc1);

    glsBuiltinPrecisionTests.ASin.prototype.precision = function(ctx, ret, x) {
        if (!deMath.deInBounds32(x, -1.0, 1.0))
            return NaN;

        if (ctx.floatPrecision == gluShaderUtil.precision.PRECISION_HIGHP) {
            // Absolute error of 2^-11
            return deMath.deLdExp(1.0, -11);
        } else {
            // Absolute error of 2^-8
            return deMath.deLdExp(1.0, -8);
        }
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CFloatFunc1}
     */
    glsBuiltinPrecisionTests.ArcTrigFunc = function(name, func, precisionULPs, domain, coddomain) {
        glsBuiltinPrecisionTests.CFloatFunc1.call(this, name, func);
        this.m_precision = precisionULPs;
        this.m_domain = domain;
        this.m_codomain = coddomain;
    };

    setParentClass(glsBuiltinPrecisionTests.ArcTrigFunc, glsBuiltinPrecisionTests.CFloatFunc1);

    glsBuiltinPrecisionTests.ArcTrigFunc.prototype.precision = function(ctx, ret, x) {
        if (!this.m_domain.contains(new tcuInterval.Interval(x)))
            return NaN;

        if (ctx.floatPrecision == gluShaderUtil.precision.PRECISION_HIGHP) {
            // Use OpenCL's precision
            return ctx.format.ulp(ret, this.m_precision);
        } else {
            // Use OpenCL half-float spec
            return ctx.format.ulp(ret, 2.0);
        }
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.ArcTrigFunc}
     */
    glsBuiltinPrecisionTests.ACos = function() {
        glsBuiltinPrecisionTests.ArcTrigFunc.call(this, 'acos', Math.acos, 4096.0,
                                                tcuInterval.withNumbers(-1, 1),
                                                tcuInterval.withNumbers(0, Math.PI));
    };

    setParentClass(glsBuiltinPrecisionTests.ACos, glsBuiltinPrecisionTests.ArcTrigFunc);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.ArcTrigFunc}
     */
    glsBuiltinPrecisionTests.ATan = function() {
        glsBuiltinPrecisionTests.ArcTrigFunc.call(this, 'atan', Math.atan, 4096.0,
                                                tcuInterval.unbounded(),
                                                tcuInterval.withNumbers(-Math.PI * 0.5, Math.PI * 0.5));
    };

    setParentClass(glsBuiltinPrecisionTests.ATan, glsBuiltinPrecisionTests.ArcTrigFunc);

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.CFloatFunc2}
     */
    glsBuiltinPrecisionTests.ATan2 = function() {
        glsBuiltinPrecisionTests.CFloatFunc2.call(this, 'atan', Math.atan2);
    };

    setParentClass(glsBuiltinPrecisionTests.ATan2, glsBuiltinPrecisionTests.CFloatFunc2);

    glsBuiltinPrecisionTests.ATan2.prototype.innerExtrema = function(ctx, xi, yi) {
        var ret = new tcuInterval.Interval();

        if (yi.contains(tcuInterval.ZERO)) {
            if (xi.contains(tcuInterval.ZERO))
                ret.operatorOrAssignBinary(tcuInterval.NAN);
            if (xi.intersects(tcuInterval.withNumbers(-Infinity, 0)))
                ret.operatorOrAssignBinary(tcuInterval.withNumbers(-Math.PI, Math.PI));
        }

        if (ctx.format.hasInf() != tcuFloatFormat.YesNoMaybe.YES && (!yi.isFinite() || !xi.isFinite())) {
            // Infinities may not be supported, allow anything, including NaN
            ret.operatorOrAssignBinary(tcuInterval.NAN);
        }

        return ret;
    };

    glsBuiltinPrecisionTests.ATan2.prototype.precision = function(ctx, ret, x, y) {
        if (ctx.floatPrecision == gluShaderUtil.precision.PRECISION_HIGHP)
            return ctx.format.ulp(ret, 4096.0);
        else
            return ctx.format.ulp(ret, 2.0);
    };

    /**
     * @constructor
     * @param {number} size
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.DeterminantBase = function(size) {
        var sig = new glsBuiltinPrecisionTests.Signature('float', 'mat' + size);
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.DeterminantBase, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.DeterminantBase.prototype.getName = function() {
        return 'determinant';
    };

   /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DeterminantBase}
     */
    glsBuiltinPrecisionTests.Determinant = function() {
        // TODO: Support sizes 3 and 4
        this.size = 2;
        glsBuiltinPrecisionTests.DeterminantBase.call(this, this.size);
    };

    setParentClass(glsBuiltinPrecisionTests.Determinant, glsBuiltinPrecisionTests.DeterminantBase);

    glsBuiltinPrecisionTests.Determinant.prototype.doExpand = function(ctx, args) {
        //  mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1]
        var elem0_0 = new glsBuiltinPrecisionTests.MatrixVariable(args.a, 0, 0);
        var elem0_1 = new glsBuiltinPrecisionTests.MatrixVariable(args.a, 0, 1);
        var elem1_0 = new glsBuiltinPrecisionTests.MatrixVariable(args.a, 1, 0);
        var elem1_1 = new glsBuiltinPrecisionTests.MatrixVariable(args.a, 1, 1);

        var val0 = app(new glsBuiltinPrecisionTests.Mul(), elem0_0, elem1_1);
        var val1 = app(new glsBuiltinPrecisionTests.Mul(), elem0_1, elem1_0);
        return new glsBuiltinPrecisionTests.Apply('float', new glsBuiltinPrecisionTests.Sub(), val0, val1);
    };

    /**
     * @constructor
     * @extends {glsBuiltinPrecisionTests.DerivedFunc}
     */
    glsBuiltinPrecisionTests.Inverse = function() {
        this.size = 2;
        var name = 'mat' + this.size;
        var sig = new glsBuiltinPrecisionTests.Signature(name, name);
        glsBuiltinPrecisionTests.DerivedFunc.call(this, sig);
    };

    setParentClass(glsBuiltinPrecisionTests.Inverse, glsBuiltinPrecisionTests.DerivedFunc);

    glsBuiltinPrecisionTests.Inverse.prototype.getName = function() {
        return 'inverse';
    };

    glsBuiltinPrecisionTests.Inverse.prototype.doExpand = function(ctx, args) {
        var mat = args.a;
        var v0 = app(new glsBuiltinPrecisionTests.Determinant(), mat);
        var det = glsBuiltinPrecisionTests.bindExpression('float', 'det', ctx, v0);

        var elem0_0 = new glsBuiltinPrecisionTests.MatrixVariable(args.a, 0, 0);
        var elem0_1 = new glsBuiltinPrecisionTests.MatrixVariable(args.a, 0, 1);
        var elem1_0 = new glsBuiltinPrecisionTests.MatrixVariable(args.a, 1, 0);
        var elem1_1 = new glsBuiltinPrecisionTests.MatrixVariable(args.a, 1, 1);

        var result0_0 = app(new glsBuiltinPrecisionTests.Div(), elem1_1, det);
        var result0_1 = app(new glsBuiltinPrecisionTests.Div(), elem0_1, det);
        result0_1 = app(new glsBuiltinPrecisionTests.Negate(), result0_1);
        var result1_0 = app(new glsBuiltinPrecisionTests.Div(), elem1_0, det);
        result1_0 = app(new glsBuiltinPrecisionTests.Negate(), result1_0);
        var result1_1 = app(new glsBuiltinPrecisionTests.Div(), elem0_0, det);

        var col0 = app(new glsBuiltinPrecisionTests.GenVec(this.size, true), result0_0, result1_0);
        var col1 = app(new glsBuiltinPrecisionTests.GenVec(this.size, true), result0_1, result1_1);
        var ret = app(new glsBuiltinPrecisionTests.GenMat(this.size, this.size), col0, col1);

        return ret;
    };

    /**
     * @param {glsBuiltinPrecisionTests.PrecisionTestContext} ctx
     * @param {glsBuiltinPrecisionTests.CaseFactory} factory
     * @return {tcuTestCase.DeqpTest}
     */
    glsBuiltinPrecisionTests.createFuncGroup = function(ctx, factory) {
        /** @type {tcuTestCase.DeqpTest} */ var group = tcuTestCase.newTest(factory.getName(), factory.getDesc());

        for (var precNdx in gluShaderUtil.precision) {
            /** @type {gluShaderUtil.precision} */ var precision = gluShaderUtil.precision[precNdx];
            /** @type {string} */ var precName = gluShaderUtil.getPrecisionName(precision);
            /** @type {tcuFloatFormat.FloatFormat} */ var fmt = ctx.formats[precision];
            /** @type {tcuFloatFormat.FloatFormat} */ var highpFmt = ctx.formats[gluShaderUtil.precision.PRECISION_HIGHP];

            for (var shaderNdx in ctx.shaderTypes) {
                /** @type {gluShaderProgram.shaderType} */ var shaderType = ctx.shaderTypes[shaderNdx];
                /** @type {string} */ var shaderName = gluShaderProgram.getShaderTypeName(shaderType);
                /** @type {string} */ var name = precName + '_' + shaderName;
                /** @type {glsBuiltinPrecisionTests.Context} */ var caseCtx = new glsBuiltinPrecisionTests.Context(name, fmt, highpFmt,
                                                 precision, shaderType, ctx.numRandoms);

                group.addChild(factory.createCase(caseCtx));
            }
        }

        return group;
    };

    /**
     * @param {glsBuiltinPrecisionTests.CaseFactories} cases
     * @param {Array<gluShaderProgram.shaderType>} shaderTypes
     * @param {tcuTestCase.DeqpTest} dstGroup
     */
    glsBuiltinPrecisionTests.addBuiltinPrecisionTests = function(cases, shaderTypes, dstGroup) {
        /** @type {tcuFloatFormat.FloatFormat} */ var highp = new tcuFloatFormat.FloatFormat(-126, 127, 23, true,
                                                 tcuFloatFormat.YesNoMaybe.MAYBE, // subnormals
                                                 tcuFloatFormat.YesNoMaybe.YES, // infinities
                                                 tcuFloatFormat.YesNoMaybe.MAYBE); // NaN
        // \todo [2014-04-01 lauri] Check these once Khronos bug 11840 is resolved.
        /** @type {tcuFloatFormat.FloatFormat} */ var mediump = new tcuFloatFormat.FloatFormat(-13, 13, 9, false);
        // A fixed-point format is just a floating point format with a fixed
        // exponent and support for subnormals.
        /** @type {tcuFloatFormat.FloatFormat} */ var lowp = new tcuFloatFormat.FloatFormat(0, 0, 7, false, tcuFloatFormat.YesNoMaybe.YES);
        /** @type {glsBuiltinPrecisionTests.PrecisionTestContext} */ var ctx = new glsBuiltinPrecisionTests.PrecisionTestContext(highp, mediump, lowp,
                                                 shaderTypes, 16384);

        for (var ndx = 0; ndx < cases.getFactories().length; ++ndx)
            dstGroup.addChild(glsBuiltinPrecisionTests.createFuncGroup(ctx, cases.getFactories()[ndx]));
    };

    /**
     * @param {function(new:glsBuiltinPrecisionTests.Func)} F
     * @param {glsBuiltinPrecisionTests.CaseFactories} funcs
     * @param {string=} name
     */
    glsBuiltinPrecisionTests.addScalarFactory = function(F, funcs, name) {
        if (name === undefined)
            name = (new F()).getName();

        funcs.addFactory(new glsBuiltinPrecisionTests.GenFuncCaseFactory(glsBuiltinPrecisionTests.makeVectorizedFuncs(F), name));
    };

    /**
     * @param {function(new:glsBuiltinPrecisionTests.Func)} F
     */
    glsBuiltinPrecisionTests.createSimpleFuncCaseFactory = function(F) {
        return new glsBuiltinPrecisionTests.SimpleFuncCaseFactory(new F());
    };

    /**
     * @param {number} caseId test case Id
     * @return {glsBuiltinPrecisionTests.CaseFactories}
     */
    glsBuiltinPrecisionTests.createES3BuiltinCases = function(caseId) {
        /** @type {glsBuiltinPrecisionTests.CaseFactories} */ var funcs = new glsBuiltinPrecisionTests.BuiltinFuncs();

        switch (caseId) {
            case 0: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Add, funcs); break;
            case 1: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Sub, funcs); break;
            case 2: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Mul, funcs); break;
            case 3: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Div, funcs); break;
            case 4: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Radians, funcs); break;
            case 5: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Degrees, funcs); break;
            case 6: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Sin, funcs); break;
            case 7: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Cos, funcs); break;
            case 8: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Tan, funcs); break;
            case 9: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.ASin, funcs); break;
            case 10: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.ACos, funcs); break;
            case 11: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.ATan, funcs); break;
            case 12: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.ATan2, funcs, 'atan2'); break;
            case 13: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Sinh, funcs); break;
            case 14: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Cosh, funcs); break;
            case 15: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Tanh, funcs); break;
            case 16: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.ASinh, funcs); break;
            case 17: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.ACosh, funcs); break;
            case 18: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.ATanh, funcs); break;
            case 19: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Pow, funcs); break;
            case 20: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Exp, funcs); break;
            case 21: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Exp2, funcs); break;
            case 22: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Log, funcs); break;
            case 23: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Log2, funcs); break;
            case 24: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Sqrt, funcs); break;
            case 25: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.InverseSqrt, funcs); break;
            case 26: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Abs, funcs); break;
            case 27: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Sign, funcs); break;
            case 28: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Floor, funcs); break;
            case 29: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Trunc, funcs); break;
            case 30: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Round, funcs); break;
            case 31: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.RoundEven, funcs); break;
            case 32: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Ceil, funcs); break;
            case 33: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Fract, funcs); break;
            case 34: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Mod, funcs); break;
            case 35: funcs.addFactory(glsBuiltinPrecisionTests.createSimpleFuncCaseFactory(glsBuiltinPrecisionTests.Modf)); break;
            case 36: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Min, funcs); break;
            case 37: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Max, funcs); break;
            case 38: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Mix, funcs); break;
            case 39: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Step, funcs); break;
            case 40: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.SmoothStep, funcs); break;
            case 41: glsBuiltinPrecisionTests.addScalarFactory(glsBuiltinPrecisionTests.Clamp, funcs); break;
            case 42: funcs.addFactory(new glsBuiltinPrecisionTests.TemplateFuncCaseFactory(glsBuiltinPrecisionTests.Length)); break;
            case 43: funcs.addFactory(new glsBuiltinPrecisionTests.TemplateFuncCaseFactory(glsBuiltinPrecisionTests.Distance)); break;
            case 44: funcs.addFactory(new glsBuiltinPrecisionTests.TemplateFuncCaseFactory(glsBuiltinPrecisionTests.Dot)); break;
            case 45: funcs.addFactory(glsBuiltinPrecisionTests.createSimpleFuncCaseFactory(glsBuiltinPrecisionTests.Cross)); break;
            case 46: funcs.addFactory(new glsBuiltinPrecisionTests.TemplateFuncCaseFactory(glsBuiltinPrecisionTests.Normalize)); break;
            case 47: funcs.addFactory(new glsBuiltinPrecisionTests.TemplateFuncCaseFactory(glsBuiltinPrecisionTests.FaceForward)); break;
            case 48: funcs.addFactory(new glsBuiltinPrecisionTests.TemplateFuncCaseFactory(glsBuiltinPrecisionTests.Reflect)); break;
            case 49: funcs.addFactory(new glsBuiltinPrecisionTests.TemplateFuncCaseFactory(glsBuiltinPrecisionTests.Refract)); break;
            case 50: funcs.addFactory(new glsBuiltinPrecisionTests.MatrixFuncCaseFactory(glsBuiltinPrecisionTests.MatrixCompMult)); break;
            case 51: funcs.addFactory(new glsBuiltinPrecisionTests.MatrixFuncCaseFactory(glsBuiltinPrecisionTests.OuterProduct)); break;
            case 52: funcs.addFactory(new glsBuiltinPrecisionTests.MatrixFuncCaseFactory(glsBuiltinPrecisionTests.Transpose)); break;
            case 53: funcs.addFactory(new glsBuiltinPrecisionTests.SquareMatrixFuncCaseFactory(glsBuiltinPrecisionTests.Determinant)); break;
            case 54: funcs.addFactory(new glsBuiltinPrecisionTests.SquareMatrixFuncCaseFactory(glsBuiltinPrecisionTests.Inverse)); break;
            default: break;
        }

        return funcs;
    };

});
