[NB I almost completely rewrote this answer since I first posted it.]
The answer to your first question: C has so many types to balance the needs of supporting machines of all different word sizes with reasonable portability. Things get complicated because of the desire to also support specialized quantities, such as "data structure sizes", "file offsets" and "real-time times", are reasonably portable, and although sometimes these specialized quantities end up not determined by the language specification or a compiler, but rather an operating base.
In general, there are two problems when converting from a large integral type to a floating point type:
In the case of time_t , an additional problem occurs:
- The
time_t type may not be an integer number of seconds, which you can significantly subtract
(These days, “useful” compiler warnings in response to these problems sometimes seem bordering on nannyish, and I share your concern. It may be difficult to understand in what obscure situation the compiler is really worried about, and it may be difficult to understand how to rewrite the code without warnings, and it can be difficult to make sure that any throws you need to insert do not ultimately make the code even less secure.)
If you are not worried about problem # 3 (if you want to assume that time_t is an integer number of seconds), you can minimize the likelihood of data loss by first subtracting (and into an integral type) and then converting:
return (sometype)(end->tv_sec - start->tv_sec) + (sometype)(end->tv_usec - start->tv_usec) / 1e6;
But, of course, the big question is: what type should be?
I believe that your best bet is to throw a double in each of these places. Both the guaranteed range and the accuracy of the double type in C are quite large. Therefore, if you do not manipulate time differences exceeding 1e50 years (and if someone did not use the subsec_t type as a 266-bit type or something else), your code should be safe even when suppressing warnings, and you can insert a comment to to this issue.
If you want to understand how these problems can manifest themselves in practice, they are easy to demonstrate. Try the following:
float f = (float)2000000123L - (float)2000000000L; printf("%f\n", f);
If you have a 64-bit compiler, you can observe precision loss even with double precision:
double d = (double)9000000000000001234LL - (double)9000000000000000000LL; printf("%f\n", d);
On my machine, these two fragments print 128 and 1024 respectively.
I'm not sure what exactly of the three questions your compiler tried to warn you about. # 1 is the most likely opportunity. And you can see how the loss of accuracy disappears if you convert after subtraction, and not earlier:
f = 2000000123L - 2000000000L; d = 9000000000000001234LL - 9000000000000000000LL;
or
f = (float)(2000000123L - 2000000000L); d = (double)(9000000000000001234LL - 9000000000000000000LL);
Back when all we had was 32-bit longs and 64-bit doubles, in practice this was not very important (since the IEEE 754 double has something like 52 bits of precision). Now that 64-bit types are becoming commonplace, these kinds of warnings are becoming more widespread. If you are satisfied that the double type has sufficient precision in all your time subtracting needs, you can use the appropriate double casts to turn off warnings. (And, again, here by “appropriate” we mean “after subtraction.”) If you want to be even more secure, you can go instead of long double . If your compiler supports it, and if it is really "longer" than a regular double , it can really reduce the problem of accuracy loss. (Here is the previous example using long double :
long double ld = (long double)9000000000000001234LL - (long double)9000000000000000000LL; printf("%Lf\n", ld);
On my system, this prints 1234 )
But with all that has been said, in this case, if you want to make your life easier and, by the way, turn to problem No. 3 at the same time, you could and perhaps should use the standard function to calculate the difference. The standard function for subtracting two time_t values is difftime . (It’s a difftime job to worry about all these things, including the possibility that time_t does not represent seconds directly.) That way you could write
return difftime(end->tv_sec - start->tv_sec) + (double)(end->tv_usec - start->tv_usec) / 1e6;
although, of course, the problem is with subseconds.
The best solution is a pre-written library function for subtracting two timeval , and you can spend some time looking for one of them.