1 //
2 // SPDX-License-Identifier: BSD-3-Clause
3 // Copyright Contributors to the OpenEXR Project.
4 //
5 
6 // For the C version test, omit the lookup table, which validates that
7 // ``half.h`` works as a "header-only" implementation not requiring
8 // the compiled library. Note that the C-language support for half
9 // only includes conversion to and from float.
10 #define IMATH_HALF_NO_LOOKUP_TABLE
11 
12 #include <half.h>
13 #include <math.h>
14 #include <stdio.h>
15 #include <string.h>
16 
17 typedef union
18 {
19     uint32_t i;
20     float f;
21 } c_half_uif;
22 
23 static const c_half_uif half_to_float[1 << 16] =
24 #include "../Imath/toFloat.h"
25 
26 static const unsigned short half_eLut[1 << 9] =
27 #include "eLut.h"
28 
exp_long_convert(int i)29 static short exp_long_convert (int i)
30 {
31     int s = (i >> 16) & 0x00008000;
32     int e = ((i >> 23) & 0x000000ff) - (127 - 15);
33     int m = i & 0x007fffff;
34 
35     //fprintf( stderr, "old_convert: s %d   e = %d, m = %d\n", s, e, m );
36     if (e <= 0)
37     {
38         if (e < -10)
39         {
40             return s;
41         }
42 
43         m = m | 0x00800000;
44 
45         int t = 14 - e;
46         int a = (1 << (t - 1)) - 1;
47         int b = (m >> t) & 1;
48 
49         m = (m + a + b) >> t;
50 
51         //fprintf( stderr, " <OLD> e %d, m: 0x%08X, t %d, a: %d 0x%08X, b: %d 0x%08X\n", e, m, t, a, a, b, b );
52         return s | m;
53     }
54     else if (e == 0xff - (127 - 15))
55     {
56         if (m == 0)
57         {
58             return s | 0x7c00;
59         }
60         else
61         {
62             m >>= 13;
63             return s | 0x7c00 | m | (m == 0);
64         }
65     }
66     else
67     {
68         m = m + 0x00000fff + ((m >> 13) & 1);
69 
70         if (m & 0x00800000)
71         {
72             m = 0;  // overflow in significand,
73             e += 1; // adjust exponent
74         }
75         if (e > 30)
76         {
77             return s | 0x7c00; // if this returns, the half becomes an
78         }                      // infinity with the same sign as f.
79         return s | (e << 10) | (m >> 13);
80     }
81 }
82 
83 static uint16_t
exptable_method(float f)84 exptable_method (float f)
85 {
86     c_half_uif x;
87     uint16_t _h = 0;
88     x.f         = f;
89 
90     if (f == 0)
91     {
92         //
93         // Common special case - zero.
94         // Preserve the zero's sign bit.
95         //
96 
97         _h = (x.i >> 16);
98     }
99     else
100     {
101         //
102         // We extract the combined sign and exponent, e, from our
103         // floating-point number, f.  Then we convert e to the sign
104         // and exponent of the half number via a table lookup.
105         //
106         // For the most common case, where a normalized half is produced,
107         // the table lookup returns a non-zero value; in this case, all
108         // we have to do is round f's significand to 10 bits and combine
109         // the result with e.
110         //
111         // For all other cases (overflow, zeroes, denormalized numbers
112         // resulting from underflow, infinities and NANs), the table
113         // lookup returns zero, and we call a longer, non-inline function
114         // to do the float-to-half conversion.
115         //
116 
117         int e = (x.i >> 23) & 0x000001ff;
118 
119         e = half_eLut[e];
120 
121         if (e)
122         {
123             //
124             // Simple case - round the significand, m, to 10
125             // bits and combine it with the sign and exponent.
126             //
127 
128             int m = x.i & 0x007fffff;
129             _h    = e + ((m + 0x00000fff + ((m >> 13) & 1)) >> 13);
130         }
131         else
132         {
133             //
134             // Difficult case - call a function.
135             //
136             _h = exp_long_convert (x.i);
137         }
138     }
139     return _h;
140 }
141 
142 int
main(int argc,char * argv[])143 main (int argc, char* argv[])
144 {
145     int ret = 0;
146     c_half_uif conv;
147     half test, test2;
148     conv.f = HALF_DENORM_MIN + HALF_DENORM_MIN * 0.5f;
149     test   = imath_float_to_half (conv.f);
150     test2  = exptable_method (conv.f);
151     if (test != test2)
152     {
153         fprintf (stderr,
154                  "Invalid conversion of %.10g 0x%08X 0x%08X downconvert 0x%04X vs 0x%04X\n",
155                  conv.f,
156                  (conv.i >> 13) & 0x3ff,
157                  (conv.i >> 13) & 0x3ff,
158                  test,
159                  test2);
160         ret = 1;
161     }
162 
163     int diffcount = 0;
164     for (int i = 0; i < (1 << 16); ++i)
165     {
166         conv.f = imath_half_to_float ((half) i);
167         if (conv.i != half_to_float[i].i)
168         {
169             uint16_t h  = (uint16_t) i;
170             uint16_t he = (h >> 10) & 0x1f;
171             uint16_t hm = (h & 0x3ff);
172 
173 #ifdef __F16C__
174             // the intel instructions do something different w/ NaN values than the original half library
175             if (he == 0x1f && isnan (conv.f))
176             {
177                 ++diffcount;
178                 continue;
179             }
180 #endif
181             fprintf (
182                 stderr,
183                 "half to float %d: C gives %.10f (0x%08X) vs %.10f (0x%08X) [h 0x%04X he 0x%04X hm 0x%04X]\n",
184                 i,
185                 conv.f,
186                 conv.i,
187                 half_to_float[i].f,
188                 half_to_float[i].i,
189                 h,
190                 he,
191                 hm);
192             ret = 1;
193         }
194     }
195     if (diffcount != 0)
196         fprintf (
197             stderr,
198             "WARNING: Seems safe, but %d NaN values were different between hardware implementation and library implementation\n",
199             diffcount);
200 
201     for (int i = 0; i < (1 << 16); ++i)
202     {
203         conv = half_to_float[i];
204         test = imath_float_to_half (conv.f);
205         if (test != i)
206         {
207             // well, we ensure that nan stays nan after conversion by
208             // adding a low bit, so it won't always align
209             int e = (conv.i >> 23) & 0xff;
210             int m = (conv.i & 0x007fffff);
211             if (e == 255 && m != 0)
212                 continue;
213 
214             fprintf (stderr,
215                      "float to half %d: %.10f (0x%08X) gives %d 0x%04X (e is %d)\n",
216                      i,
217                      conv.f,
218                      conv.i,
219                      (int) test,
220                      test,
221                      e);
222             ret = 1;
223         }
224     }
225 
226     return ret;
227 }
228