blob: fed05633dc53fee3fa26cee3b47ed3255b832a46 [file] [log] [blame]
/*
* Copyright (C) 2015 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.
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
// This utility generates a Java file containing data used in the CTS test for
// FP16 arithmetic. The Java file containes a class with the following fields:
// * A 1D array of length 'n' containing various constants used in the test
// * Four n x n x 2 arrays, each containing the reference output for
// pair-wise addition, subtraction, multiplication and division. The
// reference output is a range accounting for tolerable error. The
// acceptable error is 3 x ULP for division and 1 x ULP for other
// operations.
typedef __fp16 half;
// Macros for names of the package, class and fields in the generated java file
#define PACKAGE_NAME "android.renderscript.cts"
#define CLASS_NAME "Float16TestData"
#define INPUT_ARRAY "input"
#define OUTPUT_ARRAY_ADD "ReferenceOutputForAdd"
#define OUTPUT_ARRAY_SUB "ReferenceOutputForSub"
#define OUTPUT_ARRAY_MUL "ReferenceOutputForMul"
#define OUTPUT_ARRAY_DIV "ReferenceOutputForDiv"
// Structure to hold an FP16 constant and its human-readable description, to be
// added as a comment, in the generated Java file
typedef struct {
unsigned short value;
const char* description;
} FP16Constant;
FP16Constant input[] = {
{ 0b0011110000000000, "one" },
{ 0b0100000000000000, "two" },
{ 0b0000000000000001, "smallest subnormal" },
{ 0b0000001111111111, "largest subnormal" },
{ 0b0000010000000000, "smallest normal" },
{ 0b0111101111111111, "largest normal" },
{ 0x3880, "0.562500" },
{ 0x3e80, "1.625000" },
{ 0x5140, "42.000000" },
{ 0x5ac0, "216.000000" },
{ 0x6c75, "4564.000000" },
{ 0x7b53, "60000.000000" },
{ 0b1011110000000000, "negative one" },
{ 0b1100000000000000, "negative two" },
{ 0b1000000000000001, "negative (smallest subnormal)" },
{ 0b1000001111111111, "negative (largest subnormal)" },
{ 0b1000010000000000, "negative (smallest normal)" },
{ 0b1111101111111111, "negative (largest normal)" },
{ 0xb880, "-0.562500" },
{ 0xbe80, "-1.625000" },
{ 0xd140, "-42.000000" },
{ 0xdac0, "-216.000000" },
{ 0xec75, "-4564.000000" },
{ 0xfb53, "-60000.000000" },
{ 0b0000000000000000, "zero" },
{ 0b0111110000000000, "infinity" },
{ 0b1000000000000000, "negative zero" },
{ 0b1111110000000000, "negative infinity" },
{ 0b0111110000000001, "nan" },
};
const int numInputs = sizeof(input) / sizeof(FP16Constant);
// 16-bit masks for extracting sign, exponent and mantissa bits
static unsigned short SIGN_MASK = 0x8000;
static unsigned short EXPONENT_MASK = 0x7C00;
static unsigned short MANTISSA_MASK = 0x03FF;
// NaN has all exponent bits set to 1 and a non-zero mantissa
int isFloat16NaN(unsigned short val) {
return (val & EXPONENT_MASK) == EXPONENT_MASK &&
(val & MANTISSA_MASK) != 0;
}
// Infinity has all exponent bits set to 1 and zeroes in mantissa
int isFloat16Infinite(unsigned short val) {
return (val & EXPONENT_MASK) == EXPONENT_MASK &&
(val & MANTISSA_MASK) == 0;
}
// Subnormal numbers have exponent bits set to 0 and a non-zero mantissa
int isFloat16SubNormal(unsigned short val) {
return (val & EXPONENT_MASK) == 0 && (val & MANTISSA_MASK) != 0;
}
// Negativity test checks the sign bit
int isFloat16Negative(unsigned short val) {
return (val & SIGN_MASK) != 0;
}
// Interpret a short as a FP16 value and convert to float
float half2float(unsigned short s) {
half h = *(half *) &s;
return (float) h;
}
// Return the short value representing a float value in FP16
unsigned short float2half(float f) {
half h = (half) f;
return *(unsigned short *) &h;
}
// Compute ULP for 'value' and store value +/- tolerance * ULP in bounds sarray
void getErrorBar(unsigned short value, int tolerance, unsigned short bounds[2]) {
// Validate 'tolerance' parameter
if (tolerance != 1 && tolerance != 3) {
fprintf(stderr, "Allowed ULP error should either be 1 or 3, and not %d\n",
tolerance);
exit(0);
}
half hValue = *(half *) &value;
half ulp;
// For Infinity and NaN, bounds are equal to 'value'
if (isFloat16Infinite(value) || isFloat16NaN(value)) {
bounds[0] = value;
bounds[1] = value;
return;
}
// Compute ULP
if (isFloat16SubNormal(value)) {
// 1 ulp for a subnormal number is the smallest possible subnormal
unsigned short ulpInShort = 0b0000000000000001;
ulp = *(half *) &ulpInShort;
}
else {
// 1 ulp for a non-subnormal number is (b - a) where
// - a has same exponent as 'value', zeroes for sign and mantissa
// - b has same exponent and sign as 'a', and has '1' in the mantissa
// (b - a) gives the ULP by getting rid of the implied '1' at the front of
// the mantissa
unsigned short a = (value & EXPONENT_MASK);
unsigned short b = (a | 1);
half hA = *(half *) &a;
half hB = *(half *) &b;
ulp = hB - hA;
}
// Compute error bar based on error tolerance
half lb = hValue - tolerance * ulp;
half ub = hValue + tolerance * ulp;
if (lb > ub) {
fprintf(stderr, "Warning! inconsistency in bounds\n");
fprintf(stderr, "Value: %f, ulp: %f\n", (float) hValue, (float) ulp);
fprintf(stderr, "lb: %f ub: %f\n", (float) lb, (float) ub);
fprintf(stderr, "lb: %x ub: %x\n", *(unsigned short *) &lb, *(unsigned short *) &ub);
}
// Set the bounds
bounds[0] = *(unsigned short *) &lb;
bounds[1] = *(unsigned short *) &ub;
// RS allows flush-to-zero for sub-normal results in relaxed precision.
// Flush lower bound of a positive sub-normal result to zero.
if (!isFloat16Negative(bounds[0]) && isFloat16SubNormal(bounds[0]))
bounds[0] = 0x0;
// Flush upper bound of a negative sub-normal result to negative zero.
if (isFloat16Negative(bounds[1]) && isFloat16SubNormal(bounds[1]))
bounds[1] = 0x0 | SIGN_MASK;
}
// Utilities that take 'unsigned short' representations of two fp16 values and
// return the result of an arithmetic operation as an 'unsigned short'.
typedef unsigned short operation_t(unsigned short, unsigned short);
unsigned short add(unsigned short a, unsigned short b) {
float op1 = half2float(a);
float op2 = half2float(b);
return float2half(op1 + op2);
}
unsigned short subtract(unsigned short a, unsigned short b) {
float op1 = half2float(a);
float op2 = half2float(b);
return float2half(op1 - op2);
}
unsigned short multiply(unsigned short a, unsigned short b) {
float op1 = half2float(a);
float op2 = half2float(b);
return float2half(op1 * op2);
}
unsigned short divide(unsigned short a, unsigned short b) {
float op1 = half2float(a);
float op2 = half2float(b);
return float2half(op1 / op2);
}
// Print Java code that initializes the input array (along with the description
// of the constant as a comment)
void printInput() {
printf("static short[] %s = {\n", INPUT_ARRAY);
for (int x = 0; x < numInputs; x ++)
printf("(short) 0x%04x, // %s\n", input[x].value, input[x].description);
printf("};\n\n");
}
// Print Java code that initializes the output array with the acceptable bounds
// on the output. For each pair of inputs, bounds are calculated on the result
// from applying 'operation' on the pair.
void printReferenceOutput(const char *fieldName, operation_t operation,
int tolerance) {
unsigned short result;
unsigned short resultBounds[2];
printf("static short[][][] %s = {\n", fieldName);
for (int x = 0; x < numInputs; x ++) {
printf("{");
for (int y = 0; y < numInputs; y ++) {
// Apply 'operation' and compute error bounds for the result.
result = operation(input[x].value, input[y].value);
getErrorBar(result, tolerance, resultBounds);
printf("{ (short) 0x%04x, (short) 0x%04x},", resultBounds[0],
resultBounds[1]);
}
printf("},\n");
}
printf("};\n\n");
}
const char *preamble = "/*\n"
" * Copyright (C) 2015 The Android Open Source Project\n"
" *\n"
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
" * you may not use this file except in compliance with the License.\n"
" * You may obtain a copy of the License at\n"
" *\n"
" * http://www.apache.org/licenses/LICENSE-2.0\n"
" *\n"
" * Unless required by applicable law or agreed to in writing, software\n"
" * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
" * See the License for the specific language governing permissions and\n"
" * limitations under the License.\n"
" */\n"
"\n"
"/* Don't edit this file! It is auto-generated by float16_gen.sh */\n\n"
"package "PACKAGE_NAME";\n\n"
"public class "CLASS_NAME" {\n";
int main() {
// Print a preamble with copyright and class declaration, followed by the
// input FP16 array, and reference outputs for pair-wise arithmetic
// operations.
printf("%s", preamble);
printInput();
printReferenceOutput(OUTPUT_ARRAY_ADD, add, 1);
printReferenceOutput(OUTPUT_ARRAY_SUB, subtract, 1);
printReferenceOutput(OUTPUT_ARRAY_MUL, multiply, 1);
printReferenceOutput(OUTPUT_ARRAY_DIV, divide, 3);
printf("}");
}