|
7 | 7 | package org.mozilla.javascript.typedarrays; |
8 | 8 |
|
9 | 9 | public class ByteIo { |
| 10 | + |
| 11 | + // Float16 constants |
| 12 | + private static final double FLOAT16_MIN_NORMAL = 6.103515625e-5; // 2^-14 |
| 13 | + private static final double FLOAT16_MIN_SUBNORMAL = 5.960464477539063E-8; // 2^-24 |
| 14 | + |
10 | 15 | public static Byte readInt8(byte[] buf, int offset) { |
11 | 16 | return Byte.valueOf(buf[offset]); |
12 | 17 | } |
@@ -162,6 +167,177 @@ public static void writeUint64(byte[] buf, int offset, long val, boolean littleE |
162 | 167 | } |
163 | 168 | } |
164 | 169 |
|
| 170 | + public static Float readFloat16(byte[] buf, int offset, boolean littleEndian) { |
| 171 | + int bits = doReadInt16(buf, offset, littleEndian) & 0xffff; |
| 172 | + |
| 173 | + // Extract sign, exponent, and mantissa |
| 174 | + int sign = (bits >>> 15) & 0x1; |
| 175 | + int exponent = (bits >>> 10) & 0x1f; |
| 176 | + int mantissa = bits & 0x3ff; |
| 177 | + |
| 178 | + // Handle special cases |
| 179 | + if (exponent == 0) { |
| 180 | + if (mantissa == 0) { |
| 181 | + // Zero |
| 182 | + return sign == 0 ? 0.0f : -0.0f; |
| 183 | + } |
| 184 | + |
| 185 | + // Denormalized number |
| 186 | + float value = (float) ((double) mantissa / (1 << 10) * FLOAT16_MIN_NORMAL); |
| 187 | + return sign == 0 ? value : -value; |
| 188 | + |
| 189 | + } else if (exponent == 31) { |
| 190 | + if (mantissa == 0) { |
| 191 | + // Infinity |
| 192 | + return sign == 0 ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY; |
| 193 | + } |
| 194 | + |
| 195 | + // NaN |
| 196 | + return Float.NaN; |
| 197 | + |
| 198 | + } else { |
| 199 | + // Normalized number |
| 200 | + float value = |
| 201 | + (float) ((1.0 + (double) mantissa / (1 << 10)) * Math.pow(2, exponent - 15)); |
| 202 | + return sign == 0 ? value : -value; |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + public static void writeFloat16(byte[] buf, int offset, double val, boolean littleEndian) { |
| 207 | + float fval = (float) val; |
| 208 | + |
| 209 | + // Handle special cases |
| 210 | + if (Float.isNaN(fval)) { |
| 211 | + doWriteInt16(buf, offset, 0x7e00, littleEndian); |
| 212 | + return; |
| 213 | + } |
| 214 | + |
| 215 | + int sign = (Float.floatToIntBits(fval) >>> 31) & 0x1; |
| 216 | + float absVal = Math.abs(fval); |
| 217 | + |
| 218 | + if (Float.isInfinite(fval)) { |
| 219 | + // Infinity |
| 220 | + doWriteInt16(buf, offset, (sign << 15) | 0x7c00, littleEndian); |
| 221 | + return; |
| 222 | + } |
| 223 | + |
| 224 | + if (absVal == 0.0f) { |
| 225 | + // Zero |
| 226 | + doWriteInt16(buf, offset, sign << 15, littleEndian); |
| 227 | + return; |
| 228 | + } |
| 229 | + |
| 230 | + // Convert to float16 |
| 231 | + int exponent; |
| 232 | + int mantissa; |
| 233 | + |
| 234 | + // Check overflow using original double precision |
| 235 | + // Max representable float16 is 65504 (exp=30, mantissa=0x3ff) |
| 236 | + // Values >= 65520 definitely overflow to infinity |
| 237 | + // Values in [65504, 65520) might round to 65504 or infinity |
| 238 | + double absValDouble = Math.abs(val); |
| 239 | + if (absValDouble >= 65520.0) { |
| 240 | + // Definite overflow to infinity |
| 241 | + doWriteInt16(buf, offset, (sign << 15) | 0x7c00, littleEndian); |
| 242 | + return; |
| 243 | + } else if (absValDouble > 65504.0) { |
| 244 | + // Near overflow: value is between max finite and definite overflow |
| 245 | + // These values might round to 65504 or infinity depending on exact value |
| 246 | + // 65504 = 0x7BFF: exp=30, mantissa=0x3ff (all 1s) |
| 247 | + // Halfway point to next value would cause overflow |
| 248 | + // Values < 65520 should round to 65504 |
| 249 | + doWriteInt16(buf, offset, (sign << 15) | 0x7BFF, littleEndian); |
| 250 | + return; |
| 251 | + } |
| 252 | + if (absVal < (float) FLOAT16_MIN_NORMAL) { |
| 253 | + // Denormalized number - IEEE 754 round-to-nearest-even |
| 254 | + // Use original double precision for accuracy in denormalized range |
| 255 | + exponent = 0; |
| 256 | + |
| 257 | + double ratio = Math.abs(val) / FLOAT16_MIN_SUBNORMAL; |
| 258 | + int mantissaBase = (int) ratio; |
| 259 | + double fractional = ratio - mantissaBase; |
| 260 | + |
| 261 | + // IEEE 754 round-to-nearest-even |
| 262 | + if (fractional > 0.5) { |
| 263 | + // More than halfway: round up |
| 264 | + mantissa = mantissaBase + 1; |
| 265 | + } else if (fractional == 0.5) { |
| 266 | + // Tie: round to even |
| 267 | + if ((mantissaBase & 1) != 0) { |
| 268 | + mantissa = mantissaBase + 1; // Round up if odd |
| 269 | + } else { |
| 270 | + mantissa = mantissaBase; // Keep if even |
| 271 | + } |
| 272 | + } else { |
| 273 | + // Less than halfway: round down |
| 274 | + mantissa = mantissaBase; |
| 275 | + } |
| 276 | + } else { |
| 277 | + // Normalized |
| 278 | + int exp32 = ((Float.floatToIntBits(fval) >>> 23) & 0xff) - 127; |
| 279 | + exponent = exp32 + 15; |
| 280 | + |
| 281 | + // DEFENSIVE CHECK (UNCOVERED): Exponent overflow to infinity |
| 282 | + // This check is unreachable through normal API usage because: |
| 283 | + // - Guard at line 239 catches all values >= 65520 (which have exp32 >= 16) |
| 284 | + // - For exponent >= 31: need exp32 >= 16, meaning Float32 >= 2^16 = 65536 |
| 285 | + // - But 65536 >= 65520, so caught by guard before reaching here |
| 286 | + // - Mathematical impossibility: Can't pass guard (< 65520) AND overflow (>= 65536) |
| 287 | + // This defensive check is kept for defense-in-depth in case guards are modified. |
| 288 | + // See UNCOVERED_CODE_EXPLANATION.md for full mathematical proof. |
| 289 | + if (exponent >= 31) { |
| 290 | + // Overflow to infinity |
| 291 | + doWriteInt16(buf, offset, (sign << 15) | 0x7c00, littleEndian); |
| 292 | + return; |
| 293 | + } |
| 294 | + |
| 295 | + // Normalized mantissa processing |
| 296 | + // Note: exponent > 0 is guaranteed here because: |
| 297 | + // - We're in the normalized path (absVal >= FLOAT16_MIN_NORMAL = 2^-14) |
| 298 | + // - Therefore exp32 >= -14, so exponent = exp32 + 15 >= 1 |
| 299 | + int mant32 = Float.floatToIntBits(fval) & 0x7fffff; |
| 300 | + |
| 301 | + // Implement IEEE 754 round-to-nearest-even |
| 302 | + int bitsToShift = mant32 & 0x1fff; // Bits [12:0] that will be lost |
| 303 | + mantissa = mant32 >>> 13; // Bits [22:13] become the new mantissa |
| 304 | + |
| 305 | + if (bitsToShift > 0x1000) { |
| 306 | + // More than halfway: round up |
| 307 | + mantissa++; |
| 308 | + } else if (bitsToShift == 0x1000) { |
| 309 | + // Exactly halfway: round to even (check LSB) |
| 310 | + if ((mantissa & 1) != 0) { |
| 311 | + mantissa++; // Round up if odd |
| 312 | + } |
| 313 | + } |
| 314 | + // else: Less than halfway, round down (mantissa already truncated) |
| 315 | + |
| 316 | + // Handle rounding overflow |
| 317 | + if (mantissa >= 0x400) { |
| 318 | + exponent++; |
| 319 | + mantissa = 0; |
| 320 | + // DEFENSIVE CHECK (UNCOVERED): Mantissa rounding causes exponent overflow |
| 321 | + // This check is unreachable through normal API usage because: |
| 322 | + // - For this to execute: need exponent=30 initially, then mantissa rounds to 0x400 |
| 323 | + // - This requires Float32 values near 2^16 (approximately 65504-65536 range) |
| 324 | + // - Guard at line 243 catches all values > 65504 |
| 325 | + // - Guard at line 239 catches all values >= 65520 |
| 326 | + // - Complete coverage: All overflow cases caught by guards before reaching here |
| 327 | + // This defensive check is kept for defense-in-depth in case guards are modified. |
| 328 | + // See UNCOVERED_CODE_EXPLANATION.md for full mathematical proof. |
| 329 | + if (exponent >= 31) { |
| 330 | + // Overflow to infinity |
| 331 | + doWriteInt16(buf, offset, (sign << 15) | 0x7c00, littleEndian); |
| 332 | + return; |
| 333 | + } |
| 334 | + } |
| 335 | + } |
| 336 | + |
| 337 | + int bits = (sign << 15) | (exponent << 10) | mantissa; |
| 338 | + doWriteInt16(buf, offset, bits, littleEndian); |
| 339 | + } |
| 340 | + |
165 | 341 | public static Float readFloat32(byte[] buf, int offset, boolean littleEndian) { |
166 | 342 | long base = readUint32Primitive(buf, offset, littleEndian); |
167 | 343 | return Float.valueOf(Float.intBitsToFloat((int) base)); |
|
0 commit comments