Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.renderscript.cts;
     18 
     19 import android.util.Log;
     20 import java.util.Arrays;
     21 import junit.framework.Assert;
     22 
     23 /**
     24  * This class and the enclosed Floaty class are used to validate the precision of the floating
     25  * point operations of the various drivers.  Instances of Target contains information about the
     26  * expectations we have for the functions being tested.  There's an instance of Floaty for each
     27  * floating value being verified.
     28  */
     29 public class Target {
     30     /**
     31      * In relaxed precision mode, we allow:
     32      * - less precision in the computation
     33      * - using only normalized values
     34      * - reordering of the operations
     35      * - different rounding mode
     36      */
     37     private boolean mIsRelaxedPrecision;
     38 
     39     /*
     40      * The following two fields are set just before the expected values are computed for a specific
     41      * RenderScript function.  Hence, we can't use one instance of this class to test two APIs
     42      * in parallel.  The generated Test*.java code uses one instance of Target per test, so we're
     43      * safe.
     44      */
     45 
     46     /**
     47      * For native, we allow the same things as relaxed precision, plus:
     48      * - operations don't have to return +/- infinity
     49      */
     50     private boolean mIsNative;
     51 
     52     /**
     53      * How much we'll allow the values tested to diverge from the values
     54      * we compute.  This can be very large for native_* and half_* tests.
     55      */
     56     private int mUlpFactor;
     57 
     58     Target(boolean relaxed) {
     59         mIsRelaxedPrecision = relaxed;
     60     }
     61 
     62     /**
     63      * Sets whether we are testing a native_* function and how many ulp we allow
     64      * for full and relaxed precision.
     65      */
     66     void setPrecision(int fullUlpFactor, int relaxedUlpFactor, boolean isNative) {
     67         mIsNative = isNative;
     68         mUlpFactor = mIsRelaxedPrecision ? relaxedUlpFactor : fullUlpFactor;
     69     }
     70 
     71     /**
     72      * Helper functions to create a new 32 bit Floaty with the current expected level of precision.
     73      * We have variations that expect one to five arguments.  Any of the passed arguments are considered
     74      * valid values for that Floaty.
     75      */
     76     Floaty new32(float a) {
     77         return new Floaty(32, new double [] { a });
     78     }
     79 
     80     Floaty new32(float a, float b) {
     81         return new Floaty(32, new double [] { a, b });
     82     }
     83 
     84     Floaty new32(float a, float b, float c) {
     85         return new Floaty(32, new double [] { a, b, c });
     86     }
     87 
     88     Floaty new32(float a, float b, float c, float d) {
     89         return new Floaty(32, new double [] { a, b, c, d });
     90     }
     91 
     92     Floaty new32(float a, float b, float c, float d, float e) {
     93         return new Floaty(32, new double [] { a, b, c, d, e });
     94     }
     95 
     96     /**
     97      * Helper functions to create a new 64 bit Floaty with the current expected level of precision.
     98      * We have variations that expect one to five arguments.  Any of the passed arguments are considered
     99      * valid values for that Floaty.
    100      */
    101     Floaty new64(double a) {
    102         return new Floaty(64, new double [] { a });
    103     }
    104 
    105     Floaty new64(double a, double b) {
    106         return new Floaty(64, new double [] { a, b });
    107     }
    108 
    109     Floaty new64(double a, double b, double c) {
    110         return new Floaty(64, new double [] { a, b, c });
    111     }
    112 
    113     Floaty new64(double a, double b, double c, double d) {
    114         return new Floaty(64, new double [] { a, b, c, d });
    115     }
    116 
    117     Floaty new64(double a, double b, double c, double d, double e) {
    118         return new Floaty(64, new double [] { a, b, c, d, e });
    119     }
    120 
    121     /**
    122      * Returns a Floaty that contain a NaN for the specified size.
    123      */
    124     Floaty newNan(int numberOfBits) {
    125         return new Floaty(numberOfBits, new double [] { Double.NaN });
    126     }
    127 
    128     Floaty add(Floaty a, Floaty b) {
    129         //Log.w("Target.add", "a: " + a.toString());
    130         //Log.w("Target.add", "b: " + b.toString());
    131         assert(a.mNumberOfBits == b.mNumberOfBits);
    132         if (!a.mHasRange || !b.mHasRange) {
    133             return newNan(a.mNumberOfBits);
    134         }
    135         return new Floaty(a.mNumberOfBits, new double[] { a.mValue + b.mValue,
    136                                                           a.mMinValue + b.mMinValue,
    137                                                           a.mMaxValue + b.mMaxValue });
    138     }
    139 
    140     Floaty subtract(Floaty a, Floaty b) {
    141         //Log.w("Target.subtract", "a: " + a.toString());
    142         //Log.w("Target.subtract", "b: " + b.toString());
    143         assert(a.mNumberOfBits == b.mNumberOfBits);
    144         if (!a.mHasRange || !b.mHasRange) {
    145             return newNan(a.mNumberOfBits);
    146         }
    147         return new Floaty(a.mNumberOfBits, new double[] { a.mValue - b.mValue,
    148                                                           a.mMinValue - b.mMaxValue,
    149                                                           a.mMaxValue - b.mMinValue });
    150     }
    151 
    152     Floaty multiply(Floaty a, Floaty b) {
    153         //Log.w("Target.multiply", "a: " + a.toString());
    154         //Log.w("Target.multiply", "b: " + b.toString());
    155         assert(a.mNumberOfBits == b.mNumberOfBits);
    156         if (!a.mHasRange || !b.mHasRange) {
    157             return newNan(a.mNumberOfBits);
    158         }
    159         return new Floaty(a.mNumberOfBits, new double[] { a.mValue * b.mValue,
    160                                                           a.mMinValue * b.mMinValue,
    161                                                           a.mMinValue * b.mMaxValue,
    162                                                           a.mMaxValue * b.mMinValue,
    163                                                           a.mMaxValue * b.mMaxValue});
    164     }
    165 
    166     Floaty divide(Floaty a, Floaty b) {
    167         //Log.w("Target.divide", "a: " + a.toString());
    168         //Log.w("Target.divide", "b: " + b.toString());
    169         assert(a.mNumberOfBits == b.mNumberOfBits);
    170         if (!a.mHasRange || !b.mHasRange) {
    171             return newNan(a.mNumberOfBits);
    172         }
    173         return new Floaty(a.mNumberOfBits, new double[] { a.mValue / b.mValue,
    174                                                           a.mMinValue / b.mMinValue,
    175                                                           a.mMinValue / b.mMaxValue,
    176                                                           a.mMaxValue / b.mMinValue,
    177                                                           a.mMaxValue / b.mMaxValue});
    178     }
    179 
    180     /** Returns the absolute value of a Floaty. */
    181     Floaty abs(Floaty a) {
    182         if (!a.mHasRange) {
    183             return newNan(a.mNumberOfBits);
    184         }
    185         if (a.mMinValue >= 0 && a.mMaxValue >= 0) {
    186             // Two non-negatives, no change
    187             return a;
    188         }
    189         Floaty f = new Floaty(a);
    190         f.mValue = Math.abs(a.mValue);
    191         if (a.mMinValue < 0 && a.mMaxValue < 0) {
    192             // Two negatives, we invert
    193             f.mMinValue = -a.mMaxValue;
    194             f.mMaxValue = -a.mMinValue;
    195         } else {
    196             // We have one negative, one positive.
    197             f.mMinValue = 0.f;
    198             f.mMaxValue = Math.max(-a.mMinValue, a.mMaxValue);
    199         }
    200         return f;
    201     }
    202 
    203     /** Returns the square root of a Floaty. */
    204     Floaty sqrt(Floaty a) {
    205         //Log.w("Target.sqrt", "a: " + a.toString());
    206         if (!a.mHasRange) {
    207             return newNan(a.mNumberOfBits);
    208         }
    209         double f = Math.sqrt(a.mValue);
    210         double min = Math.sqrt(a.mMinValue);
    211         double max = Math.sqrt(a.mMaxValue);
    212         double[] values;
    213         /* If the range of inputs covers 0, make sure we have it as one of
    214          * the answers, to set the correct lowest bound, as the square root
    215          * of the negative inputs will yield a NaN flag and won't affect the
    216          * range.
    217          */
    218         if (a.mMinValue < 0 && a.mMaxValue > 0) {
    219             values = new double[]{f, 0., min, max};
    220         } else {
    221             values = new double[]{f, min, max};
    222         }
    223         Floaty answer = new Floaty(a.mNumberOfBits, values);
    224         // Allow a little more imprecision for a square root operation.
    225         answer.ExpandRangeByUlpFactor();
    226         return answer;
    227     }
    228 
    229     /**
    230      * This class represents the range of floating point values we accept as the result of a
    231      * computation performed by a runtime driver.
    232      */
    233     class Floaty {
    234         /**
    235          * The number of bits the value should have, either 32 or 64.  It would have been nice to
    236          * use generics, e.g. Floaty<double> and Floaty<double> but Java does not support generics
    237          * of float and double.  Also, Java does not have an f16 type.  This can simulate it,
    238          * although more work will be needed.
    239          */
    240         private int mNumberOfBits;
    241         /** True if NaN is an acceptable value. */
    242         private boolean mCanBeNan;
    243         /**
    244          * True if mValue, mMinValue, mMaxValue have been set.  This should be the case if mCanBeNan is false.
    245          * It's possible for both mCanBeNan and mHasRange to be true at the same time.
    246          */
    247         private boolean mHasRange;
    248         /**
    249          * The typical value we would expect.  We don't just keep track of the min and max
    250          * of the ranges of values allowed because some functions we are evaluating are
    251          * discontinuous, e.g. sqrt around 0, lgamma around -1, -2, -3 and tan around pi/2.
    252          * By keeping track of the middle value, we're more likely to handle this discontinuity
    253          * correctly.
    254          */
    255         private double mValue;
    256         /** The minimum value we would expect. */
    257         private double mMinValue;
    258         /** The maximum value we would expect. */
    259         private double mMaxValue;
    260 
    261         Floaty(Floaty a) {
    262             mNumberOfBits = a.mNumberOfBits;
    263             mCanBeNan = a.mCanBeNan;
    264             mHasRange = a.mHasRange;
    265             mValue = a.mValue;
    266             mMinValue = a.mMinValue;
    267             mMaxValue = a.mMaxValue;
    268         }
    269 
    270         /**
    271          * Creates a Floaty and initializes it so that the values passed could be represented by it.
    272          * We also expand what's allowed by +/- mUlpFactor to allow for the various rounding modes.
    273          * values[0] is treated as the representative case, otherwise the order of values does not matter.
    274          */
    275         Floaty(int numberOfBits, double values[]) {
    276             //Log.w("Floaty(double[], ulp)", "input: " + Arrays.toString(values) + ", ulp " + Integer.toString(mUlpFactor));
    277             mNumberOfBits = numberOfBits;
    278             mCanBeNan = false;
    279             mHasRange = false;
    280             mValue = values[0];
    281             for (double f: values) {
    282                 if (f != f) {
    283                     mCanBeNan = true;
    284                     continue;
    285                 }
    286                 updateMinAndMax(f);
    287                 // For relaxed mode, we don't require support of subnormal values.
    288                 // If we have a subnormal value, we'll allow both the normalized value and zero,
    289                 // to cover the two ways this small value might be handled.
    290                 if (mIsRelaxedPrecision || mIsNative) {
    291                     if (IsSubnormal(f)) {
    292                         updateMinAndMax(0.f);
    293                         updateMinAndMax(smallestNormal(f));
    294                     }
    295                 }
    296             }
    297             // Expand the range by one ulp factor to cover for the different rounding modes.
    298             ExpandRangeByUlpFactor();
    299             //Log.w("Floaty(double[], ulp)", "output: " +  toString());
    300         }
    301 
    302         /** Modify the mMinValue and mMaxValue so that f is contained within the range. */
    303         private void updateMinAndMax(double f) {
    304             if (mHasRange) {
    305                 if (f < mMinValue) {
    306                     mMinValue = f;
    307                 }
    308                 if (f > mMaxValue) {
    309                     mMaxValue = f;
    310                 }
    311             } else {
    312                 mHasRange = true;
    313                 mMinValue = f;
    314                 mMaxValue = f;
    315             }
    316         }
    317 
    318         /** Modify mMinValue and mMaxValue to allow one extra ulp factor of error on each side. */
    319         void ExpandRangeByUlpFactor() {
    320             if (mHasRange && mUlpFactor > 0) {
    321                 // Expand the edges by the specified factor.
    322                 ExpandMin(mUlpFactor);
    323                 ExpandMax(mUlpFactor);
    324             }
    325         }
    326 
    327         /** Expand the mMinValue by the number of ulp specified. */
    328         private void ExpandMin(int ulpFactor) {
    329             //Log.w("ExpandMin", java.lang.Double.toString(mMinValue) + " by " + Integer.toString(ulpFactor));
    330             if (!mHasRange) {
    331                 return;
    332             }
    333             if (mMinValue == Double.NEGATIVE_INFINITY ||
    334                 mMinValue == Double.POSITIVE_INFINITY) {
    335                 // Can't get any larger
    336                 //Log.w("ExpandMin", "infinity");
    337                 return;
    338             }
    339             double ulp = NegativeUlp();
    340             double delta = ulp * ulpFactor;
    341             double newValue = mMinValue + delta;
    342             /*
    343              * Reduce mMinValue but don't go negative if it's positive because the rounding error
    344              * we're simulating won't change the sign.
    345              */
    346             if (newValue < 0 && mMinValue > 0.f) {
    347                 mMinValue = 0.f;
    348             } else {
    349                 mMinValue = newValue;
    350             }
    351             // If subnormal, also allow the normalized value if it's smaller.
    352             if ((mIsRelaxedPrecision || mIsNative) && IsSubnormal(mMinValue)) {
    353                 if (mMinValue < 0) {
    354                     mMinValue = smallestNormal(-1.0f);
    355                 } else {
    356                     mMinValue = 0.f;
    357                 }
    358             }
    359             //Log.w("ExpandMin", "ulp " + java.lang.Double.toString(ulp) + ", delta " + java.lang.Double.toString(delta) + " for " + java.lang.Double.toString(mMinValue));
    360         }
    361 
    362         /** Expand the mMaxValue by the number of ulp specified. */
    363         private void ExpandMax(int ulpFactor) {
    364             //Log.w("ExpandMax", java.lang.Double.toString(mMaxValue) + " by " + Integer.toString(ulpFactor));
    365             if (!mHasRange) {
    366                 return;
    367             }
    368             if (mMaxValue == Double.NEGATIVE_INFINITY ||
    369                 mMaxValue == Double.POSITIVE_INFINITY) {
    370                 // Can't get any larger
    371                 //Log.w("ExpandMax", "infinity");
    372                 return;
    373             }
    374             double ulp = Ulp();
    375             double delta = ulp * ulpFactor;
    376             double newValue = mMaxValue + delta;
    377             /*
    378              * Increase mMaxValue but don't go positive if it's negative because the rounding error
    379              * we're simulating won't change the sign.
    380              */
    381             if (newValue > 0 && mMaxValue < 0.f) {
    382                 mMaxValue = 0.f;
    383             } else {
    384                 mMaxValue = newValue;
    385             }
    386             // If subnormal, also allow the normalized value if it's smaller.
    387             if ((mIsRelaxedPrecision || mIsNative) && IsSubnormal(mMaxValue)) {
    388                 if (mMaxValue > 0) {
    389                     mMaxValue = smallestNormal(1.0f);
    390                 } else {
    391                     mMaxValue = 0.f;
    392                 }
    393             }
    394             //Log.w("ExpandMax", "ulp " + java.lang.Double.toString(ulp) + ", delta " + java.lang.Double.toString(delta) + " for " + java.lang.Double.toString(mMaxValue));
    395         }
    396 
    397         /**
    398          * Returns true if f is smaller than the smallest normalized number that can be represented
    399          * by the number of bits we have.
    400          */
    401         private boolean IsSubnormal(double f) {
    402             double af = Math.abs(f);
    403             return 0 < af && af < smallestNormal(1.0f);
    404         }
    405 
    406         /**
    407          * Returns the smallest normal representable by the number of bits we have, of the same
    408          * sign as f.
    409          */
    410         private double smallestNormal(double f) {
    411             double answer = (mNumberOfBits == 32) ? Float.MIN_NORMAL : Double.MIN_NORMAL;
    412             if (f < 0) {
    413                 answer = -answer;
    414             }
    415             return answer;
    416         }
    417 
    418         /** Returns the unit of least precision for the maximum value allowed. */
    419         private double Ulp() {
    420             double u;
    421             if (mNumberOfBits == 32) {
    422                 u = Math.ulp((float)mMaxValue);
    423             } else {
    424                 u = Math.ulp(mMaxValue);
    425             }
    426             if (mIsRelaxedPrecision || mIsNative) {
    427                 u = Math.max(u, smallestNormal(1.f));
    428             }
    429             return u;
    430         }
    431 
    432         /** Returns the negative of the unit of least precision for the minimum value allowed. */
    433         private double NegativeUlp() {
    434             double u;
    435             if (mNumberOfBits == 32) {
    436                 u = -Math.ulp((float)mMinValue);
    437             } else {
    438                 u = -Math.ulp(mMinValue);
    439             }
    440             if (mIsRelaxedPrecision || mIsNative) {
    441                 u = Math.min(u, smallestNormal(-1.f));
    442             }
    443             return u;
    444         }
    445 
    446         /** Returns true if the number passed is among the values that are represented by this Floaty. */
    447         public boolean couldBe(double a) {
    448             return couldBe(a, 0.0);
    449         }
    450 
    451         /**
    452          * Returns true if the number passed is among the values that are represented by this Floaty.
    453          * An extra amount of allowed error can be specified.
    454          */
    455         public boolean couldBe(double a, double extraAllowedError) {
    456             //Log.w("Floaty.couldBe", "Can " + Double.toString(a) + " be " + toString() + "? ");
    457             // Handle the input being a NaN.
    458             if (a != a) {
    459                 //Log.w("couldBe", "true because is Naan");
    460                 return mCanBeNan;
    461             }
    462             // If the input is not a NaN, we better have a range.
    463             if (!mHasRange) {
    464                 return false;
    465             }
    466             // Handle the simple case.
    467             if ((mMinValue - extraAllowedError) <= a && a <= (mMaxValue + extraAllowedError)) {
    468                 return true;
    469             }
    470             // For native, we don't require returning +/- infinity.  If that's what we expect,
    471             // allow all answers.
    472             if (mIsNative) {
    473                 if (mMinValue == Double.NEGATIVE_INFINITY &&
    474                     mMaxValue == Double.NEGATIVE_INFINITY) {
    475                     return true;
    476                 }
    477                 if (mMinValue == Double.POSITIVE_INFINITY &&
    478                     mMaxValue == Double.POSITIVE_INFINITY) {
    479                     return true;
    480                 }
    481             }
    482             return false;
    483         }
    484 
    485 
    486         public double get64() { return mValue; }
    487         public double min64() { return mMinValue; }
    488         public double max64() { return mMaxValue; }
    489         /**
    490          * Returns mValue unless zero could be a legal value.  In that case, return it.
    491          * This is useful for testing functions where the behavior at zero is unusual,
    492          * e.g. reciprocals.  If mValue is already +0.0 or -0.0, don't change it, to
    493          * preserve the sign.
    494          */
    495         public double mid64() {
    496             if (mMinValue < 0.0 && mMaxValue > 0.0 && mValue != 0.0) {
    497                 return 0.0;
    498             }
    499             return mValue;
    500         }
    501 
    502         public float get32() { return (float) mValue; }
    503         public float min32() { return (float) mMinValue; }
    504         public float max32() { return (float) mMaxValue; }
    505         public float mid32() { return (float) mid64(); }
    506 
    507         public String toString() {
    508             String s = String.format("[f%d: ", mNumberOfBits);
    509             if (mCanBeNan) {
    510                 s += "NaN, ";
    511             }
    512             if (mHasRange) {
    513                 if (mNumberOfBits == 32) {
    514                     float min = (float)mMinValue;
    515                     float mid = (float)mValue;
    516                     float max = (float)mMaxValue;
    517                     s += String.format("%11.9g {%08x} to %11.9g {%08x} to %11.9g {%08x}", min,
    518                                        Float.floatToRawIntBits(min), mid,
    519                                        Float.floatToRawIntBits(mid), max,
    520                                        Float.floatToRawIntBits(max));
    521                 } else {
    522                     s += String.format("%24.9g {%16x} to %24.9g {%16x} to %24.9g {%16x}", mMinValue,
    523                                        Double.doubleToRawLongBits(mMinValue), mValue,
    524                                        Double.doubleToRawLongBits(mValue), mMaxValue,
    525                                        Double.doubleToRawLongBits(mMaxValue));
    526                 }
    527             }
    528             s += "]";
    529             return s;
    530         }
    531     }
    532 }
    533