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
Why Can't a Derived Class Call Protected Member Function in This Code
Why Is Allocator::Rebind Necessary When We Have Template Template Parameters
Know If .Lib Is Static or Import
How to Read-Write Into/From Text File with Comma Separated Values
C++11 Initializer List Fails - But Only on Lists of Length 2
What's Faster, Iterating an Stl Vector with Vector::Iterator or with At()
Case Insensitive Std::String.Find()
When Is an Object "Out of Scope"
How to Use Profile Guided Optimizations in G++
Should I Include <Xxxx.H> or <Cxxxx> in C++ Programs
Relative Performance of Std::Vector VS. Std::List VS. Std::Slist
Iterating C++ Vector from the End to the Beginning
C++ Compiler Error C2280 "Attempting to Reference a Deleted Function" in Visual Studio 2013 and 2015
Behaviour of Malloc with Delete in C++
Move-Only Version of Std::Function
C++11 Inheriting Constructors and Access Modifiers
Multiple Definition of Template Specialization When Using Different Objects