Showing posts with label integer data type. Show all posts
Showing posts with label integer data type. Show all posts

Sunday, 26 February 2012

Integer and Floating Point Literals in C and C++


I played around in C++ with some code to see how it calculates numbers that are integers or floating point literals. The code, comments and results follow.

The term floating-point number is generic in that it doesn’t specify a byte size. A single-precision floating-point number is specific. In C/C++ it’s a 4 byte number with 6 decimal precision (e.g., 6.123456). A double-precision floating-point number uses 8 bytes and has 10 decimal precision (e.g., 10.0123456789).

Sample Code Part 1 – Declaring Variables with Literals.

The sample code was created in MS Visual C++ 2010 as a Windows Form Application. It contains a form (Form1) that displays when the program runs. The sample code, contained in the Form Load Event, executes and adds text to a RichTextBox named rtOut.

// Title.
rtOut->Text = "Experiments Declaring Integer & Floating-point Data Types.\n\n";

// declare variables
int iVal = 8;
rtOut->Text += "Declare: int data type (iVal) with literal 8: int iVal = 8;\n";
float fVal = 8.0;
rtOut->Text += "Declare: float data type (fVal) with literal 8.0: float fVal = 8.0;\n\n";

// Integer in a string.
rtOut->Text += "The int data type (iVal) is " + iVal + " in this string.\n";
rtOut->Text += "There is no decimal point or decimal numbers.\n\n";

// Float in a string.
rtOut->Text += "The float data type (fVal) is " + fVal + " in this string.\n";
rtOut->Text += "There is no decimal point or decimal numbers.\n";
rtOut->Text += "In debug mode, the local window shows: iVal: 8 fVal: 8.000000\n";
rtOut->Text += "\n";

int iVal2 = 8.2;
rtOut->Text += "Declare: int data type (iVal)  with literal 8.2: int iVal = 8.2;\n";
rtOut->Text += "Assigning a floating-point literal to an integer results in a compiler warning:\n";
// Warning 1 warning C4244: 'initializing' : conversion from 'double' to 'int', possible loss of data
rtOut->Text += "Warning    1 warning C4244: 'initializing' : conversion from 'double' to 'int', possible loss of data\n";
rtOut->Text += "The code is compiled and the program executes.\n";
rtOut->Text += "The variable holds the value of 8. The decimals are truncated.\n\n";

rtOut->Text += "To us, 8 and 8.0 are the same, but to C/C++ they aren't the same.\n";
rtOut->Text += "8 is an integer literal while 8.0 is a floating-point literal.\n";


Output

Experiments Declaring Integer & Floating-point Data Types.

Declare: int data type (iVal) with literal 8: int iVal = 8;
Declare: float data type (fVal) with literal 8.0: float fVal = 8.0;

The int data type (iVal) is 8 in this string.
There is no decimal point or decimal numbers.

The float data type (fVal) is 8 in this string.
There is no decimal point or decimal numbers.
In debug mode, the local window shows: iVal: 8 fVal: 8.000000

Declare: int data type (iVal)  with literal 8.2: int iVal = 8.2;
Assigning a floating-point literal to an integer results in a compiler warning:
Warning           1 warning C4244: 'initializing' : conversion from 'double' to 'int', possible loss of data
The code is compiled and the program executes.
The variable holds the value of 8. The decimals are truncated.

To us, 8 and 8.0 are the same, but to C/C++ they aren't the same.
8 is an integer literal while 8.0 is a floating-point literal.


Sample Code Part 2 – Math  with Literals.

// Math.
rtOut->Text = "Math Using Literals With Integer & Floating-point Data Types.\n\n";

rtOut->Text += "iVal equals 8.\n";
rtOut->Text += "Divide iVal by 3 or iVal / 3  results in: " + iVal / 3 + "\n";
rtOut->Text += "The literal 3 is an integer.\n";
rtOut->Text += "An integer divided by an integer stays an integer.\n";
rtOut->Text += "Any decimals are truncated.\n\n";

rtOut->Text += "iVal / 3.0 results in: " + iVal / 3.0 + "\n";
rtOut->Text += "The decimals were NOT truncated.\n";
rtOut->Text += "Using iVal / 3.0 results in a floating-point number since the literal 3.0 is a floating-point number.\n";
rtOut->Text += "Specifically, C++ treats 3.0 as a double-precision floating-point number.\n";
rtOut->Text += "\n";

rtOut->Text += "Literal Suffixes: L, F, L\n\n";

rtOut->Text += "C/C++ use suffixes after a literal to specify its type and \n";
rtOut->Text += "overrides the built-in assumption about the type of a literal.\n\n";

rtOut->Text += "iVal / 3L results in: " + iVal / 3L + ".\n";
rtOut->Text += "The L in this instance means long integer.\n\n";

rtOut->Text += "Does the literal suffix have to follow the number?\n";
rtOut->Text += "Yes. It doesn't like whitespace here.\n";
rtOut->Text += "Using \"iVal / 3.0 L\" results in a compile error:\n";
rtOut->Text += "  error C2065: 'L' : undeclared identifier \n\n";

rtOut->Text += "Does the literal suffix have to be in uppercase? No.\n";
rtOut->Text += "iVal / 3l results in: " + iVal / 3l + "\n\n";

rtOut->Text += "Oddly C/C++ doesn't follow the usual rules of ignoring whitespace and case sensitivity.\n";
rtOut->Text += "The lowercase 'l' can easily be confused with a 1 therefore it is better to use the uppercase L.\n\n";

rtOut->Text += "iVal / 3.0L results in: " + iVal / 3.0L + ".\n";
rtOut->Text += "The L in this instance means long double.\n";
rtOut->Text += "To create confusion, 3L is a long integer while 3.0L is a long double.\n\n";

rtOut->Text += "That leads to the third suffix: f or F for single-precision floating point.\n\n";
rtOut->Text += "iVal / 3.0F results in: " + iVal / 3.0F + ". \n";
rtOut->Text += "iVal / 3.0f results in: " + iVal / 3.0F + ". \n";
rtOut->Text += "iVal / 3F results in compile errors: \n";
rtOut->Text += "  error C2059: syntax error : 'bad suffix on number'\n";
rtOut->Text += "  error C2065: 'F' : undeclared identifier\n";
rtOut->Text += "  error C2146: syntax error : missing ';' before identifier 'F'\n";
rtOut->Text += "3 is an integer, 3.0 is a floating-point number.\n\n";


Output.

Math Using Literals With Integer & Floating-point Data Types.

iVal equals 8.
Divide iVal by 3 or iVal / 3  results in: 2
The literal 3 is an integer.
An integer divided by an integer stays an integer.
Any decimals are truncated.

iVal / 3.0 results in: 2.66666666666667
The decimals were NOT truncated.
Using iVal / 3.0 results in a floating-point number since the literal 3.0 is a floating-point number.
Specifically, C++ treats 3.0 as a double-precision floating-point number.

Literal Suffixes: L, F, L

C/C++ use suffixes after a literal to specify its type and
overrides the built-in assumption about the type of a literal.

iVal / 3L results in: 2.
The L in this instance means long integer.

Does the literal suffix have to follow the number?
Yes. It doesn't like whitespace here.
Using "iVal / 3.0 L" results in a compile error:
  error C2065: 'L' : undeclared identifier

Does the literal suffix have to be in uppercase? No.
iVal / 3l results in: 2

Oddly C/C++ doesn't follow the usual rules of ignoring whitespace and case sensitivity.
The lowercase 'l' can easily be confused with a 1 therefore it is better to use the uppercase L.

iVal / 3.0L results in: 2.66666666666667.
The L in this instance means long double.
To create confusion, 3L is a long integer while 3.0L is a long double.

That leads to the third suffix: f or F for single-precision floating point.

iVal / 3.0F results in: 2.666667.
iVal / 3.0f results in: 2.666667.
iVal / 3F results in compile errors:
  error C2059: syntax error : 'bad suffix on number'
  error C2065: 'F' : undeclared identifier
  error C2146: syntax error : missing ';' before identifier 'F'
3 is an integer, 3.0 is a floating-point number.





Wednesday, 22 February 2012

Why The abs Function in C++ Returns A Negative Value

I played around with the abs function in Visual C++ 2010 from the <cstdlib> header file, and in one instance, the function returned a negative value.

Note the parameter for the abs in <cstdlib> is integer and it returns the absolute value.

Here’s the code:

#include <cstdlib>
...
int iVal = -2147483648;
iVal = abs(iVal);

After the call to the abs function, iVal held -2147483648. It was as if nothing had happened. Why? What happened? It has to do with the range for the int data type.

The range of a 32-bit integer is -2,147,483,648 to 2,147,483,647. As you can see, the positive value of the largest negative value is outside the range.

The abs function did work. Here’s a brief explanation of how it worked and why the result is what it is.

A signed integer uses the two’s complement binary number system. That means the left most bit is used as a sign bit (0 for positive and 1 for negative). The remaining bits are used to represent the number.

To simplify things. I’ll use a 4-bit integer. The maximum number of combinations is 2^4 or 16. The largest negative number is 2^(4-1) or 8. The largest positive number is 2^(4-1)-1 or 7. The range is therefore -8 to 7.

In binary we have:

0 1 1 1   equals +7
1 0 0 0   equals -8

So lets take the absolute value of -8 at the bit level.

Start:              1 0 0 0       equals -8
Negate bits:        0 1 1 1       equals  7
Add one             1 0 0 0       equals -8

And so we start with -8 and end up with -8. The same result happens regardless of the size of the signed integer.

Try it with a value that is in range.

Start:             1 1 0 1       equals -3
Negate bits:       0 0 1 0       equals  2
Add one            0 0 1 1       equals  3

Tuesday, 21 February 2012

The Ripped abs Function in C++

I wanted to use the abs function in C++. I checked documentation and found a lack of specifications and seeming contradictions. There were also various versions of functions to get an absolute value (abs, labs, fabs). Instead of trying to make sense of what reference was correct or outdated, I plunged ahead with code to see what would work or what didn’t work. Here’s the results.

Note: I used Visual C++ 2010.
Double Note: The MS documentation on this issue was weak.

Attempt No. 1.

My first attempt was simply to use the abs function on a double variable.

double dVal = -10;
dVal = abs(dVal);

It resulted in a compile error: C3861: 'abs': identifier not found

I knew I needed to include a header file, but I tried it this way to see what would happen.

Attempt No. 2.

What if I added a reference to the standard namespace when calling the abs function?

double dVal = -10;
dVal = std::abs(dVal);

No improvement. This time I got two compiler errors:

C3861: 'abs': identifier not found
C2039: 'abs' : is not a member of 'std'

Attempt No. 3.

Right, time to add a header file, but which one?

#include <cstdlib>
#include <stdlib.h>
#include <cmath>
#include <math.h>.

I saw documentation, text and sample code using one or the other. Instead of continued frustration looking for the answer, I decided I would try each library separately with this code.

#include <cstdlib> | <cmath>
...
double dVal = -10;
dVal = abs(dVal);

The standard library <cstdlib> resulted in a compile error:

C2668: 'abs' : ambiguous call to overloaded function

Running the code with either math library worked fine. The variable dVal becomes 10.000000000000000.

Why the compile error and what header file to include? Part of the confusion lies in the fact the abs function shows up in two different header files.

Here’s what I learnt from the ISO C++ Standard documentation (N3337 2012-01-16). It’s known as C++11—the latest standard of C++, and I should have looked here first.

<math.h> and <stdlib.h> are Standard C library headers. That's the C language, not C++, but since the C++ is a superset of C, the C++ Standard library headers <cmath> <cstdlib> are the same as in C except C++ extends them. (I refer you to paragraph 4 of subclause 26.8 of the ISO C++ Standard.)

The abs function in <cstdlib> takes an integer and returns an integer. If you use the <cmath> library, the function takes a float-point number and returns the same. Both have variations depending on the number of bits used for a particular type.

The <cstdlib> header supports:

int abs (int);
long abs (long);
long long abs (long long);

The long long data type is a 64-bit integer and new to the C++11 standard. This implementation exists in Visual C++ 2010. Microsoft also has it’s own integer types of __intN where N is 8, 16, 32, 64 or 128. __int64 is the same as long long.

The <cmath> header supports:

float abs (float);
double abs (double);
long double abs (long double);


Attempt No. 4.

Focusing on the standard library <cstdlib>, I tested the following code.

#include <cstdlib>
...
int iVal = -10;
iVal = abs(iVal);

The code executes as expected and iVal becomes 10.

But here’s a twist.

#include <cstdlib>
...
iVal = -2147483648;
iVal = abs(iVal);

The value of iVal remains the same. It’s still the exact same negative number. How can that be? To get the answer you have to understand the range of the integer data type. On my computer, int variables are signed 32-bit numbers. The range is -2,147,483,648 to 2,147,483,647. The negative number exceeds the maximum positive number. The abs function worked. It took the negative number, made it positive but since it was larger than the upper limit, it became a negative number. You’ll get the same result if you took the upper limit and added one.


Attempt No. 5.

Focusing on the math library <cmath>, I tested the following code.

#include <cmath>
...
float fVal = -10.123456; // Works.
fVal = abs(fVal);

double dVal = -10.123456; // Works.
dVal = abs(dVal);

The data type long double is the same as double in Visual C++ 2010.

There was no problem using the function.

Attempt No. 6.

The previous code had either the standard library or the math library but not both. What happens when both headers are included? I ran code similar to above and it worked. No compiler errors. No unexpected return values.

Final Thought.

In C, the program has more than one function to determine the absolute value of a number.

abs                 for integers
fabs              for float, double or long double
labs              for long int

Why have variations? Because overloading a function does not happen in C, but it is part of C++. What does that mean? With overloading, the parameters passed to a function can varying depending on their type. In turn, the return value reflects the parameters passed. In C, it needed three different functions to handle the different data types. Not so in C++, yet these C functions  remain because the philosophy of the C++ Standard is to take C and add on to it.