Code change:
(Related to now-closed ) Should implementations be able to store parsed floats in IEEE754 binary64? And if so, should we expect that they round trip between parsing and serialization? It seems like the current limits were designed to support that notion, but there are cases where they do not. (Note, this isn't about being able to serialize arbitrary C++ doubles; the spec sensibly only supports a tiny subset of all possible 64-bit floats. This is about whether should always be true) IEEE 754 guarantees that if a string with no more than 15 digits is converted to a double and then back to a string, it should result in the original string, but that appears to require rounding in some cases; truncation, which the serialization algorithm calls for, can change the string representation. Parsing the string "12345678901234.1" as a float, for instance, results in a binary64 representation with value 0x42a674e79c5fe433, whose exact decimal expansion is 12345678901234.099609375. Following the text of the serialization algorithm, will be 1, and so the number will be serialized as "12345678901234.0" It appears, as an implementer, that the only way to ensure that a float parsed from a valid string can be serialized back to the original string is to store it as a fixed point decimal in two parts, or else use a float with >64 bits of precision for storage.
Would it be adequate to change truncation to rounding in step 9 of Serializing a Float?
That's a can of worms. Which rounding? (nearest-or-even, nearest-or-away from zero, to zero, up, down?)
I know nearest-or-even is the recommended rounding technique. Can we specify that?
Round, nearest-or-even, to fractionaldigitsavail digits? While we're at it, should the spec be stricter about trailing zeros? My implementation currently strips any trailing zeros after the first position past the decimal point (so 1.230000 is serialized as 1.23, but 1.000000 is serialized as 1.0, as is 1.0000001, after rounding/truncation), which passes the test cases in URL The spec just says "at most fractionaldigitsavail digits, but that allows multiple "correct" serializations of a given float.
It's also weird to only require roundtripping for floats that can be represented in binary64. The simplest might solution here might be to just accept that floats are lossy. If an implementer needs precision, use a string. This trade-off seems to work well enough for JSON parsers.
The spec already restricts floats to those which can theoretically be round-tripped into a binary64 and back; this is just about ensuring that that actually happens, if a binary64 is used as the backing store (which is natural for at least C++ and JS implementations)
nearest-or-even SGTM. Stripping trailing zeros as well as leading ones in the integer component sounds good as well. I'll do a PR...
PTAL at the commit. I've added a test for this ("tricky precision float") -- NAME are you using the tests?
/me reads above - yes you are, sorry for the noise.
I do not think we should make stripping trailing zeros decimal digits a strict requirement, the number of decimals presented can be out-of-band metadata about the actual precision involved. For instace a timeinterval serialized as 3.0 vs 3.000 vs. 3.000000 might be used to communicate the precision of the measurements as tenths of seconds / milliseconds / microseconds respectively. But we should probably recommend stripping trailing zero decimal digits for bandwidth reasons, if there is no communicative reason to retain them.
If someone wants to use the precision of measurement that way, it'd need to be available in all implementations to be interoperable -- i.e., we'd have to require implementations to preserve that precision. I'm pretty dubious on that being possible and correctly implemented across the board. E.g., requiring every implementation to parse "0.42000" and make the trailing zeros correctly available.
If we're going to round, we probably need to do it before serializing the integer part, since the rounded fractional part may round up to 1.0, as well.
For floats, I think there are two things that we want from this spec (and please let me know if that's not right): Any string which parses as an float can have that float's value stored in a binary64, and serializing that value should reproduce the original string. (modulo some number of 0's at either end) Any real value (regardless of storage implementation) in the range -(1e14-0.05) to (1e14-0.05), exclusive, should serialize to its closest valid sh-float representation. (rounding ties towards an even least-significant-digit) Is that reasonable?
Discussed in Singapore: will discuss on list, but sense in the room is to have a fixed point. Hum in the room prefers fixed point. (Need to still determine number of decimals; q-value uses 3).
Specifically pinging NAME NAME NAME for thoughts re: moving float to fixed-point (straw-man: three digits). This seemed to have strong support in the room, but wanted to make sure you're comfortable with it.
Would fixed point be materially different that what is currently specced? Currently, the spec implies a fixed-point with 6 digits of precision, and a maximum of 15 total digits, which (not at all coincidentally) is the same range that is guaranteed to round trip through an IEEE-754 binary64 representation, which is useful for actual implementations. Edit: I see the three-digit difference now; would the integer portion be restricted to 12 digits in that case?
Yes.
I don't have a strong opinion on switching to 12+3 fixed; I think it would cover all of the use cases that I can currently imagine (and I'm trying hard to discount any sunk costs from implementing the previous draft :stuckouttongue:) If implementation complexity (or perceived complexity) factors in to the decision at all, Chrome's current sh-float serializer, which passes the public test suite, is
If people think it's useful to have a fixed-point number, and can settle on the optimal number of digits for most of their use-cases, then
The (only!) two "real" reason we have float/decimal in the first place is values and subsecond resolution time-{intervals,stamps}, and the one "unreal" reason is that JSON offered that. I think has three decimals, either by norm or convention. Time-intervals such as "process-time for this request" are often sub-millisecond, and that has by and large been the argument for "at least six decimals". Time-intervals do not convincingly overcome Gettys 1st rule[1] or Phil Karltons rule[2]. so 12.3 would be fine with me. [1] Do not add new functionality unless an implementor cannot complete a real application without it. [2] The only thing worse than generalizing from one example is generalizing from no examples at all.
These days at least, it's by specification. It's a number in the range 0.000 to 1.000, and all algorithms I know of that use it would be just as well served by an integer from 0 to 1000. So the only reason to include the dot is if we want to backport existing headers as-is. Not backporting existing headers was a justification for some decisions earlier on, so if that's been reversed, should we revisit some of those other decisions?
Although thinking more about it, most of the earlier decisions are sort of OBE, so it's maybe not such a big deal.
PTAL

Related discussion:
text: 4.1.5. <del> Given a float as input_float, return an ASCII string suitable for use in a HTTP header value. </del> <ins> Given a decimal_number as input_decimal, return an ASCII string suitable for use in a HTTP header value. </ins> Let output be an empty string. <del> If input_float is less than (but not equal to) 0, append "-" to </del> <ins> If input_decimal is less than (but not equal to) 0, append "-" to </ins> output. <del> Append input_float's integer component represented in base 10 </del> <ins> Append input_decimal's integer component represented in base 10 </ins> (using only decimal digits) to output; if it is zero, append "0". <del> Let integer_digits be the number of characters appended in the previous step. If integer_digits is greater than 14, fail serialisation. Let digits_avail be 15 minus integer_digits. Let fractional_digits_avail be the minimum of digits_avail and 6. </del> <ins> If the number of characters appended in the previous step is greater than 12, fail serialisation. </ins> Append "." to output. <del> If input_float's fractional component is 0, append "0" to output. </del> <ins> If input_decimal's fractional component is zero, append "0" to output. </ins> <del> Else if input_float's fractional component has fractional_digits_avail or less digits, append input_float's fractional component represented in base 10 to output. </del> <ins> Else if input_decimal's fractional component has up to three digits, append them represented in base 10 (using only decimal digits) to output. </ins> <del> Else append fractional_digits_avail digits of input_float's fractional component represented in base 10 to output, rounding to the nearest value, or to the even value if it is equidistant. </del> <ins> Otherwise, append the first three digits of input_decimal's fractional component (represented in base 10, using only decimal digits) to output, rounding the final digit to the nearest value, or to the even value if it is equidistant. </ins> Return output.