PF Datatypes Arrays - 11
PF Datatypes Arrays - 11
• This equals 232, thus, the largest value that can be stored as an
unsigned int is 232 – 1 = 4294967295
• Approximately 4 billion
unsigned short
• Sometimes, you don’t need to store numbers this large
• Variables declared unsigned short are allocated two
bytes
• 2 bytes is 2 × 8 = 16 bits
• 16 different 1s and 0s can be stored
• The smallest and largest:
0000000000000000
1111111111111111
• The smallest represents 0
• The largest is one less than
10000000000000000
16 zeros
• This equals 216, thus, the largest value that can be stored as an
unsigned int is 216 – 1 = 65535
unsigned long
• Sometimes, you need to store very large numbers
• Variables declared unsigned long are allocated eight bytes
• 8 bytes is 8 × 8 = 64 bits
• 64 different 1s and 0s can be stored
• The smallest and largest:
0000000000000000000000000000000000000000000000000000000000000000
1111111111111111111111111111111111111111111111111111111111111111
• The smallest represents 0
• The largest is one less than
1 0000000000000000000000000000000000000000000000000000000000000000
64 zeros
• This equals 264, thus, the largest value that can be stored as an
unsigned int is 264 – 1 = 18446744073709551615
• This 18 billion billion or 18 quintillion
Example
• Consider this program: Note:
#include <iostream> First, a is upcast to unsigned int
before to the first addition
// Function declarations Then, the result is upcast to
int main(); unsigned long before the second addition
// Function definitions
int main() {
unsigned short a{42};
unsigned int b{207500}; Output:
unsigned long c{299792458}; 300000000
return 0;
}
Example
• On the stack, an appropriate number of bytes are
allocated to each variable
#include <iostream>
// Function declarations
int main();
// Function definitions
int main() {
unsigned short a{42};
unsigned int b{207500};
unsigned long c{299792458};
return 0;
}
Example
• Each of these variables is then initialized
#include <iostream>
// Function declarations
int main();
// Function definitions
int main() {
unsigned short a{42};
unsigned int b{207500};
unsigned long c{299792458};
return 0;
}
Example
• Generally, however, we display the bytes in memory as a
column of bytes, the values of which are concatenated
#include <iostream>
// Function declarations
int main();
// Function definitions
int main() {
unsigned short a{42};
unsigned int b{207500};
unsigned long c{299792458};
return 0;
}
Wasted space?
• If an integer does not use all the bytes, the remaining bits
are never-the-less allocated until the variable goes out of
scope
• In general-purpose computing, this is often not a problem
• This is a critical issue, however, in embedded systems
• More memory:
• Costs more
• Uses more power
• Produces more heat
Determining the size of a type
• We have said short, int and long are 2, 4 and 8 bytes
• This is true on most every general-purpose computer
• Unfortunately, the C++ specification doesn’t require this
• Fortunately, the sizeof operator gives you this information
#include <iostream>
int main();
int main() {
std::cout << "An 'unsigned short' occupies "
<< sizeof ( unsigned short ) << " bytes" << std::endl;
std::cout << "An 'unsigned int' occupies "
<< sizeof ( unsigned int ) << " bytes" << std::endl;
std::cout << "An 'unsigned long' occupies "
<< sizeof ( unsigned long ) << " bytes" << std::endl;
return 0;
Output on ecelinux:
} An 'unsigned short' occupies 2 bytes
An 'unsigned int' occupies 4 bytes
An 'unsigned long' occupies 8 bytes
Memory and initial values
• Question:
• What happens if the initial value cannot be stored?
#include <iostream>
int main();
int main() {
unsigned short c{299792458};
std::cout << "The speed of light is " << c
<< " m/s." << std::endl;
return 0;
}
Memory and initial values
• Fortunately, you get a warning:
example.cpp: In function 'int main()':
example.cpp:6:31: warning: narrowing conversion of â299792458â from 'int' to
'short unsigned int' inside { } [-Wnarrowing]
unsigned short c{299792458};
^
example.cpp:6:31: warning: large integer implicitly truncated to unsigned
type [-Woverflow]
int main();
int main() {
unsigned short m1{40000}, m2{42000};
int n1{40000}, n2{42000};
unsigned short sum{m1 + m2}, diff{m1 - m2}, prod{m1*m2};
std::cout << sum << "\t" << (n1 + n2) << std::endl;
std::cout << diff << "\t" << (n1 - n2) << std::endl;
std::cout << prod << "\t" << (n1*n2) << std::endl;
return 0;
Output:
}
16464 82000
63536 -2000
50176 1680000000
Memory and arithmetic
• Let’s look at the actual values and the evaluated results:
16464 0100000001010000
82000 10100000001010000
63536 1111100000110000
–2000 -0000011111010000
50176 1100010000000000
1680000000 1100100001000101100010000000000
• For the sum and product, the result ignores the higher-
order bits
• The negative number is a little odd….
Memory and arithmetic
• What happens if the sum, difference or product of two
integers exceeds what can be stored?
#include <iostream>
int main();
int main() {
unsigned short smallest{0}, largest{65535};
1010111111100000
0101000000100000
0000100100101100
1111011011010100
2’s complement
• The 2’s complement of 0 stored as an unsigned int is
00000000000000000000000000000000
11111111111111111111111111111111
+ 1
100000000000000000000000000000000
int main();
//////////////////////////////////////////////////
// Verify that every possible sum, difference,
// product, division and remainder option on
// 'unsigned short' is the actual operation
// modulo 65536
//////////////////////////////////////////////////
int main() {
long const TWO_15{32768};
long const TWO_16{2*TWO_15};
if ( k < 0 ) {
k += TWO_16;
}
if ( k < 0 ) {
k += TWO_16;
}
if ( k < 0 ) {
k += TWO_16;
}
if ( k < 0 ) {
k += TWO_16;
}
if ( k < 0 ) {
k += TWO_16;
}
return 0;
}
Summary so far
• We have the following:
• Unsigned integers are stored as either 1, 2, 4 or 8 bytes
• The value is stored in the binary representation
Approximate
Type Bytes Bits Range
Range
unsigned char 1 8 0, …, 28 – 1 0, …, 255
unsigned short 2 16 0, …, 216 – 1 0, …, 65535
unsigned int 4 32 0, …, 232 – 1 0, …, 4.3 million
unsigned long 8 64 0, …, 264 – 1 0, …, 1.8 trillion
• Note that
–1 + 1 = 0, but also 11 + 1 = 0
–5 + 2 = –3, but also 7 + 2 = 9, which we are equating to –3
Signed integers
• Here is a workable solution:
• If the leading bit is 0:
• Assume the remainder of the number is the integer represented
• For short, this includes
0000000000000000 0
0111111111111111 215 – 1 = 32767
• This includes 215 different positive numbers
• If the leading bit is 1:
• Assume the number is negative and its magnitude can be found by
applying the 2’s complement algorithm
• Recall the 2’s complement algorithm is self-inverting
Signed integers
• For negative numbers stored as a short:
1000000000000000
0111111111111111
+ 1
1000000000000000
• This is the representation of the largest negative number: –215
1111111111111111
0000000000000000
+ 1
0000000000000001
• This is the representation of the smallest negative number: –1
Signed integers
• Here, you can compare these two techniques
• In both cases, we go from –12/2 to 12/2 – 1 and –216/2 to 216/2 – 1
Signed integers
• For example, 1111111111010110 is a negative short
1111111111010110
0000000000101001
+ 1
0000000000101010
• Thus, it represents –42
• Let’s calculate –42 + 91 = 49 and –42 – 91 = –133:
1111111111010110
+ 0000000001011011
10000000000110001
49
1111111111010110
–91
+ 1111111110100101
131
11111111101111011
–133 10000101
Summary
• To summarize:
• Integer types are stored as either 1, 2, 4 or 8 bytes
• Negative numbers are stored in the 2’s complement representation
Approximate
Type Bytes Bits Range
Range
unsigned char 1 8 0, …, 28 – 1 0, …, 255
unsigned short 2 16 0, …, 216 – 1 0, …, 65535
unsigned int 4 32 0, …, 232 – 1 0, …, 4.3 million
unsigned long 8 64 0, …, 264 – 1 0, …, 1.8 trillion
signed char 1 8 –27, …, 27 – 1 –128, …, 127
short 2 16 –215, …, 215 – 1 –32768, …, 32767
int 4 32 –231, …, 231 – 1 –2.15 million, …, 2.15 million
long 8 64 –263, …, 263 – 1 –0.9 trillion, …, 0.9 trillion
Summary
• Following this lesson, you now
• Understand the representation of unsigned integers
• Know how to perform subtraction using 2’s complement
• Similar to 10’s complement used a century ago
• Understand that signed integers store negative numbers in their
2’s complement representation
• Know that char is actually just an integer type
• It can be interpreted as a printable character if necessary
• Understand the ranges stored by char, short, int and long
References
[1] Wikipedia
https://en.wikipedia.org/wiki/Integer_(computer_science)
https://en.wikipedia.org/wiki/Two%27s_complement
FLOATING POINT DATA
TYPES
Scientific notation
• Recall from secondary school scientific notation that
allows us to write numbers clearly and succinctly:
Conventional notation Scientific notation
0.0000000000667408 6.67408 × 10–11
299792458 2.99792458 × 108
0.0000000000000000000000000000000006626070040 6.626070040 × 10–34
0.00000000000000000016021766208 1.6021766208 × 10–19
8.3144598 8.3144598 × 100
3.14159265358979323 3.14159265358979323 × 100
6.67408 × 10–11
Exponent
Mantissa
Base
Scientific notation
• The number of decimal digits used is the precision:
• There are special values for ±∞ for numbers too large to represent
• There are other values for NAN (not-a-number) to represent
calculations such as 0.0/0.0 and ∞ – ∞
• Numbers too small are represented by 0.0
Weaknesses
• This fixed precision leads to some weaknesses
• It can happen that x + y = x even if y ≠ 0
• The calculation x – y can be problematic if x ≈ y
• There is no difference…
Weaknesses
Pi + 1e-10 = 3.1415926536897931
• For example: (Pi + 1e-10) - Pi = 1.000000082740371e-10
#include <iostream>
#include <cmath>
int main();
Pi + 1e-16 = 3.1415926535897931
(Pi + 1e-16) - Pi = 0
int main() {
std::cout.precision( 17 ); // Print floating-point numbers to 17 digits of precision
double x{std::acos(-1.0)};
double y{1e-10};
double z{x + y};
y = 1e-16;
z = x + y;
return 0;
}
Weaknesses
• Subtraction results in a loss of precision
• Suppose we subtract these two numbers:
-001 8414709848079505
sin 1.0000000000001
-001 8414709848078965 sin 1
-014 5400000000000000
d sin 1 h sin 1 h
sin 1 lim
dx h 0 2h
h = 1e-15;
dsin1 = (std::sin( 1.0 + h ) - std::sin( 1.0 - h ))/(2*h);
std::cout << "When h = " << h << ", " << dsin1 << std::endl;
return 0;
} Calculating the derivative of sin(x) at x = 1:
cos(1) = 0.54030230586813977
When h = 1e-10, 0.54030224738710331
When h = 1.0000000000000001e-15, 0.55511151231257827
IEEE 754-2008
• Originally written in 1985, this document specifies the
representations of both float and double
• Examples:
int temperatures[10]; // an array of 10
integers
double voltages[23]; // an array of 23
floating-
// point numbers
Array storage
• An array of 10 int requires 40 bytes
• Each int requires 4 bytes
• The indices of
datatype array_name[n];
always go from 0 to n - 1
Array initialization
• Consider this uninitialized array:
int main() {
double data[4];
return 0;
} The output is
47.2
48.3
48.9
49.4
Array initialization
• If you don’t give enough initial values, the rest are set to
zero:
int main() {
// Sets all entries to 0
double data[4]{};
return 0;
}
example.cpp:6:33: error: too many initializers for 'double [4]'
double data[4]{1, 2, 3, 4, 5};
^
Array entries
• If an array has four entries, those four entries can be
accessed using an index from 0 to 3:
double data[4]; // an array of 4 integers
std::cout << "The average entry is " << average << std::endl;
double maximum{data[0]};
if ( data[1] > maximum ) {
maximum = data[1];
}
if ( data[2] > maximum ) {
maximum = data[2];
}
if ( data[3] > maximum ) {
maximum = data[3];
}
std::cout << "The maximum entry is " << maximum << std::endl;
Array entry assignment
• Each of the ten entries of this array can be assigned a value
int temperature[10]{}; // an array of 10
integers
• Suppose we declare:
bool flags[5]{};
• You can use flags[2] in a logical expression
• You cannot use flags in a logical expression
Looping through an array
• Alternatively, we can loop through an array:
int main() {
double data[4]{25.23, 27.59, 28.10, 28.86};
return 0;
}
return 0;
}
Looping through an array
• Here is another example:
int main() {
double data[4]{25.23, 27.59, 28.10, 28.86};
double maximum{data[0]};
return 0;
}
Array capacities
• The array capacity need not be known at compile time:
int main();
int main() {
std::size_t capacity{};
std::cout << "Enter the number of data points: ";
std::cin >> capacity;
double data[capacity];
int main() {
double data[10]{};
0x7fff2fa3bac0 data[0]
0x7fff2fa3bac8 data[1]
0x7fff2fa3bad0 data[2]
0x7fff2fa3bad8 data[3]
0x7fff2fa3bae0 data[4]
0x7fff2fa3bae8 data[5]
0x7fff2fa3baf0 data[6]
0x7fff2fa3baf8 data[7]
0x7fff2fa3bb00 data[8]
0x7fff2fa3bb08 data[9]
Value of an array variable
• Unlike other local variables/parameters, you cannot
assign to arrays
#include <iostream>
int main();
int main() {
double pi{3.14};
double data[10];
double tmp_array[10];
return 0;
}
Arrays as parameters
• When a function is called, the arguments are evaluated
and copied to the locations for the parameters on the call
stack
• The parameters are variables restricted to the function
• The arguments can be local variables, but they can also be
expressions
int main() {
double x{3.14};
std::cout << std::sin( x ) << std::endl;
std::cout << std::sin( 2*x + 1 ) << std::endl;
return 0;
}
Arrays as parameters
• Recalling these images:
• Suppose main() has three local variables
• The memory for these variables is on the stack
Arrays as parameters
• If main() calls f(...), the arguments are evaluated and
copied to the appropriate locations reserved for the
parameters
Arrays as parameters
• When f(...) is called, additional space for any local
variable for f(...) is also reserved on the stack
• Inside f(...), you can modify the parameters and local, but when
the function exists, those changes are lost
Arrays as parameters
• We can write a function that accepts an array as a
parameter
int main();
double average( double data[4] );
int main() {
// drone speed in m/s
double speeds[4]{178.2, 182.5, 187.1, 191.6};
return sum/4.0;
}
Arrays as parameters
• But what is copied to the parameter?
int main();
void print_array( double array[] );
int main() {
// drone speed in m/s
double speeds[4]{178.2, 182.5, 187.1, 191.6};
std::cout << "Inside main: " << speeds << std::endl;
print_array( speeds );
return 0;
}
return sum/4.0;
}
Arrays as parameters
We will accept an array of any capacity
— as long as they are double
• We can separately pass the capacity:
double average( double data[], std::size_t capacity );
return sum/capacity;
}
Arrays as parameters
• We can now call this average as follows:
int main();
double average( double data[], std::size_t capacity );
int main() {
// drone speed in m/s
double speeds[4]{178.2, 182.5, 187.1, 191.6};
return 0;
}
Arrays as parameters
• Suppose we author and then call this function:
double initialize( double array[], std::size_t capacity );
return 0;
}
void f() {
// 'x' is uninitialized
double x;
std::cout << "f: The unitialized local variable x = " << x << std::endl;
x = 3.14;
std::cout << "f: The assigned local variable x = " << x << std::endl;
}
Exceeding array bounds
int main() {
double data[5]{3.7, 4.0, 2.9, 8.6, 1.5};
f();
std::cout << "main: data[-5] = " << data[-5] << std::endl;
std::cout << "main: Assigning data[-5] the value 2.71828..." << std::endl;
data[-5] = 2.71828;
f();
std::cout << "main: data[-5] = " << data[-5] << std::endl;
return 0;
}
void f() {
// 'x' is uninitialized
double x;
std::cout << "f: The unitialized local variable x = " << x << std::endl;
x = 3.14;
std::cout << "f: The assigned local variable x = " << x << std::endl;
}
Exceeding array bounds
• The output is:
f: The unitialized local variable x = 6.9167e-310
f: The assigned local variable x = 3.14
main: data[-5] = 3.14
main: Assigning data[-5] the value 2.71828...
f: The unitialized local variable x = 2.71828
f: The assigned local variable x = 3.14
main: data[-5] = 3.14
Exceeding array bounds
• How about this program?
#include <iostream>
int main();
int main() {
double data[10]{3.7, 4.0, 2.9, 8.6, 1.5};
Output:
return 0;
Segmentation fault (core dumped)
}
or some other catastrophic error…
– The program execution is terminated
Exceeding array bounds
• The most common error:
void initialize( double array[], std::size_t capacity );
initialize( data, 5 );
return 0;
}
• The initialized memory for the array data is here
initialize( data, 5 );
for ( std::size_t k{1}; k <= capacity; ++k ) {
return 0; array[k] = 0.0;
} }