C++: How to Round a Double to an Int

C++: How to round a double to an int?

add 0.5 before casting (if x > 0) or subtract 0.5 (if x < 0), because the compiler will always truncate.

float x = 55; // stored as 54.999999...
x = x + 0.5 - (x<0); // x is now 55.499999...
int y = (int)x; // truncated to 55

C++11 also introduces std::round, which likely uses a similar logic of adding 0.5 to |x| under the hood (see the link if interested) but is obviously more robust.

A follow up question might be why the float isn't stored as exactly 55. For an explanation, see this stackoverflow answer.

Convert double to int?

The reason for this is that GCC tries to make the code backward compatible with older architectures of the CPU as much as possible, while MSVC tries to take advantage of the newer futures of the architecture.

The code generated by MSVC multiplies the two numbers, 10.0 × 18.40:

.text:00401006 fld     ds:dbl_40D168
.text:0040100C fstp [ebp+var_8]
.text:0040100F fld ds:dbl_40D160
.text:00401015 fmul [ebp+var_8]
.text:00401018 call __ftol2_sse

and then call a function named __ftol2_sse, inside this function it converts the result to integer using some instruction named cvttsd2si:

.text:00401189 push    ebp
.text:0040118A mov ebp, esp
.text:0040118C sub esp, 8
.text:0040118F and esp, 0FFFFFFF8h
.text:00401192 fstp [esp+0Ch+var_C]
.text:00401195 cvttsd2si eax, [esp+0Ch+var_C]
.text:0040119A leave
.text:0040119B retn

This instruction, cvttsd2si, is according to this page:

Convert scalar double-precision floating-point value (with truncation)
to signed doubleword of quadword integer (SSE2)

it basically converts the double into integer. This instruction is part of instruction set called SSE2 which is introduced with Intel Pentium 4.

GCC doesn't uses this instructions set by default and tries to do it with the available instructions from i386:

fldl   0x28(%esp)
fldl 0x403070
fmulp %st,%st(1)
fnstcw 0x1e(%esp)
mov 0x1e(%esp),%ax
mov $0xc,%ah
mov %ax,0x1c(%esp)
fldcw 0x1c(%esp)
fistpl 0x18(%esp)
fldcw 0x1e(%esp)
mov 0x18(%esp),%eax
mov %eax,0x4(%esp)
movl $0x403068,(%esp)
call 0x401b44 <printf>
mov $0x0,%eax

if you want GCC to use cvttsd2si you need to tell it to use the futures available from SSE2 by compiling with the flag -msse2, but also this means that some people who still using older computers won't be able to run this program. See here Intel 386 and AMD x86-64 Options for more options.

So after compiling with -msse2 it will use cvttsd2si to convert the result to 32 bit integer:

0x004013ac <+32>:    movsd  0x18(%esp),%xmm1
0x004013b2 <+38>: movsd 0x403070,%xmm0
0x004013ba <+46>: mulsd %xmm1,%xmm0
0x004013be <+50>: cvttsd2si %xmm0,%eax
0x004013c2 <+54>: mov %eax,0x4(%esp)
0x004013c6 <+58>: movl $0x403068,(%esp)
0x004013cd <+65>: call 0x401b30 <printf>
0x004013d2 <+70>: mov $0x0,%eax

now both MSVC and GCC should give the same number:

> type test.c
#include <stdio.h>

int main(int argc, char *argv[])
{

double f = 18.40;
printf("%d\n", (int) (10.0 * f));

return 0;
}
> gcc -Wall test.c -o gcctest.exe -msse2
> cl test.c /W3 /link /out:msvctest.exe
> gcctest.exe
184
> msvctest.exe
184
>

Casting a double as an int, does it round or just strip digits?

It does not round, it just returns the integral part before the decimal point.

Reference (thanks Rawling) Explicit Numeric Conversions Table:

When you convert a double or float value to an integral type, this
value is rounded towards zero to the nearest integral value.

You can try simple issues like this by yourself by writing simple tests. The following test (using NUnit) will pass and therefore give an answer to your question:

[Test]
public void Cast_float_to_int_will_not_round_but_truncate
{
var x = 3.9f;
Assert.That((int)x == 3); // <-- This will pass
}

Double to integer casting in C++

First of all, let me demonstrate that what you claim to be happening is not happening, with a simple test program:

#include <iostream>

int main()
{
double doubleAmount = 16200000.0;
int intAmount = (int)doubleAmount;
std::cout << intAmount << std::endl;
}

This outputs


16200000

What is happening in your program is that whilst you think that your floating point value is 16200000.0, in fact it is something like 16199999.xxx. Your value is most likely coming from some calculation that results in a value close to 16200000.0 but slightly less than 16200000.0. When you truncate to int you get 16199999.

Why do you think that the value is 16200000.0 when in fact it is a value slightly less? Again I am speculating, but most likely that's because you are not printing the value to full precision. Make sure when you print the value out, or inspect it in the debugger, you are observing the full precision of the value.

This sort of phenomenon is common with floating point calculations. Finite precision binary floating point data types cannot represent all numbers, and cannot perform all calculations exactly. These imprecisions are inherent in binary floating point arithmetic.

Without knowing what problem you are trying to solve, and without knowing how the value was calculated, it is not possible to give you definitive advice on the correct way to tackle your problem. Rounding to the nearest integer (use std::round or std::lround) may indeed be a reasonably solution to your problem. But without more detail it's not possible to say that with certainty.

Finally, if you have not already done so, it is time to read David Goldberg's classic: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

cast variable to int vs round() function

In case of casting a float/double value to int, you generally loose the fractional part due to integer truncation.

This is quite different from rounding as we would usually expect, so for instance 2.8 ends up as 2 with integer truncation, just as 2.1 would end up as 2.

Update:

Another source of potential (gross) inaccuracy with casting is due to the limited range of values being able to be represented with integers as opposed to with floating point types (thanks to @R reminding us about this in the comment below)

Is it safe to cast to int from std::round?

The std::round function returns a floating point value, "rounding halfway cases away from zero". As with any double to int implicit conversion the compiler will issue a warning:

conversion from 'double' to 'int', possible loss of data

Use std::lround or std::lrint if you want to return an integral value.

How to round down a double to the nearest smaller int in C?

int i = (int)floor(25.342);


Related Topics



Leave a reply



Submit