Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

half-precision float NaN loss of information (nitpicky) #203

Open
ranvis opened this issue Mar 10, 2022 · 1 comment
Open

half-precision float NaN loss of information (nitpicky) #203

ranvis opened this issue Mar 10, 2022 · 1 comment
Assignees
Labels

Comments

@ranvis
Copy link
Contributor

ranvis commented Mar 10, 2022

The behavior of NaN for half-precision float is not consistent with single/double-float.

  • Decoding: if compiled on Visual Studio (confirmed on v14,15,17), a sign bit (signaling) of half-float NaN returned by _cbor_decode_half() is flipped because constant NAN has sign bit set:

val = mant == 0 ? INFINITY : NAN;
return (float)(half & 0x8000 ? -val : val);

// nan.c
#include <stdio.h>
#include <math.h>
#include <inttypes.h>
void main() {
  float fco = NAN;
  float ffn = nanf("");
  printf("%" PRIx32 "  %" PRIx32 "\n", *(uint32_t *)&fco, *(uint32_t *)&ffn);
  printf("%f, %f\n", fco, ffn);
}
gcc.exe nan.c && a.exe  # MinGW'ish
7fc00000  7fc00000
1.#QNAN0, 1.#QNAN0

cl.exe nan.c && nan.exe  # Visual Studio
ffc00000  7fc00000       # NAN constant has sign bit on
-nan(ind), nan

Also _cbor_decode_half() ignores fraction bits (payloads.)
In contrast, single-precision float CBOR is loaded as is, and those bits are kept when you cast it to double.

void main()
{
  float f32;
  double f64 = NAN;
  printf("%" PRIx64 "\n", *(uint64_t *)&f64);
  *(uint32_t *)&f32 = 0x7fc110ff;
  f64 = f32;
  printf("%" PRIx64 "\n", *(uint64_t *)&f64);
}
7ff8000000000000
7ff8221fe0000000
  • Encoding: sign/fraction bits are cleared if half-float is encoded:

Half-float NaN is always encoded into 7e00.

libcbor/src/cbor/encoding.c

Lines 136 to 139 in 7b806bf

if (exp == 0xFF) { /* Infinity or NaNs */
if (value != value) {
res = (uint16_t)0x007e00; /* Not IEEE semantics - required by CBOR
[s. 3.9] */

The code in cbor_encode_half() refers to RFC 7049 section 3.9 "Canonical CBOR".

If NaN is an allowed value, it must always be represented as 0xf97e00.

But that section looks like mentioning a virtual protocol using CBOR as a data format. It would not directly apply to a generic encoder.
This section corresponds to RFC 8949 section 4.2.2 "Additional Deterministic Encoding Considerations".

If NaN is an allowed value, and there is no intent to support NaN payloads or signaling NaNs, the protocol needs to pick a single representation, typically 0xf97e00.

Don't come up with a real problem it can make, but if some more bits are retained for 2-byte NaN, it sounds neat.

@ranvis ranvis added the bug label Mar 10, 2022
@ranvis
Copy link
Contributor Author

ranvis commented Mar 17, 2022

a sign bit (signaling) of half-float NaN

This was wrong. The bit is not for signaling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants