Is there a g++ warning or other tool that can identify integer division (truncation toward zero)? I have thousands of lines of code with calculations that inevitably will have numerical errors typically due to "float = int/int" that need to be located. I need a reasonable method for finding these.
Try -Wconversion.
From gcc's man page:
Warn for implicit conversions that may
alter a value. This includes
conversions between real and integer,
like "abs (x)" when "x" is "double";
conversions between signed and
unsigned, like "unsigned ui = -1"; and
conversions to smaller types, like
"sqrtf (M_PI)". Do not warn for
explicit casts like "abs ((int) x)"
and "ui = (unsigned) -1", or if the
value is not changed by the conversion
like in "abs (2.0)". Warnings about
conversions between signed and
unsigned integers can be disabled by
using -Wno-sign-conversion.
For C++, also warn for conversions
between "NULL" and non-pointer types;
confusing overload resolution for
user-defined conversions; and
conversions that will never use a type
conversion operator: conversions to
"void", the same type, a base class or
a reference to them. Warnings about
conversions between signed and
unsigned integers are disabled by
default in C++ unless
-Wsign-conversion is explicitly enabled.
For the following sample program (test.cpp), I get the error test.cpp: In function ‘int main()’:
test.cpp:7: warning: conversion to ‘float’ from ‘int’ may alter its value.
#include <iostream>
int main()
{
int a = 2;
int b = 3;
float f = a / b;
std::cout << f;
return 0;
}
I have a hard time calling these numerical errors. You asked for integer calculations, and got the correct numbers for integer calculations. If those numbers aren't acceptable, then ask for floating point calculations:
int x = 3;
int y = 10;
int z = x / y;
// "1." is the same thing as "1.0", you may want to read up on
// "the usual arithmetic conversions." You could add some
// parentheses here, but they aren't needed for this specific
// statement.
double zz = 1. * x / y;
This page contains info about g++ warnings. If you've already tried -Wall then the only thing left could be the warnings in this link. On second look -Wconversion might do the trick.
Note: Completely edited the response.
Remark on -Wconversion of gcc:
Changing the type of the floating point variable from float to double makes the warning vanish:
$ cat 'file.cpp'
#include <iostream>
int main()
{
int a = 2;
int b = 3;
double f = a / b;
std::cout << f;
}
Compiling with $ g++-4.7 -Wconversion 'file.cpp' returns no warnings (as $ clang++ -Weverything 'file.cpp').
Explanation:
The warning when using the type float is not returned because of the totally valid integer arithmetics, but because float cannot store all possible values of int (larger ones cannot be captured by float but by double). So there might be a change of value when assigning RHS to f in the case of float but not in the case of double. To make it clear: The warning is not returned because of int/int but because of the assignment float = int.
For this see following questions: what the difference between the float and integer data type when the size is same in java, Storing ints as floats and Rounding to use for int -> float -> int round trip conversion
However, when using float -Wconversion could still be useful to identify possible lines which are affected but is not comprehensive and is actually not intended for that. For the purpose of -Wconversion see docs/gcc/Warning-Options.html and here gcc.gnu.org/wiki/NewWconversion
Possibly of interest is also following discussion 'Implicit casting Integer calculation to float in C++'
The best way to find such error is to have really good unit tests. All alternatives are not good enough.
Have a look at this clang-tidy detection.
It catches cases like this:
d = 32 * 8 / (2 + i);
d = 8 * floatFunc(1 + 7 / 2);
d = i / (1 << 4);
Related
Short example:
#include <iostream>
#include <string_view>
#include <iomanip>
#define PRINTVAR(x) printVar(#x, (x) )
void printVar( const std::string_view name, const float value )
{
std::cout
<< std::setw( 16 )
<< name
<< " = " << std::setw( 12 )
<< value << std::endl;
}
int main()
{
std::cout << std::hexfloat;
const float x = []() -> float
{
std::string str;
std::cin >> str; //to avoid
//trivial optimization
return strtof(str.c_str(), nullptr);
}();
const float a = 0x1.bac178p-5;
const float b = 0x1.bb7276p-5;
const float x_1 = (1 - x);
PRINTVAR( x );
PRINTVAR( x_1 );
PRINTVAR( a );
PRINTVAR( b );
PRINTVAR( a * x_1 + b * x );
return 0;
}
this code on godbolt
This code produces different output on different platforms/compilers/optimizations:
X = 0x1.bafb7cp-5 //this is float in the std::hexfloat notation
Y = 0x1.bafb7ep-5
The input value is always the same: 0x1.4fab12p-2
compiler
optimization
x86_64
aarch64
GCC-12.2
-O0
X
X
GCC-12.2
-O2
X
Y
Clang-14
-O0
X
Y
Clang-14
-O2
X
Y
As we can see, Clang gives us identical results between -O0 and -O2 within same architecture, but GCC does not.
The question is - should we expect the identical result with -O0 and -O2 on the same platform?
The question is - should we expect the identical result with -O0 and -O2 on the same platform?
No, not in general.
C++ 2020 draft N4849 7.1 [expr.pre] 6 says:
The values of the floating-point operands and the results of floating-point expressions may be represented in greater precision and range than that required by the type; the types are not changed thereby.51
Footnote 51 says:
The cast and assignment operators must still perform their specific conversions as described in 7.6.1.3, 7.6.3, 7.6.1.8
and 7.6.19.
This means that while evaluating a * x_1 + b * x, the C++ implementation may use the nominal float type of the operands or it may use any “superset” format with greater precision and/or range. That could be double or long double or an unnamed format. Once the evaluation is complete, and the result is assigned to a variable (including, in your example, a function parameter), the result calculated with extended precision must be converted to a value representable in the float type. So you will always see a float result, but it may be a different result than if the arithmetic were performed entirely with the float type.
The C++ standard does not require the C++ implementation to make the same choice about what precision it uses in all instances. Even if it did, each combination of command-line switches to the compiler may be regarded a different C++ implementation (at least for the switches that may affect program behavior). Thus the C++ implementation obtained with -O0 may use float arithmetic throughout while the C++ implementation obtained with -O2 may use extended precision.
Note that the extended precision used to calculate may be obtained not just through the use of machine instructions for a wider type, such as instructions that operate on double values rather than float values, but may arise through instructions such as a fused multiply-add, which computes a*b+c as if a•b+c were computed with infinite precision and then rounded to the nominal type. This avoids the rounding error that would occur if a*b were computed first, producing a float result, and then added to c.
Consider the following program:
#include <iostream>
int main()
{
unsigned int a = 3;
unsigned int b = 7;
std::cout << (a - b) << std::endl; // underflow here!
return 0;
}
In the line starting with std::cout an underflow is happening because a is lesser than b so a-b is less than 0, but since a and b are unsigend so is a-b.
Is there a compiler flag (for G++) that gives me a warning when I try to calculate the difference of two unsigend integers?
Now, one could argue that an overflow/underflow can happen in any calculation using any operator. But I think it is more dangerous to apply operator - to unsigend ints because with unsigned integers this error may happen with quite low (to me: "more common") numbers.
A (static analysis) tool that finds such things would also be great but I much prefer a compiler flag and warning.
GCC does not (afaict) support it, but Clang's UBSanitizer has the following option [emphasis mine]:
-fsanitize=unsigned-integer-overflow: Unsigned integer overflow, where the result of an unsigned integer computation cannot be represented in its type. Unlike signed integer overflow, this is not undefined behavior, but it is often unintentional. This sanitizer does not check for lossy implicit conversions performed before such a computation
Basically an integer variable should allow only integer values to be set for its variable. Then how come such special words as follows are allowed?
int a = 200L;
int a = 200U;
int a = 200F;
I found this when i run the program, it ran perfectly without giving any error. Other letters are not allowed as expected. But why these?
L, U and F means long, unsigned and float respectively.
so, the code means
int a = (long) 200;
int a = (unsigned) 200;
int a = (float) 200;
What you do is called implicit conversion.
If you are using gcc compiler you can add
-Wconversion
(not part of -Wall) option to check any implicit conversion that may alter the value.
Without any option, conversion from signed to unsigned is not warned by default. So you need to active
-Wsign-conversion
If you want an explicit conversion, it will not be warned by those 2 options.
int percent = (int)((int)4.1)*.5;
Two different things are going on here.
1) Some letters when stuck on the end of a number take on meaning. 'l' is for long, 'u' is for unsigned, and 'f' is for float.
"Long" is generally 64 bits wide vs int's 32 bits... but that can
vary wildly from machine to machine. DO NOT depend on bit width of
int and long.
"Unsigned" means it doesn't bother to track positive or
negative values... assuming everything is positive. This about
doubles how high an integer can go. Look up "two's complement" for
further information.
"Float" means "floating point". Non whole numbers. 1.5, 3.1415, etc. They can be very large, or very precise, but not both. Floats ARE 32 bits. "Double" is a 64-bit floating point value, which can permit some extreme values of size or precision.
2) Type Coercion, pronounced "co ER shun".
The compiler knows how to convert (coerce) from long to int, unsigned to int, or float to int. They're all just numbers, right? Note that converting from float to into "truncates" (drops) anything after a decimal place. ((int)3.00000001) == 3. ((int)2.9999999) == 2
If you dial your warnings up to max sensitivity, I believe those statements will all trigger warnings because all those conversions could potentially lose data... though the exact phrasing of that warning will vary from compiler to compiler.
Bonus Information:
You can trigger this same behavior (accidentally) with classes.
struct Foo {
Foo(int bar) {...}
};
Foo baz = 42;
The compiler will treat the above constructor as an option when looking to convert from int to Foo. The compiler is willing to hop through more than one hoop to get there... so Foo qux = 3.14159; would also compile. This is also true of other class constructors... so if you have some other class that takes a foo as it's only constructor param, you can declare a variable of that class and assign it something that can be coerced to a foo... and so on:
struct Corge {
Corge(Foo foo) {...}
};
corge grault = 1.2345; // you almost certainly didn't intend what will happen here
That's three layers of coercion. double to int, into to foo, and foo to corge. Bleh!
You can block this with the explicit keyword:
struct Foo {
explicit Foo(int bar) {...}
};
Foo baz = 1; // won't compile
I wish they'd made explicit the default and used some keyword to define conversion constructors instead, but that change would almost certainly break someone's code, so it'll never happen.
What happens is that you are telling the compiler to convert the value into a different type of data. That is to say:
int a = 200L; // It's like saying: Hey C++, convert this whole to Long
int a = 200U; // And this to Unsigned
int a = 200F; // And this one to Float
There is no error because the compiler understands that these letters at the end indicate a type of conversion.
I get an answer of -496307337. What is wrong in the code?
#include <iostream>
using namespace std;
int main() {
// your code goes here
int displacement_sum_x = -4500;
unsigned long cluster_size = 900;
//... with some calculation the value of displacement_sum_x is -4500.
displacement_sum_x /= cluster_size;
std::cout << displacement_sum_x;
}
Can someone explain the process of the conversion here?
EDIT:
I found out that a typecast needs to be done at displacement_sum_x /= (int)cluster_size; and then the code works.
Is there a way, wherein I can use some kind of c++ warning / error, so that I get the problem at the compile time? The gcc compiler with usual settings, did not complain about this.
First and foremost, you should be avoiding the mixing of signed and unsigned values in the same expression (especially when there are negative numbers involved). In your case, the operation:
displacement_sum_x /= cluster_size;
Is equivalent to:
displacement_sum_x = displacement_sum_x / cluster_size;
In the expression above, since the operands to operator / are different (int/unsigned long), the int is promoted to an unsigned long, which here is a very large value. That result is then divided by 5, and downcasted back to an integer (A good compiler should warn you of the signed/unsigned mismatch). To solve, just change this sort of expression so that cluster_size is initialized as an integer or add a cast it to an integer using static_cast (I recommend the previous).
Here is an example that illustrates this.
You need to cast the value in order to do the operation:
displacement_sum_x /= (int) cluster_size;
The gcc options -Wconversion and -Wsign-conversion should help to see the issue.
You can get more info about the warning options here
Problem Lies here in your code
displacement_sum_x /= cluster_size;
Points
1) You are dividing like (unsigned long) / (int)
2) Since division requires same data type so the int is promoted to unsigned long
3) The value (int displacement_sum_x = -4500 ) is converted to unsigned long which will be equal to 4294962796
4) Then the division takes place with it as 4294962796 / 900 = 4772180.88444
5) And the result will be 4772180 since it integral not a floating type.
I'm often using the wrong literals in expressions, e.g. dividing a float by an int, like this:
float f = read_f();
float g = f / 2;
I believe that the compiler will in this case first convert the int literal (2) to float, and then apply the division operator. GCC and Clang have always let stuff like that pass, but Visual C++ warns about an implicit conversion. So I have to write it like this:
float f = read_f();
float g = f / 2.0f;
That got me wondering: Should I always use the appropriate literals for float, double, long etc.? I normally use int literals whenever I can get away with it, but I'm not sure if that's actually a good idea.
Is this a likely cause of subtle errors?
Is this only an issue for expressions or also for function parameters?
Are there warning levels for GCC or Clang that warn about such implicit conversions?
How about unsigned int, long int etc?
You should always explicitly indicate the type of literal that you intend to use. This will prevent problems when for example this sort of code:
float foo = 9.0f;
float bar = foo / 2;
changes to the following, truncating the result:
int foo = 9;
float bar = foo / 2;
It's a concern with function parameters as well when you have overloading and templates involved.
I know gcc has -Wconversion but I can't recall everything that it covers.
For integer values that fit in int I usually don't qualify those for long or unsigned as there is usually much less chance there for subtle bugs.
There's pretty much never an absolutely correct answer to a "should" question. Who's going to use this code, and for what? That's relevant here. But also, particularly for anything to do with floats, it's good to get into the habit of specifying exactly the operations you require. float*float is done in single-precision. anything with a double is done double-precision, 2 gets converted to a double so you're specifying different operations here.
The best answer here is What Every Computer Scientist Should Know About Floating-Point Arithmetic. I'd say don't tl;dr it, there are no simple answers with floating point.