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