gpsd-numbers-matter.adoc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. = GPSD Numbers Matter
  2. Gary E. Miller <gem@rellim.com>
  3. 6 December 2021
  4. :author: Gary E. Miller
  5. :description: How and why GPSD tortures numbers.
  6. :email: <gem@rellim.com>
  7. :keywords: gpsd, NaN, precision
  8. :robots: index,follow
  9. :sectlinks:
  10. :source-highlighter: rouge
  11. :toc: macro
  12. include::inc-menu.adoc[]
  13. == ABSTRACT
  14. Geodesy tortures numbers harder than almost any other discipline. It
  15. requires that very large numbers be known to very small precision. This
  16. leads to some unexpected, sometimes perplexing, choices in how *gpsd*
  17. handles numbers. This white paper will explore many of those choices.
  18. == Latitude and Longitude
  19. Your GNSS receiver starts with really big, numbers. Like the Earth's
  20. polar radius: 6356752.314245. Then with the help of a lot of math,
  21. computes your position to a high precision. The u-blox ZED-F9P reports
  22. 0.1 mm (1e-9 or 0.000000001 degree) precision. That is about 12
  23. decimal digits of precision. It is certainly not that accurate, maybe
  24. soon. No matter, *gpsd* wants to not lose the precision of the data it
  25. is given.
  26. 12 digits of precision fits in a C double which has 15.95 decimal
  27. digits of precision (53 binary bits of precision). printf() format %f
  28. defaults to %.6f, which will truncate the result. so print with %.7f, or
  29. even %9f, if you have a survey grade GPS. Here is a rough idea of how
  30. degrees relate to distance, at the equator:
  31. |====
  32. |Degrees|Resolution|DMS|Distance at equator
  33. |0.0001|1e-4|0° 00′ 0.36″|11.132 m
  34. |0.000001|1e-6|0° 00′ 0.0036″|11.132 cm
  35. |0.0000001|1e-7|0° 00′ 0.00036″|11.132 mm
  36. |0.00000001|1e-8|0° 00′ 0.000036″|1.1132 mm
  37. |0.000000001|1e-9|0° 00′ 0.0000036″|0.1113 mm
  38. |====
  39. Source: <<DD>>
  40. u-blox firmware since at least protocol version 4 (Antaris 4)
  41. has reported latitude and longitude to 0.0000001 (1e-7) with the
  42. UBX-NAV-POSLLH message. At that time, 1e-7 was wildly optimistic.
  43. Starting with protocol version 20, those u-blox with High Precision
  44. firmware supports the UBX-NAV-HPPOSLLH message. That message reports to
  45. 0.00000000001 (1e-9) precision, about 0.1 mm.
  46. Python floats are very similar to C doubles, plus some annoying bugs
  47. related to <<NaN>>.
  48. See <<DD>> for more information on Decimal Degrees and precision.
  49. == Time
  50. In the "Latitude and Longitude" section above we learned that C doubles
  51. are just fine for holding position information. The same can not be said
  52. for "Time". There is loss of precision when storing time as a double!
  53. * A double is 53 significant bits.
  54. * POSIX time to nanoSec precision is 62 significant bits
  55. * POSIX time to nanoSec precision after 2038 is 63 bits
  56. * POSIX time as a double is only microSec precision
  57. That is why POSIX time as a double and PPS do not play well together.
  58. WARNING:: Loss of precision telling time as a double!
  59. That is why *gpsd* tells time using *struct timespec*. That look like this:
  60. [source,c]
  61. ----
  62. struct timespec {
  63. time_t tv_sec; /* Seconds */
  64. long tv_nsec; /* Nanoseconds */
  65. };
  66. ----
  67. *time_t* is usually a 64-bit integer. Older systems, and some 32-bit
  68. systems, define *time_t* as a 32-bit integer, which is deprecated. A
  69. 32-bit integer will overflow at: 03:14:07 UTC on 19 January 2038. Plan
  70. for that apocalypse now. Source: <<Y2038>>
  71. In 2021 cosmologists believe the age of the universe is just under
  72. 14 billion years. That is 4.4 e17 seconds, which fits comfortably
  73. into a 59 bit unsigned integer. A 64-bit *time_t* will be good enough
  74. for a while longer.
  75. The other part of *timespec_t* is a long, carrying the nanosecond part
  76. of the time stamp. In 2021, a GNSS receiver can tell you the start of
  77. every second to about 1 nano second (1 e-9 seconds) accuracy. That fits
  78. comfortably into a 30 bit unsigned integer. As long integer in C is
  79. always at least 32 bits.
  80. A *timespec_t* fails when you need to measure time to better than 1 nano
  81. second. The SI second is defined as 9,192,631,770 cycles of radiation
  82. from a caesium-133 (Cs) atom. Close to 0.1 nano seconds. That requires
  83. a 34 bit unsigned integer.
  84. In 2021 the smallest frequency difference that can be measured is about
  85. 1 second in 400 million years, one part in about 1.23 e16. That involves
  86. clocks composed of strontium atoms, and measuring time differences with
  87. optical combs. The time difference between those two is thus 7.9 e-17
  88. seconds per second. Needing a 54 bit unsigned integer fraction of a
  89. second to hold it.
  90. === Time Accuracy
  91. Just because gpsd can represent a number does not mean the number means
  92. what you think it does. The u-blox ZED-F9T data sheet says it can
  93. output absolute PPS to 5ns. But the fine print says: "1-sigma, fixed
  94. position mode, depends on temperature, atmospheric conditions, baseline
  95. length, GNSS antenna, multipath conditions, satellite visibility and
  96. geometry".
  97. There are many distinct embodiments of Universal Coordinated Time
  98. (UTC). In the USA there are two, one kept by the National Institute
  99. of Standards and Technology (NIST] and one by the US Naval Observatory
  100. (USNO). These are referred to as UTC(NIST) and UTC(USNO). The primary
  101. source for UTC(NIST) is in Fort Collins Colorado. Their secondary
  102. (backup) source is in Gaithersburg Maryland. According to <<NIST2187>>,
  103. in 2021, the secondary UTC(NIST) site is only plus/minus 25 nano seconds
  104. aligned with the primary source. Don't expect to tell time better than
  105. the NIST.
  106. UTC(USNO) supplies the master clock for the GPS system. In 2020, NIST
  107. said that UTC(USNO) differed from UTC(NIST) by plus/minus 20 nano
  108. seconds. See <<NIST USNO>>. Even if you could track GPS time perfectly,
  109. and it tracked UTC(USNO) perfectly, you are still off by plus/minus 20
  110. nano seconds.
  111. The biggest obstacle to *gpsd* and *ntpd* keeping accurate time is the
  112. granularity of the local host clock. The *gpsd* release includes a program
  113. called *clock_test*, and the NTPsec release includes a program in the
  114. attic caled *clock*. Both can characterize your system clock.
  115. Using these programs you can determine the granularity of you system
  116. clock. Some examples:
  117. |====
  118. |CPU |Clock speed|Clock granularity|Standard deviation
  119. |Raspberry Pi 3B|1.2GHz|155 ns|120 ns
  120. |Raspberry Pi 4B|1.5GHz|56 ns|90 ns
  121. |Xeon E5-1620 v3|3.50GHz|14 ns|46 ns
  122. |Core i5-4570|3.20GHz|11 ns|231 ns
  123. |Core i7-8750H|2.2GHz|18 ns|19 ns
  124. |Ryzen 5 3600|3.6 GHz|10 ns|60 ns
  125. |====
  126. Consider these best cases. Any load, reduced clock speed, I/O
  127. interrupts, interrupt latency, etc. will reduce the accuracy with which
  128. he system clock can be read or set. Your goal, and that of NIST stated
  129. in <<NIST2187>>, is that you can tell time to less than 1 micro second.
  130. == NaN ain't your Nana
  131. The most obviously confounding choice is the use in *gpsd* of *NaNs*
  132. (Not A Number). *gpsd* keeps track of a large number of individual
  133. numbers, most of them are invalid at any one time. To keep track of
  134. which integers are valid, a bit field is used. When an integer is
  135. valid, a corresponding bit is set. Keeping track of which bit matches
  136. which integer is challenging. <<IEEE754>> eliminates that problem with
  137. floating point numbers.
  138. When *gpsd* marks a floating point number invalid, it sets the value to
  139. <<NaN>>. So before using any *gpsd* floating point number, check that
  140. it is valid. C obeys <<IEEE754>>. Python sort of does, enough for our
  141. purposes.
  142. === C NaN
  143. A little C program will make the behavior of <<NaN>> easy to see:
  144. [source%nowrap,c,numbered]
  145. ----
  146. // Compile with: gcc nan.c -o nan
  147. #include <stdio.h> // for printf()
  148. int main(int argc, char **argv)
  149. {
  150. double a = 1.0 / 0.0;
  151. double b = -1.0 / 0.0;
  152. printf("a: %f b: %f\n", a, b);
  153. }
  154. ----
  155. What do you expect to see whan that program is run? Try it:
  156. ----
  157. ~ $ gcc nan.c -o nan
  158. ~ $ ./nan
  159. a: inf b: -inf
  160. ----
  161. 1.0 divided by 0.0 is infinity. -1.0 divided by 0.0 is negative infinity.
  162. Any program that printed out a lot of "inf" or -inf" would annoy the users
  163. and they would complain. To avoid that, *gpsd* clients check, and print
  164. out "n/a" instead.
  165. Here is a common solution:
  166. [source%nowrap,c,numbered]
  167. ----
  168. // Compile with: gcc nan.c -o nan
  169. #include <math.h> // for isnan()
  170. #include <stdio.h> // for printf()
  171. int main(int argc, char **argv)
  172. {
  173. double a = 1.0 / 0.0;
  174. if (isnan(a)) {
  175. printf("a: n/a\n");
  176. } else {
  177. printf("a: %f\n", a);
  178. }
  179. }
  180. ----
  181. What do you expect to see whan that program is run? Try it:
  182. ----
  183. ~ $ gcc nan.c -o nan
  184. ~ $ ./nan
  185. a: inf
  186. ----
  187. Whoops. All <<NaN>>s are not <<NaN>>s! Very confusing, rather than try to
  188. explain, I'll send you to the Wikipedia explanation: <<NaN>>. But there
  189. is a simple solution. We do not really care if a number if <<NaN>>, or if it
  190. is infinity. We care that it is finite, and that is easy to test for:
  191. [source%nowrap,c,numbered]
  192. ----
  193. // Compile with: gcc nan.c -o nan
  194. #include <math.h> // for isfinite()
  195. #include <stdio.h> // for printf()
  196. int main(int argc, char **argv)
  197. {
  198. double a = 1.0 / 0.0;
  199. if (isfinite(a)) {
  200. printf("a: %f\n", a);
  201. } else {
  202. printf("a: n/a\n");
  203. }
  204. }
  205. ----
  206. What do you expect to see whan that program is run? Try it:
  207. ----
  208. ~ $ gcc nan.c -o nan
  209. ~ $ ./nan
  210. a: n/a
  211. ----
  212. Exactly the desired result. Now you know why *isfinite()* is all over
  213. *gpsd* client code.
  214. === Python NaN
  215. Python is similar, it almost follows <<IEEE754>>, but has many undocumented
  216. "features" that conflict with <<IEEE754>>:
  217. [source%nowrap,numbered]
  218. ----
  219. # python
  220. >>> a = 1.0 / 0.0
  221. Traceback (most recent call last):
  222. File "<stdin>", line 1, in <module>
  223. ZeroDivisionError: float division by zero
  224. ----
  225. For shame. It does provide a sideways method to set a variable to
  226. various <<NaN>>s:
  227. ----
  228. ~ $ python
  229. >>> Inf = float('inf')
  230. >>> Ninf = float('-inf')
  231. >>> NaN = float('NaN')
  232. >>> print("Inf: %f Ninf: %f NaN: %f" % (Inf, Ninf, NaN))
  233. Inf: inf Ninf: -inf NaN: nan
  234. ----
  235. And *math.isnan()* and *math.isfinite()* work as expected. Continuing
  236. the previous example:
  237. ----
  238. >>> import math
  239. >>> math.isnan(Inf)
  240. False
  241. >>> math.isnan(NaN)
  242. True
  243. >>> math.isfinite(NaN)
  244. False
  245. >>> math.isfinite(Inf)
  246. False
  247. ----
  248. And that is why *gpsd* uses *math.isfinite()* instead of *math.isnan()*.
  249. <<NaN>>s have many other interesting properties, be sure to read up on
  250. the subject. The <<IEEE754>> document is a closed source standard. For a
  251. public description look at the Wikipedia <<NaN>> article.
  252. == REFERENCES
  253. * *GPSD Project web site:* {gpsdweb}
  254. [bibliography]
  255. * [[[DD]]] https://en.wikipedia.org/wiki/Decimal_degrees[Decimal Degrees] Wikipedia Article
  256. * [[[Y2038]]] https://en.wikipedia.org/wiki/Year_2038_problem[2038 Problem] Wikipedia article
  257. * [[[IEEE754]]] https://standards.ieee.org/standard/754-2019.html[IEEE Standard
  258. for Floating-Point Arithmetic]
  259. * [[[NaN]]] https://en.wikipedia.org/wiki/NaN[NaN] Wikipedia Article
  260. * [[[NIST2187]]] https://nvlpubs.nist.gov/nistpubs/TechnicalNotes/NIST.TN.2187.pdf[NIST TN 2187]
  261. * [[[NIST-USNO]]] https://www.nist.gov/pml/time-and-frequency-division/time-services/nist-usno/nist-usno-2020-archive[NIST USNO]
  262. == COPYING
  263. This file is Copyright 2021 by the GPSD project +
  264. SPDX-License-Identifier: BSD-2-clause