xref: /aosp_15_r20/external/skia/tests/Float16Test.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2016 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkHalf.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkRandom.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkVx.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "tests/Test.h"
12*c8dee2aaSAndroid Build Coastguard Worker 
13*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
14*c8dee2aaSAndroid Build Coastguard Worker #include <cstdint>
15*c8dee2aaSAndroid Build Coastguard Worker #include <cstring>
16*c8dee2aaSAndroid Build Coastguard Worker 
17*c8dee2aaSAndroid Build Coastguard Worker // float = s[31] e[30:23] m[22:0]
18*c8dee2aaSAndroid Build Coastguard Worker static constexpr uint32_t kF32_Sign = 1 << 31;
19*c8dee2aaSAndroid Build Coastguard Worker static constexpr uint32_t kF32_Exp  = 255 << 23;
20*c8dee2aaSAndroid Build Coastguard Worker static constexpr uint32_t kF32_Mant = ~(kF32_Sign | kF32_Exp);
21*c8dee2aaSAndroid Build Coastguard Worker static constexpr int      kF32_Bias = 127;
22*c8dee2aaSAndroid Build Coastguard Worker 
23*c8dee2aaSAndroid Build Coastguard Worker // half  = s[15] e[14:10] m[9:0]
24*c8dee2aaSAndroid Build Coastguard Worker static constexpr uint32_t kF16_Sign = 1 << 15;
25*c8dee2aaSAndroid Build Coastguard Worker static constexpr uint32_t kF16_Exp  = 31 << 10;
26*c8dee2aaSAndroid Build Coastguard Worker static constexpr uint32_t kF16_Mant = ~(kF16_Sign | kF16_Exp);
27*c8dee2aaSAndroid Build Coastguard Worker static constexpr int      kF16_Bias = 15;
28*c8dee2aaSAndroid Build Coastguard Worker 
DEF_TEST(FloatToHalf,r)29*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(FloatToHalf, r) {
30*c8dee2aaSAndroid Build Coastguard Worker #if 0
31*c8dee2aaSAndroid Build Coastguard Worker     // Exhaustive test (slow)
32*c8dee2aaSAndroid Build Coastguard Worker     for (uint64_t bits = 0; bits <= 0xffffffff; bits++) {
33*c8dee2aaSAndroid Build Coastguard Worker         if (bits % (1 << 24) == 0) {
34*c8dee2aaSAndroid Build Coastguard Worker             SkDebugf("progress 0x%08X\n", (int) bits);
35*c8dee2aaSAndroid Build Coastguard Worker         }
36*c8dee2aaSAndroid Build Coastguard Worker #else
37*c8dee2aaSAndroid Build Coastguard Worker     // Check all 8-bit exponents and all 10-bit upper mantissas, with a combination of all 0s,
38*c8dee2aaSAndroid Build Coastguard Worker     // all 1s, and random bits in the remaining 13 fractional mantissa bits.
39*c8dee2aaSAndroid Build Coastguard Worker     static constexpr int kTestCount = /*sign*/2 * /*exp*/255 * /*man*/1024 * /*frac*/8;
40*c8dee2aaSAndroid Build Coastguard Worker     SkRandom rand;
41*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < kTestCount; ++i) {
42*c8dee2aaSAndroid Build Coastguard Worker         uint32_t sign = (i & 1) << 31;
43*c8dee2aaSAndroid Build Coastguard Worker         uint32_t exp  = ((i >> 1) & 255) << 23;
44*c8dee2aaSAndroid Build Coastguard Worker         uint32_t man  = ((i >> 9) & 1023) << 13;
45*c8dee2aaSAndroid Build Coastguard Worker         uint32_t frac = ((i >> 19) & 7); // 0 and 1 are special, 6 other values are random bits
46*c8dee2aaSAndroid Build Coastguard Worker         uint64_t bits = sign | exp | man | ((frac == 0) ? 0 :  // all 0s in lost fraction
47*c8dee2aaSAndroid Build Coastguard Worker                                             (frac == 1) ? (1 << 13) - 1 // all 1s in lost fraction
48*c8dee2aaSAndroid Build Coastguard Worker                                                         : rand.nextBits(13)); // random lost bits
49*c8dee2aaSAndroid Build Coastguard Worker #endif
50*c8dee2aaSAndroid Build Coastguard Worker 
51*c8dee2aaSAndroid Build Coastguard Worker         float f = SkBits2Float(bits);
52*c8dee2aaSAndroid Build Coastguard Worker         if (SkIsNaN(f)) {
53*c8dee2aaSAndroid Build Coastguard Worker #ifndef SK_DEBUG
54*c8dee2aaSAndroid Build Coastguard Worker             // We want float->half and half->float to play well with infinities and max
55*c8dee2aaSAndroid Build Coastguard Worker             // representable values in the 16-bit precision, but NaNs should have been caught ahead
56*c8dee2aaSAndroid Build Coastguard Worker             // of time, so the conversion logic is allowed to convert them to infinities in release
57*c8dee2aaSAndroid Build Coastguard Worker             // builds. We skip calling `to_half` in debug since it asserts that NaN isn't passed in.
58*c8dee2aaSAndroid Build Coastguard Worker             uint16_t actual2 = to_half(skvx::float2{f})[0];
59*c8dee2aaSAndroid Build Coastguard Worker             uint16_t actual4 = to_half(skvx::float4{f})[0];
60*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(r, (actual2 & kF16_Exp) == kF16_Exp);
61*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(r, (actual4 & kF16_Exp) == kF16_Exp);
62*c8dee2aaSAndroid Build Coastguard Worker #endif
63*c8dee2aaSAndroid Build Coastguard Worker             continue;
64*c8dee2aaSAndroid Build Coastguard Worker         }
65*c8dee2aaSAndroid Build Coastguard Worker 
66*c8dee2aaSAndroid Build Coastguard Worker         uint32_t s32 = (uint32_t) bits & kF32_Sign;
67*c8dee2aaSAndroid Build Coastguard Worker         uint32_t e32 = (uint32_t) bits & kF32_Exp;
68*c8dee2aaSAndroid Build Coastguard Worker         uint32_t m32 = (uint32_t) bits & kF32_Mant;
69*c8dee2aaSAndroid Build Coastguard Worker 
70*c8dee2aaSAndroid Build Coastguard Worker         // Half floats can represent a real exponent from -14 to 15. Anything less than that would
71*c8dee2aaSAndroid Build Coastguard Worker         // need to be a denorm, which is flushed to zero, or overflows and becomes infinity.
72*c8dee2aaSAndroid Build Coastguard Worker         int      e   = (int) (e32 >> 23) - kF32_Bias; // the true signed exponent
73*c8dee2aaSAndroid Build Coastguard Worker 
74*c8dee2aaSAndroid Build Coastguard Worker         uint32_t s16 = s32 >> 16;
75*c8dee2aaSAndroid Build Coastguard Worker         uint32_t e16;
76*c8dee2aaSAndroid Build Coastguard Worker         uint32_t m16;
77*c8dee2aaSAndroid Build Coastguard Worker         if (e < -kF16_Bias-10 || (e == -kF16_Bias-10 && m32 <= 0)) {
78*c8dee2aaSAndroid Build Coastguard Worker             // Rounds to zero
79*c8dee2aaSAndroid Build Coastguard Worker             e16 = 0;
80*c8dee2aaSAndroid Build Coastguard Worker             m16 = 0;
81*c8dee2aaSAndroid Build Coastguard Worker         } else if ((e32 | m32) < 0x38fe'0000) {
82*c8dee2aaSAndroid Build Coastguard Worker             // A subnormal non-zero f16 value
83*c8dee2aaSAndroid Build Coastguard Worker             e16 = 0;
84*c8dee2aaSAndroid Build Coastguard Worker             m16 = 0xffff & sk_bit_cast<uint32_t>(0.5f + SkBits2Float(e32 | m32));
85*c8dee2aaSAndroid Build Coastguard Worker         } else if ((e32 | m32) < 0x3880'0000) {
86*c8dee2aaSAndroid Build Coastguard Worker             // Rounds up to smallest normal f16 (2^-14)
87*c8dee2aaSAndroid Build Coastguard Worker             e16 = 1;
88*c8dee2aaSAndroid Build Coastguard Worker             m16 = 0;
89*c8dee2aaSAndroid Build Coastguard Worker         } else if (e > kF16_Bias) {
90*c8dee2aaSAndroid Build Coastguard Worker             // Either f32 infinity or a value larger than what rounds down to the max normal half.
91*c8dee2aaSAndroid Build Coastguard Worker             e16 = kF16_Exp;
92*c8dee2aaSAndroid Build Coastguard Worker             m16 = 0;
93*c8dee2aaSAndroid Build Coastguard Worker         } else {
94*c8dee2aaSAndroid Build Coastguard Worker             // A normal half value, which is rounded towards nearest even.
95*c8dee2aaSAndroid Build Coastguard Worker             e16 = (uint32_t) (e + kF16_Bias) << 10;
96*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT((e16 & ~kF16_Exp) == 0);
97*c8dee2aaSAndroid Build Coastguard Worker 
98*c8dee2aaSAndroid Build Coastguard Worker             // round to nearest even
99*c8dee2aaSAndroid Build Coastguard Worker             m32 += 0xfff + ((m32>>13)&1);
100*c8dee2aaSAndroid Build Coastguard Worker 
101*c8dee2aaSAndroid Build Coastguard Worker             if (m32 > kF32_Mant) {
102*c8dee2aaSAndroid Build Coastguard Worker                 // overflow
103*c8dee2aaSAndroid Build Coastguard Worker                 e16 += (1 << 10);
104*c8dee2aaSAndroid Build Coastguard Worker                 m16 = 0;
105*c8dee2aaSAndroid Build Coastguard Worker             } else {
106*c8dee2aaSAndroid Build Coastguard Worker                 m16 = m32 >> 13;
107*c8dee2aaSAndroid Build Coastguard Worker             }
108*c8dee2aaSAndroid Build Coastguard Worker         }
109*c8dee2aaSAndroid Build Coastguard Worker 
110*c8dee2aaSAndroid Build Coastguard Worker         // Expected conversion from f32 to f16
111*c8dee2aaSAndroid Build Coastguard Worker         uint16_t expected = s16 | e16 | m16;
112*c8dee2aaSAndroid Build Coastguard Worker         uint16_t actual2 = to_half(skvx::float2{f})[0];
113*c8dee2aaSAndroid Build Coastguard Worker         uint16_t actual4 = to_half(skvx::float4{f})[0];
114*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(r, expected == actual2);
115*c8dee2aaSAndroid Build Coastguard Worker         REPORTER_ASSERT(r, expected == actual4);
116*c8dee2aaSAndroid Build Coastguard Worker     }
117*c8dee2aaSAndroid Build Coastguard Worker }
118*c8dee2aaSAndroid Build Coastguard Worker 
119*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(FloatToHalf_Constants, r) {
120*c8dee2aaSAndroid Build Coastguard Worker     auto to_half = [](float f) { return skvx::to_half(skvx::float4{f})[0]; };
121*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(r, 0 == to_half(0.f));
122*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(r, kF16_Sign == to_half(-0.f));
123*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(r, SK_Half1 == to_half(1.f));
124*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(r, (kF16_Sign | SK_Half1) == to_half(-1.f));
125*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(r, SK_HalfMax == to_half(65504.f));
126*c8dee2aaSAndroid Build Coastguard Worker     REPORTER_ASSERT(r, SK_HalfMin == to_half(1.f / (1 << 14)));
127*c8dee2aaSAndroid Build Coastguard Worker }
128*c8dee2aaSAndroid Build Coastguard Worker 
129*c8dee2aaSAndroid Build Coastguard Worker DEF_TEST(HalfToFloat, r) {
130*c8dee2aaSAndroid Build Coastguard Worker      for (uint32_t bits = 0; bits <= 0xffff; bits++) {
131*c8dee2aaSAndroid Build Coastguard Worker         uint32_t s16 = bits & kF16_Sign;
132*c8dee2aaSAndroid Build Coastguard Worker         uint32_t e16 = bits & kF16_Exp;
133*c8dee2aaSAndroid Build Coastguard Worker         uint32_t m16 = bits & kF16_Mant;
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker         float actual2 = from_half(skvx::half2{(uint16_t) bits})[0];
136*c8dee2aaSAndroid Build Coastguard Worker         float actual4 = from_half(skvx::half4{(uint16_t) bits})[0];
137*c8dee2aaSAndroid Build Coastguard Worker 
138*c8dee2aaSAndroid Build Coastguard Worker         if (e16 == 0) {
139*c8dee2aaSAndroid Build Coastguard Worker             // De-normal f16 or a zero = 2^-14 * 0.[m16] = 2^-14 * 2^-10 * [m16].0
140*c8dee2aaSAndroid Build Coastguard Worker             float expected = (1.f / (1 << 14)) * (1.f / (1 << 10)) * m16;
141*c8dee2aaSAndroid Build Coastguard Worker             if (s16 != 0) {
142*c8dee2aaSAndroid Build Coastguard Worker                 expected *= -1.f;
143*c8dee2aaSAndroid Build Coastguard Worker             }
144*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(r, actual2 == expected);
145*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(r, actual4 == expected);
146*c8dee2aaSAndroid Build Coastguard Worker         } else if (e16 == kF16_Exp) {
147*c8dee2aaSAndroid Build Coastguard Worker             if (m16 != 0) {
148*c8dee2aaSAndroid Build Coastguard Worker                 // A NaN stays NaN
149*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(r, SkIsNaN(actual2));
150*c8dee2aaSAndroid Build Coastguard Worker                 REPORTER_ASSERT(r, SkIsNaN(actual4));
151*c8dee2aaSAndroid Build Coastguard Worker             } else {
152*c8dee2aaSAndroid Build Coastguard Worker                 // +/- infinity stays infinite
153*c8dee2aaSAndroid Build Coastguard Worker                 if (s16) {
154*c8dee2aaSAndroid Build Coastguard Worker                     REPORTER_ASSERT(r, actual2 == SK_ScalarNegativeInfinity);
155*c8dee2aaSAndroid Build Coastguard Worker                     REPORTER_ASSERT(r, actual4 == SK_ScalarNegativeInfinity);
156*c8dee2aaSAndroid Build Coastguard Worker                 } else {
157*c8dee2aaSAndroid Build Coastguard Worker                     REPORTER_ASSERT(r, actual2 == SK_ScalarInfinity);
158*c8dee2aaSAndroid Build Coastguard Worker                     REPORTER_ASSERT(r, actual4 == SK_ScalarInfinity);
159*c8dee2aaSAndroid Build Coastguard Worker                 }
160*c8dee2aaSAndroid Build Coastguard Worker             }
161*c8dee2aaSAndroid Build Coastguard Worker         } else {
162*c8dee2aaSAndroid Build Coastguard Worker             // A normal f16 is exactly representable in f32
163*c8dee2aaSAndroid Build Coastguard Worker             uint32_t s32 = s16 << 16;
164*c8dee2aaSAndroid Build Coastguard Worker             uint32_t e32 = ((e16 >> 10) + kF32_Bias - kF16_Bias) << 23;
165*c8dee2aaSAndroid Build Coastguard Worker             uint32_t m32 = m16 << 13;
166*c8dee2aaSAndroid Build Coastguard Worker 
167*c8dee2aaSAndroid Build Coastguard Worker             float expected = SkBits2Float(s32 | e32 | m32);
168*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(r, actual2 == expected);
169*c8dee2aaSAndroid Build Coastguard Worker             REPORTER_ASSERT(r, actual4 == expected);
170*c8dee2aaSAndroid Build Coastguard Worker         }
171*c8dee2aaSAndroid Build Coastguard Worker     }
172*c8dee2aaSAndroid Build Coastguard Worker }
173