1

I would like to convert a float or double to a decimal fixed point integer in C language. I am searching the most appropriate (robust) solution for this problem, considering C language's specification.

The problem for example is along the lines of this:

double d = 15.6;
int    i;
...
i = (int)(d * 10.0); /* I am excepting getting 156 in 'i' */

Relevant elements of the standard (using a C99 one, ISO/IEC 9899:TC3, see this question for download) as far as I see:

  • 6.3.1.4 / 1: When a finite value of real floating type is converted to an integer type other than _Bool, the fractional part is discarded (i.e., the value is truncated towards zero). (...)
  • 6.4.4.2 / 3: (...) For decimal floating constants, (...) the result is either the nearest representable value, or the larger or smaller representable value immediately adjacent to the nearest representable value, chosen in an implementation-defined manner. (...)

The 15.6 in the example has not got an exact representation in IEEE double, so I would except d to get something slightly above, or slightly below. The problem is with the "slightly below" part, then i in the example wouldn't get the excepted result (would get 155 instead).

My obvious take would be something like this (considering zero or positive values only):

i = (int)(d * 10.0 + 0.5);

At least if I interpreted the C standard correctly. I ask because due to the "implementation-defined" behavior, one may experience a consistent result while in the reality some other implementation may break the program, so trial and error is not an adequate method for finding an appropriate solution.

In particular the following question relates this problem, which I believe has an incorrect accepted answer.

Community
  • 1
  • 1
Jubatian
  • 2,171
  • 16
  • 22
  • The C standard does not mandate the use of IEEE floating point. It's theoretically possible `d` is represented as some crazy number like `15.65`. This becomes much more likely as `d` gets larger. I would say `d` is already wrong and you should be using an arbitrary precision library from the beginning. – Kevin Dec 10 '14 at 14:35
  • @Kevin: I just mentioned IEEE since even in IEEE this example shows the problem. If you throw in the "heavy artillery" going strict by the wording of the C standard, of course you soon have not just one big gaping hole. An arbitrary precision library may not always be feasible, consider an embedded target, for example. – Jubatian Dec 10 '14 at 14:44
  • Well, then you have to decide which you value more: performance or correctness? – Kevin Dec 10 '14 at 14:47
  • I would suspect `(void) fesetround(FE_DOWNWARD);` followed by `i = (int) nearbyint(d);`, but I'm hardly a floating point guru so I don't dare post this as an answer. – Lundin Dec 10 '14 at 14:49
  • @Kevin: C language floats and doubles shouldn't be **that** bad! Or are they? (so that this problem is impossible to be solved using standard C assuming worst-case) – Jubatian Dec 10 '14 at 14:51
  • If intent on doing something like `i = (int)(d * 10.0 + 0.5);`, use `i = (int)round(d*10.0);` instead. Offset by 0.5, `floor()` , or `ceil()` have issues with + and - numbers. – chux - Reinstate Monica Dec 10 '14 at 15:13
  • @chux `round()` is documented by MS but is not in my `math` library. – Weather Vane Dec 10 '14 at 15:14
  • @Weather Vane `round()` is specified C11 and C99. What compiler are you using? – chux - Reinstate Monica Dec 10 '14 at 15:19
  • @Weather Vane Mnay aspects of MS Visual C are not compliant with the 15 year old C99 spec - maybe someday, it will. For now suggest a simple wrapper function like your comment `double round(double d) { if (d >= 0) return floor(d + 0.5); return = -floor(-d + 0.5); }` or similar till then. Note: VS 2013 appears to have it. – chux - Reinstate Monica Dec 10 '14 at 15:27
  • @chux: While I got an answer which going by the standard should be perfect, these concerns should not be neglected. I will keep an eye on this question, if a proper answer shows up addressing these standard compliance issues (or giving some more general solution, if such is conceivable), I might rather accept that. It is kind of sad / frightening how complicated such simple things may get (I hardly ever use floating point, so have no proper experience on this field). – Jubatian Dec 10 '14 at 16:01
  • BTW: 15.6 does not got an exact representation in the common IEEE 754 [binary64](http://en.wikipedia.org/wiki/Double-precision_floating-point_format) but it does in the less used IEEE 754 [decimal64](http://en.wikipedia.org/wiki/Decimal64_floating-point_format). C does not specify either one. – chux - Reinstate Monica Dec 10 '14 at 16:21

1 Answers1

2

C99 specifies a round function for exactly this purpose. Use that, then cast to int.

UPDATE: for C89, you could try

double y = floor(x);
double z = x == y ? x : floor(2.0*x-y);

This should give the same as C99 round, except that negative numbers with fractional parts equal to 0.5 will be rounded upwards (like Java), and zeros may be signed incorrectly (this is based on a similar trick due to Arch Robinson).

Simon Byrne
  • 7,694
  • 1
  • 26
  • 50
  • Huh, found it (I mean in the standard)! I am a strict C89 guy working in embedded, didn't even pop in my mind that C99 could have this. I accept it since I referred to a C99 standard in my post, so I really can't say anything wrong with this solution. It would be nice though if the answer was eventually edited to include a C89 compliant solution as well. – Jubatian Dec 10 '14 at 15:37
  • Interesting (the article, too), and seems functional, OK by C89. By the way `z = floor(x + 0.5)` why wouldn't be sufficient? C89 (and anything above) defines `floor` to return the largest integer not greater than it's operand, so it should work OK for negatives, too. Arch Robinson was using `trunc`, which for negatives rounds towards zero (similar to my take if I go by 6.3.1.4/1 from the standard - it is an other story that the existence of `fesetround` in C99 makes this point of the standard smelling bogus). – Jubatian Dec 13 '14 at 14:24
  • `floor(x+0.5)` might be sufficient for your case, but not generally: the two cases where it causes problems are `x = 0.49999999999999994` (the floating point number below `0.5`), where adding half will round to 1, and odd values of absolute value between 2^52 and 2^53, where adding half will round to the next even integer. – Simon Byrne Dec 13 '14 at 16:19
  • Also, I seem to recall that C89 doesn't specify the truncating behaviour that is in C99 (i.e. it allows the choice of either the integer above or below), but you should double check that. – Simon Byrne Dec 13 '14 at 16:33
  • The truncating exists in the C89 draft I have, I even formed the question based on that. I did not refer the C89 draft since it is (probably?) not that easy to find like the C99 edition I cited from. Nice little details, though in your previous post. As I see FP, one just shouldn't rely on anything that precise when dealing with FP, rounding also working like "I need values reasonably around 1 to round to integer 1": after all any FP calculation will bring in inaccuracies accumulating. It is an other thing though when it is for compiler design. – Jubatian Dec 15 '14 at 16:14