1 /*
2  * Copyright 2020 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 @file:Suppress("KotlinRedundantDiagnosticSuppress")
17 
18 package androidx.compose.ui.graphics
19 
20 import androidx.compose.ui.util.floatFromBits
21 import kotlin.jvm.JvmInline
22 
23 /**
24  * The `Float16` class is a wrapper and a utility class to manipulate half-precision 16-bit
25  * [IEEE 754](https://en.wikipedia.org/wiki/Half-precision_floating-point_format) floating point
26  * data types (also called fp16 or binary16). A half-precision float can be created from or
27  * converted to single-precision floats, and is stored in a short data type. To distinguish short
28  * values holding half-precision floats from regular short values, it is recommended to use the
29  * `@HalfFloat` annotation.
30  *
31  * The IEEE 754 standard specifies an fp16 as having the following format:
32  * - Sign bit: 1 bit
33  * - Exponent width: 5 bits
34  * - Significand: 10 bits
35  *
36  * The format is laid out as follows:
37  * ```
38  *     1   11111   1111111111
39  *     ^   --^--   -----^----
40  *     sign  |          |_______ significand
41  *     |
42  *     -- exponent
43  * ```
44  *
45  * Half-precision floating points can be useful to save memory and/or bandwidth at the expense of
46  * range and precision when compared to single-precision floating points (fp32).
47  *
48  * To help you decide whether fp16 is the right storage type for you need, please refer to the table
49  * below that shows the available precision throughout the range of possible values. The *precision*
50  * column indicates the step size between two consecutive numbers in a specific part of the range.
51  * <table summary="Precision of fp16 across the range">
52  * <tr><th>Range start</th><th>Precision</th></tr>
53  * <tr><td>0</td><td>1/16,777,216</td></tr>
54  * <tr><td>1/16,384</td><td>1/16,777,216</td></tr>
55  * <tr><td>1/8,192</td><td>1/8,388,608</td></tr>
56  * <tr><td>1/4,096</td><td>1/4,194,304</td></tr>
57  * <tr><td>1/2,048</td><td>1/2,097,152</td></tr>
58  * <tr><td>1/1,024</td><td>1/1,048,576</td></tr>
59  * <tr><td>1/512</td><td>1/524,288</td></tr>
60  * <tr><td>1/256</td><td>1/262,144</td></tr>
61  * <tr><td>1/128</td><td>1/131,072</td></tr>
62  * <tr><td>1/64</td><td>1/65,536</td></tr>
63  * <tr><td>1/32</td><td>1/32,768</td></tr>
64  * <tr><td>1/16</td><td>1/16,384</td></tr>
65  * <tr><td>1/8</td><td>1/8,192</td></tr>
66  * <tr><td>1/4</td><td>1/4,096</td></tr>
67  * <tr><td>1/2</td><td>1/2,048</td></tr>
68  * <tr><td>1</td><td>1/1,024</td></tr>
69  * <tr><td>2</td><td>1/512</td></tr>
70  * <tr><td>4</td><td>1/256</td></tr>
71  * <tr><td>8</td><td>1/128</td></tr>
72  * <tr><td>16</td><td>1/64</td></tr>
73  * <tr><td>32</td><td>1/32</td></tr>
74  * <tr><td>64</td><td>1/16</td></tr>
75  * <tr><td>128</td><td>1/8</td></tr>
76  * <tr><td>256</td><td>1/4</td></tr>
77  * <tr><td>512</td><td>1/2</td></tr>
78  * <tr><td>1,024</td><td>1</td></tr>
79  * <tr><td>2,048</td><td>2</td></tr>
80  * <tr><td>4,096</td><td>4</td></tr>
81  * <tr><td>8,192</td><td>8</td></tr>
82  * <tr><td>16,384</td><td>16</td></tr>
83  * <tr><td>32,768</td><td>32</td></tr> </table>
84  *
85  * This table shows that numbers higher than 1024 lose all fractional precision.
86  */
87 @JvmInline
88 internal value class Float16(val halfValue: Short) : Comparable<Float16> {
89     /**
90      * Constructs a newly allocated `Float16` object that represents the argument converted to a
91      * half-precision float.
92      *
93      * @param value The value to be represented by the `Float16`
94      */
95     constructor(value: Float) : this(floatToHalf(value))
96 
97     /**
98      * Constructs a newly allocated `Float16` object that represents the argument converted to a
99      * half-precision float.
100      *
101      * @param value The value to be represented by the `Float16`
102      */
103     constructor(value: Double) : this(value.toFloat())
104 
105     /**
106      * Returns the value of this `Float16` as a `Byte` after a narrowing primitive conversion.
107      *
108      * @return The half-precision float value represented by this object converted to type `Byte`
109      */
toBytenull110     fun toByte(): Byte = toFloat().toInt().toByte()
111 
112     /**
113      * Returns the value of this `Float16` as a `Short` after a narrowing primitive conversion.
114      *
115      * @return The half-precision float value represented by this object converted to type `Short`
116      */
117     fun toShort(): Short = toFloat().toInt().toShort()
118 
119     /**
120      * Returns the value of this `Float16` as a `Int` after a narrowing primitive conversion.
121      *
122      * @return The half-precision float value represented by this object converted to type `Int`
123      */
124     fun toInt(): Int = toFloat().toInt()
125 
126     /**
127      * Returns the value of this `Float16` as a `Long` after a narrowing primitive conversion.
128      *
129      * @return The half-precision float value represented by this object converted to type `Long`
130      */
131     fun toLong(): Long = toFloat().toLong()
132 
133     /**
134      * Returns the value of this `Float16` as a `Float` after a widening primitive conversion.
135      *
136      * @return The half-precision float value represented by this object converted to type `Float`
137      */
138     fun toFloat(): Float = halfToFloat(halfValue)
139 
140     /**
141      * Returns the value of this `Float16` as a `Double` after a widening primitive conversion.
142      *
143      * @return The half-precision float value represented by this object converted to type `Double`
144      */
145     fun toDouble(): Double = toFloat().toDouble()
146 
147     /**
148      * Returns a representation of the half-precision float value according to the bit layout
149      * described in [Float16].
150      *
151      * Unlike [toRawBits], this method collapses all possible Not-a-Number values to a single
152      * canonical Not-a-Number value defined by [NaN].
153      *
154      * @return The bits that represent the half-precision float value
155      */
156     fun toBits(): Int =
157         if (isNaN()) {
158             Fp16TheNaN
159         } else {
160             halfValue.toInt() and 0xffff
161         }
162 
163     /**
164      * Returns a representation of the half-precision float value according to the bit layout
165      * described in [Float16].
166      *
167      * @return The bits that represent the half-precision float value
168      */
toRawBitsnull169     fun toRawBits(): Int = halfValue.toInt() and 0xffff
170 
171     /**
172      * Returns a string representation of the specified half-precision float value. See [toString]
173      * for more information.
174      *
175      * @return A string representation of this `Float16` object
176      */
177     override fun toString(): String = toFloat().toString()
178 
179     /**
180      * Compares to another half-precision float value. The following conditions apply during the
181      * comparison:
182      * * [NaN] is considered by this method to be equal to itself and greater than all other
183      *   half-precision float values (including [PositiveInfinity])
184      * * [PositiveZero] is considered by this method to be greater than [NegativeZero].
185      *
186      * @param other The half-precision float value to compare to the half-precision value
187      *   represented by this `Float16` object
188      * @return The value `0` if `this` is numerically equal to [other]; a value less than `0` if
189      *   `this` is numerically less than [other]; and a value greater than `0` if `this` is
190      *   numerically greater than [other]
191      */
192     override operator fun compareTo(other: Float16): Int {
193         if (isNaN()) {
194             return if (other.isNaN()) 0 else 1
195         } else if (other.isNaN()) {
196             return -1
197         }
198         return toCompareValue(halfValue).compareTo(toCompareValue(other.halfValue))
199     }
200 
201     /**
202      * Returns the sign of this half-precision float value.
203      * * `-1.0` if the value is negative,
204      * * zero if the value is zero,
205      * * `1.0` if the value is positive
206      *
207      * Special case:
208      * * `NaN.sign` is `NaN`
209      */
210     val sign: Float16
211         get() {
212             val v = halfValue.toInt() and Fp16Combined
213             val u =
214                 if ((v > Fp16ExponentMax) or (v == 0)) { // 0.0 or NaN
215                     v
216                 } else {
217                     (halfValue.toInt() and Fp16SignMask) or Fp16One
218                 }
219             return Float16(u.toShort())
220         }
221 
222     /** Returns a [Float16] with the magnitude of this and the sign of [sign] */
withSignnull223     fun withSign(sign: Float16): Float16 =
224         Float16(
225             (sign.halfValue.toInt() and Fp16SignMask or (halfValue.toInt() and Fp16Combined))
226                 .toShort()
227         )
228 
229     /**
230      * Returns the absolute value of the half-precision float. Special values are handled in the
231      * following ways:
232      * * If the specified half-precision float is [NaN], the result is [NaN]
233      * * If the specified half-precision float is zero (negative or positive), the result is
234      *   positive zero (see [PositiveZero])
235      * * If the specified half-precision float is infinity (negative or positive), the result is
236      *   positive infinity (see [PositiveInfinity])
237      */
238     fun absoluteValue(): Float16 {
239         return Float16((halfValue.toInt() and Fp16Combined).toShort())
240     }
241 
242     /**
243      * Returns the closest integral half-precision float value to the this half-precision float
244      * value. Special values are handled in the following ways:
245      * * If the specified half-precision float is [NaN], the result is [NaN]
246      * * If the specified half-precision float is infinity (negative or positive), the result is
247      *   infinity (with the same sign)
248      * * If the specified half-precision float is zero (negative or positive), the result is zero
249      *   (with the same sign)
250      *
251      * @return The value of the specified half-precision float rounded to the nearest half-precision
252      *   float value
253      */
roundnull254     fun round(): Float16 {
255         val bits = halfValue.toInt() and 0xffff
256         var e = bits and 0x7fff
257         var result = bits
258 
259         if (e < 0x3c00) {
260             result = result and Fp16SignMask
261             result = result or (0x3c00 and if (e >= 0x3800) 0xffff else 0x0)
262         } else if (e < 0x6400) {
263             e = 25 - (e shr 10)
264             val mask = (1 shl e) - 1
265             result += 1 shl e - 1
266             result = result and mask.inv()
267         }
268 
269         return Float16(result.toShort())
270     }
271 
272     /**
273      * Returns the smallest half-precision float value toward negative infinity greater than or
274      * equal to this half-precision float value. Special values are handled in the following ways:
275      * * If the specified half-precision float is [NaN], the result is [NaN]
276      * * If the specified half-precision float is infinity (negative or positive), the result is
277      *   infinity (with the same sign)
278      * * If the specified half-precision float is zero (negative or positive), the result is zero
279      *   (with the same sign)
280      *
281      * @return The smallest half-precision float value toward negative infinity greater than or
282      *   equal to the half-precision float value
283      */
ceilnull284     fun ceil(): Float16 {
285         val bits = halfValue.toInt() and 0xffff
286         var e = bits and 0x7fff
287         var result = bits
288 
289         if (e < 0x3c00) {
290             result = result and Fp16SignMask
291             result = result or (0x3c00 and -((bits shr 15).inv() and if (e != 0) 1 else 0))
292         } else if (e < 0x6400) {
293             e = 25 - (e shr 10)
294             val mask = (1 shl e) - 1
295             result += mask and (bits shr 15) - 1
296             result = result and mask.inv()
297         }
298 
299         return Float16(result.toShort())
300     }
301 
302     /**
303      * Returns the largest half-precision float value toward positive infinity less than or equal to
304      * this half-precision float value. Special values are handled in the following ways:
305      * * If the specified half-precision float is [NaN], the result is [NaN]
306      * * If the specified half-precision float is infinity (negative or positive), the result is
307      *   infinity (with the same sign)
308      * * If the specified half-precision float is zero (negative or positive), the result is zero
309      *   (with the same sign)
310      *
311      * @return The largest half-precision float value toward positive infinity less than or equal to
312      *   the half-precision float value
313      */
floornull314     fun floor(): Float16 {
315         val bits = halfValue.toInt() and 0xffff
316         var e = bits and 0x7fff
317         var result = bits
318 
319         if (e < 0x3c00) {
320             result = result and Fp16SignMask
321             result = result or (0x3c00 and if (bits > 0x8000) 0xffff else 0x0)
322         } else if (e < 0x6400) {
323             e = 25 - (e shr 10)
324             val mask = (1 shl e) - 1
325             result += mask and -(bits shr 15)
326             result = result and mask.inv()
327         }
328 
329         return Float16(result.toShort())
330     }
331 
332     /**
333      * Returns the truncated half-precision float value of this half-precision float value. Special
334      * values are handled in the following ways:
335      * * If the specified half-precision float is NaN, the result is NaN
336      * * If the specified half-precision float is infinity (negative or positive), the result is
337      *   infinity (with the same sign)
338      * * If the specified half-precision float is zero (negative or positive), the result is zero
339      *   (with the same sign)
340      *
341      * @return The truncated half-precision float value of the half-precision float value
342      */
truncnull343     fun trunc(): Float16 {
344         val bits = halfValue.toInt() and 0xffff
345         var e = bits and 0x7fff
346         var result = bits
347 
348         if (e < 0x3c00) {
349             result = result and Fp16SignMask
350         } else if (e < 0x6400) {
351             e = 25 - (e shr 10)
352             val mask = (1 shl e) - 1
353             result = result and mask.inv()
354         }
355 
356         return Float16(result.toShort())
357     }
358 
359     /**
360      * The unbiased exponent used in the representation of the specified half-precision float value.
361      * if the value is NaN or infinite, this* method returns [MaxExponent] + 1. If the argument is 0
362      * or a subnormal representation, this method returns [MinExponent] - 1.
363      */
364     val exponent: Int
365         get() = (halfValue.toInt().ushr(Fp16ExponentShift) and Fp16ExponentMask) - Fp16ExponentBias
366 
367     /**
368      * The significand, or mantissa, used in the representation of this half-precision float value.
369      */
370     val significand: Int
371         get() = halfValue.toInt() and Fp16SignificandMask
372 
373     /**
374      * Returns true if this `Float16` value represents a Not-a-Number, false otherwise.
375      *
376      * @return True if the value is a NaN, false otherwise
377      */
isNaNnull378     fun isNaN(): Boolean = halfValue.toInt() and Fp16Combined > Fp16ExponentMax
379 
380     /**
381      * Returns true if the half-precision float value represents infinity, false otherwise.
382      *
383      * @return True if the value is positive infinity or negative infinity, false otherwise
384      */
385     fun isInfinite(): Boolean = halfValue.toInt() and Fp16Combined == Fp16ExponentMax
386 
387     /**
388      * Returns false if the half-precision float value represents infinity, true otherwise.
389      *
390      * @return False if the value is positive infinity or negative infinity, true otherwise
391      */
392     fun isFinite(): Boolean = halfValue.toInt() and Fp16Combined != Fp16ExponentMax
393 
394     /**
395      * Returns true if the half-precision float value is normalized (does not have a subnormal
396      * representation). If the specified value is [PositiveInfinity], [NegativeInfinity],
397      * [PositiveZero], [NegativeZero], [NaN] or any subnormal number, this method returns false.
398      *
399      * @return True if the value is normalized, false otherwise
400      */
401     fun isNormalized(): Boolean {
402         val v = halfValue.toInt() and Fp16ExponentMax
403         return (v != 0) and (v != Fp16ExponentMax)
404     }
405 
406     /**
407      * Returns a hexadecimal string representation of the half-precision float value. If the value
408      * is a NaN, the result is `"NaN"`, otherwise the result follows this format:
409      * * If the sign is positive, no sign character appears in the result
410      * * If the sign is negative, the first character is `'-'`
411      * * If the value is inifinity, the string is `"Infinity"`
412      * * If the value is 0, the string is `"0x0.0p0"`
413      * * If the value has a normalized representation, the exponent and significand are represented
414      *   in the string in two fields. The significand starts with `"0x1."` followed by its lowercase
415      *   hexadecimal representation. Trailing zeroes are removed unless all digits are 0, then a
416      *   single zero is used. The significand representation is followed by the exponent,
417      *   represented by `"p"`, itself followed by a decimal string of the unbiased exponent
418      * * If the value has a subnormal representation, the significand starts with `"0x0."` followed
419      *   by its lowercase hexadecimal representation. Trailing zeroes are removed unless all digits
420      *   are 0, then a single zero is used. The significand representation is followed by the
421      *   exponent, represented by `"p-14"`
422      *
423      * @return A hexadecimal string representation of the specified value
424      */
toHexStringnull425     fun toHexString(): String {
426         val o = StringBuilder()
427 
428         val bits = halfValue.toInt() and 0xffff
429         val s = bits.ushr(Fp16SignShift)
430         val e = bits.ushr(Fp16ExponentShift) and Fp16ExponentMask
431         val m = bits and Fp16SignificandMask
432 
433         if (e == 0x1f) { // Infinite or NaN
434             if (m == 0) {
435                 if (s != 0) o.append('-')
436                 o.append("Infinity")
437             } else {
438                 o.append("NaN")
439             }
440         } else {
441             if (s == 1) o.append('-')
442             if (e == 0) {
443                 if (m == 0) {
444                     o.append("0x0.0p0")
445                 } else {
446                     o.append("0x0.")
447                     val significand = m.toString(16)
448                     o.append(significand.replaceFirst("0{2,}$".toRegex(), ""))
449                     o.append("p-14")
450                 }
451             } else {
452                 o.append("0x1.")
453                 val significand = m.toString(16)
454                 o.append(significand.replaceFirst("0{2,}$".toRegex(), ""))
455                 o.append('p')
456                 o.append((e - Fp16ExponentBias).toString())
457             }
458         }
459 
460         return o.toString()
461     }
462 
463     companion object {
464         /** The number of bits used to represent a half-precision float value. */
465         const val Size = 16
466 
467         /**
468          * Epsilon is the difference between 1.0 and the next value representable by a
469          * half-precision floating-point.
470          */
471         val Epsilon = Float16(0x1400.toShort())
472 
473         /** Maximum exponent a finite half-precision float may have. */
474         const val MaxExponent = 15
475         /** Minimum exponent a normalized half-precision float may have. */
476         const val MinExponent = -14
477 
478         /** Smallest negative value a half-precision float may have. */
479         val LowestValue = Float16(0xfbff.toShort())
480         /** Maximum positive finite value a half-precision float may have. */
481         val MaxValue = Float16(0x7bff.toShort())
482         /** Smallest positive normal value a half-precision float may have. */
483         val MinNormal = Float16(0x0400.toShort())
484         /** Smallest positive non-zero value a half-precision float may have. */
485         val MinValue = Float16(0x0001.toShort())
486         /** A Not-a-Number representation of a half-precision float. */
487         val NaN = Float16(0x7e00.toShort())
488         /** Negative infinity of type half-precision float. */
489         val NegativeInfinity = Float16(0xfc00.toShort())
490         /** Negative 0 of type half-precision float. */
491         val NegativeZero = Float16(0x8000.toShort())
492         /** Positive infinity of type half-precision float. */
493         val PositiveInfinity = Float16(0x7c00.toShort())
494         /** Positive 0 of type half-precision float. */
495         val PositiveZero = Float16(0x0000.toShort())
496     }
497 }
498 
499 private const val Fp16SignShift = 15
500 private const val Fp16SignMask = 0x8000
501 private const val Fp16ExponentShift = 10
502 private const val Fp16ExponentMask = 0x1f
503 private const val Fp16SignificandMask = 0x3ff
504 private const val Fp16ExponentBias = 15
505 private const val Fp16Combined = 0x7fff
506 private const val Fp16ExponentMax = 0x7c00
507 private const val Fp16One = 0x3c00
508 private const val Fp16TheNaN = 0x7e00
509 
510 private const val Fp32SignShift = 31
511 private const val Fp32ExponentShift = 23
512 private const val Fp32ExponentMask = 0xff
513 private const val Fp32SignificandMask = 0x7fffff
514 private const val Fp32ExponentBias = 127
515 private const val Fp32QNaNMask = 0x400000
516 
517 private const val Fp32DenormalMagic = 126 shl 23
518 private val Fp32DenormalFloat = floatFromBits(Fp32DenormalMagic)
519 
520 @Suppress("NOTHING_TO_INLINE")
toCompareValuenull521 private inline fun toCompareValue(value: Short): Int {
522     return if (value.toInt() and Fp16SignMask != 0) {
523         0x8000 - (value.toInt() and 0xffff)
524     } else {
525         value.toInt() and 0xffff
526     }
527 }
528 
529 /**
530  * Convert a single-precision float to a half-precision float, stored as [Short] data type to hold
531  * its 16 bits.
532  */
533 @Suppress("NOTHING_TO_INLINE")
floatToHalfnull534 internal inline fun floatToHalf(f: Float): Short {
535     val bits = f.toRawBits()
536     val s = bits ushr Fp32SignShift
537     var e = bits ushr Fp32ExponentShift and Fp32ExponentMask
538     var m = bits and Fp32SignificandMask
539 
540     var outE = 0
541     var outM = 0
542 
543     if (e == 0xff) { // Infinite or NaN
544         outE = 0x1f
545         outM = if (m != 0) 0x200 else 0
546     } else {
547         e = e - Fp32ExponentBias + Fp16ExponentBias
548         if (e >= 0x1f) { // Overflow
549             outE = 0x31
550         } else if (e <= 0) { // Underflow
551             if (e < -10) {
552                 // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
553             } else {
554                 // The fp32 value is a normalized float less than MIN_NORMAL,
555                 // we convert to a denorm fp16
556                 m = m or 0x800000 shr 1 - e
557                 if (m and 0x1000 != 0) m += 0x2000
558                 outM = m shr 13
559             }
560         } else {
561             outE = e
562             outM = m shr 13
563             if (m and 0x1000 != 0) {
564                 // Round to nearest "0.5" up
565                 var out = outE shl Fp16ExponentShift or outM
566                 out++
567                 return (out or (s shl Fp16SignShift)).toShort()
568             }
569         }
570     }
571 
572     return (s shl Fp16SignShift or (outE shl Fp16ExponentShift) or outM).toShort()
573 }
574 
575 /** Convert a half-precision float to a single-precision float. */
576 @Suppress("NOTHING_TO_INLINE")
halfToFloatnull577 internal inline fun halfToFloat(h: Short): Float {
578     val bits = h.toInt() and 0xffff
579     val s = bits and Fp16SignMask
580     val e = bits ushr Fp16ExponentShift and Fp16ExponentMask
581     val m = bits and Fp16SignificandMask
582 
583     var outE = 0
584     var outM = 0
585 
586     if (e == 0) { // Denormal or 0
587         if (m != 0) {
588             // Convert denorm fp16 into normalized fp32
589             var o = floatFromBits(Fp32DenormalMagic + m)
590             o -= Fp32DenormalFloat
591             return if (s == 0) o else -o
592         }
593     } else {
594         outM = m shl 13
595         if (e == 0x1f) { // Infinite or NaN
596             outE = 0xff
597             if (outM != 0) { // SNaNs are quieted
598                 outM = outM or Fp32QNaNMask
599             }
600         } else {
601             outE = e - Fp16ExponentBias + Fp32ExponentBias
602         }
603     }
604 
605     val out = s shl 16 or (outE shl Fp32ExponentShift) or outM
606     return floatFromBits(out)
607 }
608 
609 /**
610  * Returns the smaller of two half-precision float values (the value closest to negative infinity).
611  * Special values are handled in the following ways:
612  * * If either value is [Float16.NaN], the result is [Float16.NaN]
613  * * [Float16.NegativeZero] is smaller than [Float16.PositiveZero]
614  *
615  * @param x The first half-precision value
616  * @param y The second half-precision value
617  * @return The smaller of the two specified half-precision values
618  */
minnull619 internal fun min(x: Float16, y: Float16): Float16 {
620     if (x.isNaN() || y.isNaN()) {
621         return Float16.NaN
622     }
623     return if (x <= y) x else y
624 }
625 
626 /**
627  * Returns the larger of two half-precision float values (the value closest to positive infinity).
628  * Special values are handled in the following ways:
629  * * If either value is [Float16.NaN], the result is [Float16.NaN]
630  * * [Float16.PositiveZero] is greater than [Float16.NegativeZero]
631  *
632  * @param x The first half-precision value
633  * @param y The second half-precision value
634  * @return The larger of the two specified half-precision values
635  */
maxnull636 internal fun max(x: Float16, y: Float16): Float16 {
637     if (x.isNaN() || y.isNaN()) {
638         return Float16.NaN
639     }
640     return if (x >= y) x else y
641 }
642