Subject: [Cbor] Preferred encoding of floats

From: Laurence Lundblade <[email-masked]>
Date: Wed, 1 Aug 2018 16:02:32 -0700

Thinking through the preferred encoding for floats…

For 0, NaN and +/- Infinity, converting down to half-precision to save space is seems OK. Conversion between half, single and double-precision for these is fully deterministic. If these values occur frequently in an a protocol there will be some space saving.

For other than these values, you could have an algorithm for converting double and single to half where you only do it when the significand bits that are to be truncated are 0. This would preserve the precision and save space.  Assuming even distribution of values, the odds of this happening are very low (1 in 8192 when converting single, 23 bits, to half, 10 bits) and thus the space saving is negligible (0.01%).  I think this is what is meant by the text about floating point in section 3.9 on canonicalization. Am I right about that? 

This seems weird, complicating and confusing for very little benefit, especially for those unfamiliar with float point. The size reduction is the only benefit and it is negligible for many and probably most use cases. 

I thus do not think there should be any conversion of floating point types for preferred encoding / decoding.  That’s why I didn’t put any in the PR <https://github.com/cbor-wg/CBORbis/pull/28>.

I’m still thinking about it, but it does seem that we should require NaN to be always in one particular form and to disallow denormal numbers o the wire. That will require some active code on the encoding side. You can’t just take any IEEE float (e.g. the C language float or double), convert it to network byte order and send it.  I didn’t put any text in the PR for this yet.

Last, my view on half-precision is that preferred encoders should not send it, but preferred decoders should accept it. This just based on Postel and the fact that half-precision is not widely supported. 

LL

---

From: Sean Solberg <[email-masked]>
Date: Wed, 1 Aug 2018 18:23:12 -0500

In my opinion, the CBOR protocol level should never be converting data from
one type to another for the sake of being able to represent the same value
on the wire more compactly.  The receiver may want to distinguish between a
particular value that can be represented as either a double, single or half
float.  A double "0" is not the same "value" as a single "0" because they
in fact do have different precision.
One of the big benefits (imho) that CBOR has over other mechanisms such as
JSON, etc. is that it does transmit explicitly defined data types and the
choice for those data types are at the application level, not the
protocol/serialization level.  Please do not propose any CBOR protocol
level conversion of data types for any reason whatsoever, including "more
compact on the wire".

..Sean Solberg

---

From: Thiago Macieira <[email-masked]>
Date: Wed, 1 Aug 2018 20:56:20 -0700

On Wednesday, 1 August 2018 16:23:12 PDT Sean Solberg wrote:
> in fact do have different precision. One of the big benefits (imho) that
> CBOR has over other mechanisms such as JSON, etc. is that it does transmit
> explicitly defined data types and the choice for those data types are at
> the application level, not the protocol/serialization level.  Please do not
> propose any CBOR protocol level conversion of data types for any reason
> whatsoever, including "more compact on the wire".

I think that boat has sailed. A lot of higher-level API dealing with CBOR does 
type-erasure on floating points, if not all numbers. Requiring a specific FP 
type to remain after a round of decoding and re-encoding is not feasible.

I would also advise against protocols requiring a specific one of the FP 
types.

-- 
Thiago Macieira - thiago.macieira (AT) intel.com
  Software Architect - Intel Open Source Technology Center

---

From: Laurence Lundblade <[email-masked]>
Date: Thu, 2 Aug 2018 14:14:10 -0700

I think the job of a CBOR library is to convert to/from data in a particular programing environment from/to the CBOR on-the-wire format. Different environments will do this differently because they support different types. 

I don’t think we want to reduce all CBOR use to the least common denominator of all programming environments. While some protocols will prefer this reduction to maximize interop and ease implementation, I don’t think that is a given or a requirement. If we were to make it a given or requirement, it would be a sad waste of something already built into CBOR and that it does well. 

For example: JSON does a poor job with floating point and CBOR is commonly converted to it. A custom tweaked compiler and a recent model CPU (or GPU) can handle half-precision very efficiently in HW. Someone should be able to built a super efficient protocol with CBOR for the half-precision environment if they want. They probably can’t expect it to work that with well JSON, but that is their choice and that is OK.

Sean, I agree that nothing in the CBOR standard should preclude a protocol design that does no conversion. A comprehensive CBOR library for the C / C++ / assembly programing environment would allow precise control of the floating point types encoded and decoded, including half-precision. In other environments libraries will necessarily be more crude about this and that is OK too. 

I’ve seen some use of CBOR for representing data in marshalled IPC/RPC mechanisms. In these use cases the data never goes on a wire and never gets close to JSON, Java, Python and such. It just goes between processes and maybe between locally connected CPUs (a typical SoC in a smart phone has lots and lots of CPUs of different sizes and types).

LL

> On Aug 1, 2018, at 8:56 PM, Thiago Macieira <[email-masked]> wrote:
> 
> _______________________________________________
> CBOR mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/cbor
>

---

From: Carsten Bormann <[email-masked]>
Date: Fri, 3 Aug 2018 00:22:03 +0200

Interesting discussion.

Hardware support for binary16 (half-precision floating point) would add very little to the speed of a CBOR encoder/decoder.  The argument “no support for half-precision” is a red herring.  Because we expected less people to be familiar with it in 2013 than with single and double precision, we added code examples in Appendix D of RFC 7049.  (And, BTW, half-precision converts great to JSON decimal, at least if you think decimal is great in the first place.)  Half-precision is definitely the preferred encoding for numbers that can use it (as is often the case with sensor readings).  On an ARM that does floating point at all, you have hardware support for half-precision (and C compiler support in the form of __fp16/_Float16), but none of that is really needed if you just want to encode/decode CBOR.

JSON uses decimal floating point, which requires a relatively expensive conversion from and to the more common binary floating point numbers (which also is often done wrong, even to the point that there are several CVEs describing security vulnerabilities caused by bugs in that conversion).

CBOR offers decimal, too (Tag 4), but its base support is for binary floating point, in the three most common forms defined in IEEE 754 (binary16, binary32, binary64).  (The larger IEEE 754 types can be re-arranged into Tag 5 values, if needed, or we could add special tags that don’t need any rearranging once that becomes a serious burden for an application.)

CBOR’s preference for binary floating point also means that converting the more common CBOR floating point numbers to JSON is relatively expensive.  This is very different from simply rearranging the bits as required for converting between the binary64/binary32/binary16 numbers.  My theory is that many people that use JSON really don’t care about floating point at all, and those who do, don’t care about that level of efficiency (they would be using HDF5 otherwise…).

The advantage of an encoder that always uses the shortest form of floating point is not that a very low fraction of numbers would get shortened with a truly random distribution.  The advantage is that an application that operates on binary32 or binary16 floating point can hand these to the encoder without having to make sure they don’t accidentally are handed over as binary64 somewhere on the way.  If your environment never does that, there is probably little point in checking whether a shorter form is possible.

BTW, there is nothing wrong with denormalized numbers in IEEE 754 floating point; there is no reason not to send or process them.

Grüße, Carsten

---

From: Sean Solberg <[email-masked]>
Date: Fri, 3 Aug 2018 07:41:00 -0500

I just hope there aren't any words in the spec that says it "must" or
"should" reduce to the smallest data type that can represent that value.
If the spec says that a cbor implementation "may" do that, then great.

..sean

---------- Forwarded message ---------
From: Laurence Lundblade <[email-masked]>
Date: Thu, Aug 2, 2018, 4:14 PM
Subject: Re: [Cbor] Fwd: Preferred encoding of floats
To: Thiago Macieira <[email-masked]>
Cc: <[email-masked]>, Sean Solberg <[email-masked]>

I think the job of a CBOR library is to convert to/from data in a
particular programing environment from/to the CBOR on-the-wire format.
Different environments will do this differently because they support
different types.

I don’t think we want to reduce all CBOR use to the least common
denominator of all programming environments. While some protocols will
prefer this reduction to maximize interop and ease implementation, I don’t
think that is a given or a requirement. If we were to make it a given or
requirement, it would be a sad waste of something already built into CBOR
and that it does well.

For example: JSON does a poor job with floating point and CBOR is commonly
converted to it. A custom tweaked compiler and a recent model CPU (or GPU)
can handle half-precision very efficiently in HW. Someone should be able to
built a super efficient protocol with CBOR for the half-precision
environment if they want. They probably can’t expect it to work that with
well JSON, but that is their choice and that is OK.

Sean, I agree that nothing in the CBOR standard should preclude a protocol
design that does no conversion. A comprehensive CBOR library for the C /
C++ / assembly programing environment would allow precise control of the
floating point types encoded and decoded, including half-precision. In
other environments libraries will necessarily be more crude about this and
that is OK too.

I’ve seen some use of CBOR for representing data in marshalled IPC/RPC
mechanisms. In these use cases the data never goes on a wire and never gets
close to JSON, Java, Python and such. It just goes between processes and
maybe between locally connected CPUs (a typical SoC in a smart phone has
lots and lots of CPUs of different sizes and types).

LL

---

From: Carsten Bormann <[email-masked]>
Date: Fri, 3 Aug 2018 15:01:08 +0200

On Aug 3, 2018, at 14:41, Sean Solberg <[email-masked]> wrote:
> 
> I just hope there aren't any words in the spec that says it "must" or "should" reduce to the smallest data type that can represent that value.  If the spec says that a cbor implementation "may" do that, then great.

It’s certainly a MAY (RFC 2119 sense of MAY: the peer has to be prepared to encounter it).

It is definitely not a SHOULD or a MUST.

The term “preferred encoding” in any case is more of a “quality of implementation expectation”, which is somewhat orthogonal to the “interoperability considerations” that require RFC 2119 wording.

Grüße, Carsten

---

From: Laurence Lundblade <[email-masked]>
Date: Sun, 5 Aug 2018 13:21:40 -0700

My comments below

> On Aug 3, 2018, at 6:01 AM, Carsten Bormann <[email-masked]> wrote:
> 

I don’t think IETF does “quality of implementation”. I don’t think it should. Formally, that is the realm of certifications like Common Criteria and FIPS. Informally, we all want to do a good job of our code, have code reviews, fuzz test and such, but none of that goes in an RFC.

I also don’t think any particular feature set or mode of CBOR should be considered necessary for higher quality implementation, to defend against buffer overruns or general SW security. In this day and age all features and options in CBOR should be implementable in equally and very secure ways.

So I think preferred encoding / decoding is only about interoperability. Interoperability is the focus of this pull request <https://github.com/cbor-wg/CBORbis/pull/28> on preferred encoding / decoding.  (I think we’ve determined canonicalization for the sake or repeatable deterministic encoding is a lesser focus). 

For interop for half, float and double, the main thing I think is important is that preferred decoders support all three. They are liberal in what they accept. Some implementations, those on small CPUs without any floating point support, will not be able to implement this and thus can’t be preferred decoders. You have to support float to be a preferred decoder. 

I’m still trying to get my head fully around what to do with denormal and varying NaNs for interop. 

It seems like preferred encoding should require the canonical form for NaN, but I’m not sure if it should support both sNaN and qNaN, or just one NaN.

Denormal seems to have historical controversy. HW / compilers have options to turn it on and off. Because of this, I’d say suggest encoding should require denormal-as-zero.  It’s tempting to say that it’s not a big deal if a decoder folds denormal it receives to zero, but maybe the sender didn’t want that. It would certainly not be OK to fold small integer values 0x01, 0x02, 0x03 to 0x00.

LL

---

From: Carsten Bormann <[email-masked]>
Date: Mon, 6 Aug 2018 10:21:14 +0200

> I don’t think IETF does “quality of implementation”.

You may be surprised how often it does:

https://www.google.de/search?q=%22quality+of+implementation%22+site%3Arfc-editor.org

(These are just the RFCs that use that exact phrase, but there are tons other that provide guidance of a similar kind using different words.)

> I don’t think it should.

I’d say, we are doing it much less than we actually should.  

We are defining protocols to create benefit.  This includes giving implementers guidance what to implement.  Beyond what is absolutely needed for interoperability per se, there may also guidance on what implementation decisions may further increase benefit.

> Formally, that is the realm of certifications like Common Criteria and FIPS. Informally, we all want to do a good job of our code, have code reviews, fuzz test and such, but none of that goes in an RFC.

Neither certification nor code review do.  But the above mentioned “quality of implementation” guidance does.
Implementers are often looking for a cop-out to do something the easy way (and, I’d say, legitimately so!).
Allowing some undesirable behavior in an RFC (just because it is not fatal for interoperability) may serve as such a cop-out.  That’s why additional “quality of implementation” guidance is so useful.  (Or implementers may simply not be aware of the benefits of achieving some quality of implementation objective.)

> I also don’t think any particular feature set or mode of CBOR should be considered necessary for higher quality implementation, to defend against buffer overruns or general SW security. In this day…

I’m not sure where security comes in here, but I agree CBOR is implementable.  
But it sure helps new implementers to know what is a good way to do things.

> So I think preferred encoding / decoding is only about interoperability.

As I said above, interoperability is an important benefit, but this is about overall benefit, not just interoperability.

> Interoperability is the focus of this pull request on preferred encoding / decoding.  (I think we’ve determined canonicalization for the sake or repeatable deterministic encoding is a lesser focus). 

Different focus.  (Whether it’s lesser is dependent on what the application needs.)

> For interop for half, float and double, the main thing I think is important is that preferred decoders support all three. They are liberal in what they accept. Some implementations, those on small…

I don’t think we want to have “preferred decoders” and “non-preferred decoders”.  A decoder implements all of CBOR or an application-specific subset.  “All of CBOR” is certainly preferred for what tries to be a generic decoder, but that is so obvious that it probably does not need to be stated.

“Preferred” mostly comes in where there are *encoding* choices that are all “valid”, but some of which are better than others.  Talking about quality of implementation in a specification may help an implementer to make the right decisions here.  Generally, it is good for an ecosystem if the non-essential decisions mostly go into the same direction, e.g., because decoder implementers can then optimize for that.

> I’m still trying to get my head fully around what to do with denormal and varying NaNs for interop. 

As I said, denormal is a normal thing in IEEE 754 (called subnormal there).
Varying NaNs are not disallowed by CBOR; I don’t think there is a good expectation for interoperability with “NaN boxing” or similar approaches, though.

> It seems like preferred encoding should require the canonical form for NaN, but I’m not sure if it should support both sNaN and qNaN, or just one NaN.

The current text for recommended canonical, maps all NaNs to a single (quiet) NaN.
That may indeed not be the best way for all applications.

> Denormal seems to have historical controversy.

Yes, at the time probably the biggest controversy around IEEE 754.  Settled some 30 years ago…

> HW / compilers have options to turn it on and off.

And that is fine.  But now you are into application processing of data *before* they go into interchange.

(BTW, CDDL does not have a way to say that a number may not be subnormal.  
Could be added easily as a control.)

> Because of this, I’d say suggest encoding should require denormal-as-zero.  

Absolutely not.  Subnormal is needed for what is called “gradual underflow”, so normally you *want* to transfer them unharmed.  There also is no point in loading every single encoder with considerations about what limitations the floating point environment of the decoder might have.  (Just because a receiver *might* have some undesirable handling of certain numbers should not mean you should *force* that undesirable behavior on every sender.  Unless, of course, the specific application wants it, but then it’s an application thing.)

> It’s tempting to say that it’s not a big deal if a decoder folds denormal it receives to zero, but maybe the sender didn’t want that.

A receiving software environment may have all kinds of weird properties the sender did not want.

If a sender wants receivers with both subnormal-challenged and full IEEE 754 implementations to operate on the data in the same way [we are in a bit of canonical land here, again], it will need to do flush-to-zero in the application.  But there is no need to burden the CBOR environment with this.  (There is nothing to stop a CBOR library implementer from providing flush-to-zero as an encoder option for the convenience of the application.  But I wouldn’t expect an encoder to provide that — it’s a nice feature for some applications, but it is not “preferred”.)

> It would certainly not be OK to fold small integer values 0x01, 0x02, 0x03 to 0x00.

Sure, but integers are exact numbers, so the comparison is not very useful for inexact numbers.

Grüße, Carsten

---

From: Laurence Lundblade <[email-masked]>
Date: Tue, 7 Aug 2018 12:37:37 -0700

Comments below.

> On Aug 6, 2018, at 1:21 AM, Carsten Bormann <[email-masked]> wrote:
> 
> 
> 

So I had/have some idea that:
A) if some text affects how bits on the wire are sent or received, it should be mainline, complete, non-subjective, non-qualified, normative text
B) quality of implementation text is not about bits on the wire, but rather about UX, SW security, certification, APIs and such

From looking at RFC in your google search, quality of implementation sections sometimes address A) and sometimes address B). I still prefer the A), B) separation as helpful towards sharp clear text specifying interop and bits on the wire, but now understand that is not always the way it is done.  Thanks for the Google search and other info.

> I don’t think we want to have “preferred decoders” and “non-preferred decoders”.  A decoder implements all of CBOR or an application-specific subset.  “All of CBOR” is certainly preferred for what…

All of CBOR is too much to expect any majority of decoders and encoders to implement. Somethings like float, double and half are a little hard to figure out (as I am discovering). Other things like bignums seem useful for a very very narrow set of use cases (they aren’t even used for COSE keys).

Since we can’t expect “all” from the majority of decoders, all we have is application-specific subsets. That’s a little hard on those of us working on generic libraries. 

> “Preferred” mostly comes in where there are *encoding* choices that are all “valid”, but some of which are better than others.  Talking about quality of implementation in a specification may help a…

My PR is basically proposing that there be a “preferred” CBOR subset that will cover the vast majority of use cases and that all uses of this preferred subset are guaranteed to interoperate. To do this the preferred subset must apply to decoding as well as encoding. 

This notion of “preferred" makes it easy for protocol designers. They just stick to the preferred set which is should be enough for most use cases. They don’t have to think too hard about interop. It makes it easy for generic library implementors and it make it easy for an application to select a library. Protocol implementors just design for preferred and select a library that is preferred. It should also make translation to JSON more uniform. 

I think we also have to thoroughly acknowledge that not all implementations will be able to implement all of preferred for very good reason that they are on a constrained device or have a special use case. There is / was sentiment here that such uses of CBOR are bad and non-interoperable. That is not the case. 

> As I said, denormal is a normal thing in IEEE 754 (called subnormal there).

OK. Understood about denormal. Thanks for taking the time to explain.

> That may indeed not be the best way for all applications.

I assume NaN boxing and NaN payloads are the same thing. 

NaN boxing/payloads is not compatible with a canonical / preferred representation NaN. You have to pick one or the other. 

So in CBOR we can choose one of these paths:
A) Always use the one single representation of NaN and entirely disallow boxing/payloads
B) In preferred/canonical CBOR use the one single representation, which makes boxing/payloads work for regular, but not canonical/preferred CBOR
C) Have no recommendation at all. NaNs are just carried as they come (like denormals)

My preference is for A), but could go with C)
- I like sparing users of CBOR the work of having to figure out more weird stuff like NaN boxing. NaN boxing/payloads seems like a very marginal feature. 
- I think B) is weird and less interoperable because sometimes NaN boxing/payloads work in CBR and sometimes they do not.
- I think C) is not as crisp as A)

LL

---

From: Carsten Bormann <[email-masked]>
Date: Wed, 8 Aug 2018 00:37:35 +0200

Hi Laurence,

thanks for keeping up the discussion of floats; they are often neglected in circles like the IETF (say, YANG doesn’t not even have them!) and we have to get this right.

> All of CBOR is too much to expect any majority of decoders and encoders to implement.

Actually, since anyone can *extend* CBOR at any time by defining another Tag, that is an impossible expectation.  But we can describe what a full generic CBOR decoder would do:

1 — decode any valid CBOR,
2 — present all information in there that goes beyond representation variants to the application.

Now, number 2 is the fun part.  Is the difference between 1 and 2(h’01’) just a representation variant?
For many implementations, it will be.  However, implementations that are stuck with a BigInt that isn’t even numeric may think this is a valid distinction (e.g., a JavaScript implementation might want to encode 1n as 2(h’01’) to distinguish it from 1).  We’ll need to make some taste decisions there.

> Somethings like float, double and half are a little hard to figure out (as I am discovering).

I just extracted the float decoder implementation from my Elixir code.  (This code is a bit more complicated than needed because Erlang does not handle nonfinites, so there is about twice as much code as needed.)

  …
          7 -> case bin do
                 << 0xf9, sign::size(1), exp::size(5), mant::size(10),
                    _::binary >> -> {decode_half(sign, exp, mant, treatment), rest}
                 << 0xfa, value::[float, size(32)], _::binary >> -> {value, rest}
                 << 0xfa, sign::size(1), 255::size(8), mant::size(23),
                    _::binary >> -> {treatment.nonfinites.(sign, mant), rest}
                 << 0xfb, value::[float, size(64)], _::binary >> -> {value, rest}
                 << 0xfb, sign::size(1), 2047::size(11), mant::size(52),
                    _::binary >> -> {treatment.nonfinites.(sign, mant), rest}
  …
  defp decode_half(sign, 31, mant, treatment), do: treatment.nonfinites.(sign, mant)
  defp decode_half(sign, exp, mant, _) do
    << value::[float, size(32)] >> =
      << sign::size(1), exp::size(8), mant::size(10), 0::size(13) >>
    value * 5192296858534827628530496329220096.0 # 2**112 — difference in bias

Just showing that this is not particularly hard even in a language that doesn’t give you a lot of tools to handle floating point.

> Other things like bignums seem useful for a very very narrow set of use cases (they aren’t even used for COSE keys).

The JavaScript people are just now finding out that they were lacking them all the time…
It is just needed for many applications that can’t survive artificial limitations.

(Each of us has a certain set of experience behind their tastes, and it is too easy to fall into the trap that just because something does not occur in that experience that it is unimportant…)

> Since we can’t expect “all” from the majority of decoders, all we have is application-specific subsets. That’s a little hard on those of us working on generic libraries. 

I would expect generic libraries to differ a lot between differently capable programming environments.
A Lua implementation will always be more limited than a Haskell one.
I don’t think we can normalize that.  But we can make sure that all full generic implementations at least enable applications to handle some types (awkwardly) that the programing environment doesn’t.  Like the ugly nonfinites above…

> My PR is basically proposing that there be a “preferred” CBOR subset that will cover the vast majority of use cases and that all uses of this preferred subset are guaranteed to interoperate. To do…

That would be the I-JSON of CBOR.  Meaning the other half of CBOR can no longer be used.  We don’t have to make that mistake, I think.

> This notion of “preferred" makes it easy for protocol designers. They just stick to the preferred set which is should be enough for most use cases. They don’t have to think too hard about interop.…

More shades of I-JSON…

> I assume NaN boxing and NaN payloads are the same thing. 

Yes.

> NaN boxing/payloads is not compatible with a canonical / preferred representation NaN. You have to pick one or the other. 

An application could define their “canonical” with the right way of NaN handling.  CBOR definitely can transport NaN payloads.

> - I think C) is not as crisp as A)

I’m leaning the same way, but I still think it is valid for an application to go ahead and use the full capabilities of the CBOR serialization (e.g., to carry around NaN payloads).  It is probably a mistake in my implementation that I always canonicalize NaNs in the encoder — I should only do this when canonical encoding was requested.  I still think that it is good to turn both 0xFB7FF8000000000000 and 0xFA7FC00000 into 0xF97E00 in the generic encoder.

Grüße, Carsten

---

From: Laurence Lundblade <[email-masked]>
Date: Thu, 9 Aug 2018 19:45:56 -0700

This thread is getting a bit frayed, but will try to go a bit further with it.

> On Aug 7, 2018, at 3:37 PM, Carsten Bormann <[email-masked]> wrote:
> 
> Hi Laurence,
> 

I kind of took this the wrong direction by discussing types. I should have stuck to serialization since that’s the focus of the PR. 

The PR <https://github.com/cbor-wg/CBORbis/pull/28> does say say that a preferred decoder should implement all serializations defined by CBOR. It lists them out one by one and says a preferred decoder must implement them. 

So we agree on what a preferred decoder should do if we are to have one. 

I am in favor of describing the preferred decoder in the RFC. I think some decoders don’t implement all serializations. We want to encourage them to for much better interop. CBOR libraries can also say the support preferred and that helps completeness and interop.

> Just showing that this is not particularly hard even in a language that doesn’t give you a lot of tools to handle floating point.

Well it was hard for me to do the thorough job I wanted to do. :-).  I agree the code isn’t very long.  I had to learn a lot to support half-precision. Then I had to learn about denormal and NaN payloads to know if the job was really done.

>> Since we can’t expect “all” from the majority of decoders, all we have is application-specific subsets. That’s a little hard on those of us working on generic libraries. 

Like I said above, I kind of take this back. We should expect decoders to implement all serializations and might want to call those preferred decoders.

> I’m leaning the same way, but I still think it is valid for an application to go ahead and use the full capabilities of the CBOR serialization (e.g., to carry around NaN payloads).  It is probably…

Supporting NaN payloads in C/native on a CPU that supports IEEE 754 is seems pretty easy. It is actually less code than A) too.  But what about other languages and environments? That would push me towards A) instead of C), though I suspect in the end use of NaN payloads is extremely rare and this ends up not mattering very much. 

LL