0

Say you have a double value, and want to round it to an integer...

Many round() functions return a double instead of an integer:

  • C# - round(double) -> double
  • C++ - round(double) -> double
  • Darwin - round(double) -> double
  • Swift - double.rounded() -> double
  • Java - round(double) -> int
  • Ruby: float.round() -> int

(This is most likely because doubles have a much wider range of possible values.)

Given this "default" behavior, it probably explains why you'll commonly see the following recommended:

Int(round(myDouble))

(Here we assume that Int() removes everything after the decimal: 4.9 -> 4.)

So far so good, until you realize how complex floating points really are. E.g. 55 might actually be stored as 54.9999999999999999, for example.

Because of this, it sounds like the following might happen:

Int(round(55.4))       // we ask it to round 55.4, expecting 55
Int(54.9999999999999)  // it rounded it to "55.0"
54                     // the Int() function removed all remaining digits

We were expecting 55.4 rounded to be 55, but it ended up evaluating to 54.

  1. Can something like the above really happen if we use Int(round(x))?
  2. If so, what should we use instead of Int(round())?
  3. Related: Many languages define floor(double) -> double. Is Int(floor(double)) safe?
Community
  • 1
  • 1
Senseful
  • 86,719
  • 67
  • 308
  • 465
  • The assumption that rounded values (result of round function) could have a non null fraction part is not corresponding to reality. I don't know if such exotic floating point model did ever exist, but if so, it surely died. If it's not in the model, it's not in the library either: a round function that does not round is a bug, not a feature. – aka.nice May 11 '17 at 07:46
  • So you don't want to convert to Int for removing the decimals, round() already did that for you. You want to convert to Int because some function further in code do not support polymorphic types, and if you do so, you'll have to deal with limited Int range - that's the unsafe part. If the language supports unbounded Integer, then round result is probably already an Integer. – aka.nice May 11 '17 at 07:59

1 Answers1

2

Floating point models are constructed on these foundations:

  • a base b
  • a significand with a limited number of digits in that base (p the precision)
  • an exponent e for shifting the floating point, also limited in a certain range
  • a sign

So the floating point values are made like this: (-1)^signBit * significand * b^e

The significand can be represented in a normalized form x.xxxxxxxx with 1 non null digit left of floating point (except for zero, or eventually values near zero that lose precision and gradually underflow), and p-1 digit after floating point.

But by shifting appropriately the exponent (e+1-p), it can as well be considered as an integer with p digits, xxxxxxxxx.0.

With a reasonnable range for exponent, we see that every integer up to b^p can be represented exactly by such floating point model. With the limited precision, only the last digits in base b are lost, so if we have an integer too large to fit in significand, it will necessarily have a null fraction part. Thus, there is no reason for round to answer anything else but an integral value (with null fraction part).

The only unsafe part as you noted is that Int range might be much smaller than range of floating point values. Thus converting large floating point to Int could result in overflow exception, or worse, silent overflow with undefined behavior...

The conversion to Int is thus not necessary for the sake of eliminating the fraction part. It must be for other purposes (like feeding another part of the program that would only accept an Int).

aka.nice
  • 9,100
  • 1
  • 28
  • 40