lfpinit() signed or unsigned?
Eric S. Raymond
esr at thyrsus.com
Sat Mar 11 12:56:38 UTC 2017
Achim Gratz <Stromeko at nexgo.de>:
> Also, proper review might avoid "improvements"
> that really aren't, like this:
>
> --8<---------------cut here---------------start------------->8---
> @@ -13,11 +13,22 @@
> * NTP uses two fixed point formats. The first (l_fp) is the "long"
> * format and is 64 bits wide with the decimal between bits 31 and 32.
> * This is used for time stamps in the NTP packet header (in network
> - * byte order) and for internal computations of offsets (in local host
> - * byte order). We use the same structure for both signed and unsigned
> - * values, which is a big hack but saves rewriting all the operators
> - * twice. Just to confuse this, we also sometimes just carry the
> - * fractional part in calculations, in both signed and unsigned forms.
> + * byte order). It is defined in RFC 5905 in Section 6 (Data Types).
> + *
> + * The integral part is unsigned seconds since 0 h 1 January 1900 UTC.
> + * It will overflow in 2036.
> + *
> + * The fractional part is jusr float seconds divided by 32. Each LSB
> + * is 232 pico seconds.
> + *
> + * For internal computations of offsets (in local host byte order)
> + * the same structure is used, but signed, to allow for positive and
> + * negative offsets. We use the same structure for both signed and
> + * unsigned values, which is a big hack but saves rewriting all the
> + * operators twice. Just to confuse this, we also sometimes just carry
> + * the fractional part in calculations, in both signed and unsigned
> + * forms.
> + *
> * Anyway, an l_fp looks like:
> *
> * 0 1 2 3
> --8<---------------cut here---------------end--------------->8---
>
> Again, l_fp is no structure anymore. It doesn't really consist of an
> integral and fractional part either now that it is no longer a
> structure, it simply is 64bits in a unit of seconds scaled by 2e-32.
> That it was a structure consisting of two 32bit parts is an
> implementation detail of how the older code represented NTP time.
>
> There's no "float seconds" and especially none that are "divided by 32"
> (which would be a right shift by 5). The LSB unit would be 233ps if you
> round to picosecond precision.
>
> The offset computations don't really use the same structure or even the
> same type, but they use the same time unit and are indeed signed. The
> hack the old code used was to not define a separate structure for that
> and just stuff the offests and timestamps into the same structure with
> oblivion. There is absolutely no way to treat the lower 32bit
> (fractional seconds) directly as a signed type unless you first
> determine the sign or direction of the offset and branch based on that
> information.
This is all correct. I have revised the comment as follows:
* NTP uses two fixed point formats.
*
* The first (l_fp) is the "long" format and is 64 bits wide in units
* of 1/2e32 seconds (which is between 232 and 233 decimal
* picoseconds). The zero value signifies the zero date of the
* current NTP era; era zero began on the date 1900-00-00T00:00:00 in
* proleptic UTC (leap second correction was not introduced until
* 1972).
*
* The long format is used for timestamps in the NTP packet header (in
* network byte order). It is defined in RFC 5905 in Section 6 (Data
* Types). In the on-the-wire context, it is always unsigned.
*
* When it is convenient to compute in float seconds, this type can
* be interpreted as a fixed-point float with the radix point between
* bits 31 and 32. This is why there are macros to extract the low and
* high halves.
*
* Internally, this type is sometimes used for time offsets. In that
* context it is interpreted as signed and can only express offsets
* up to a half cycle. Offsets are normally much, much smaller than that;
* for an offset to have a value even as large as 1 second would be
* highly unusual.
> Here's another one:
> --8<---------------cut here---------------start------------->8---
> @@ -114,6 +114,22 @@ normalize_tspec(
> return x;
> }
>
> +/* convert a double to a rounded and normalized timespec */
> +static inline struct timespec
> +d_to_tspec(
> + double d
> + )
> +{
> + struct timespec x;
> +
> + d += 0.5e-9; /* round on LSB */
> + x.tv_sec = (time_t) d;
> + x.tv_nsec = (long)((d - (double)x.tv_sec) * 1e9);
> +
> + /* unlikely, but ensure it is normalized */
> + return normalize_tspec(x);
> +}
> +
> /* x = a + b */
> static inline struct timespec
> add_tspec(
> --8<---------------cut here---------------end--------------->8---
>
> Sprinkling magical constants throughout the code like that isn't going
> to make it easier to maintain (especially when you consider that there's
> a myriad ways of writing the same number). There's a reason NANOSECONDS
> gets defined at the top of that file, even though it's unlikely that the
> unit of time will change in timespec. So (0.5/NANOSECONDS) and (… *
> NANOSECONDS) would be much easier to find in the event that later on
> someone wanted to bump the filed sizes up and scale to attoseconds.
This is also correct. And everyone here should know better than to introduce
magic constants!
--
<a href="http://www.catb.org/~esr/">Eric S. Raymond</a>
Please consider contributing to my Patreon page at https://www.patreon.com/esr
so I can keep the invisible wheels of the Internet turning. Give generously -
the civilization you save might be your own.
More information about the devel
mailing list