On a machine with 16-bit int
s,
the C expression (30000 + 30000)
yields a negative result.
Why the negative result?
How can you make it produce
the correct result?
Since the two numbers in the expression
are treated as int
values,
the overall expression is computed as
an int
value.
In C, an int
is a signed value,
which means that one of the sixteen bits
is effectively used as a sign (+
or -
) bit,
leaving only fifteen bits to represent the magnitude.
The negative result occurs
because 60000 cannot be stored in fifteen bits.
The maximum value is \( \mathtt{7FFF}_{16} = 2^{15} - 1 = 32767 \).
One approach to resolving the problem is
to write the expression as (30000u + 30000u)
,
which will cause C to evaluate the expression
as an unsigned int
value;
unsigned int
s can use
the whole sixteen bits for magnitude,
allowing them to store values
up to \( \mathtt{FFFF}_{16} = 2^{16} - 1 = 65535 \).
Assume that the following hexadecimal values
are 16-bit twos-complement.
Convert each to the corresponding decimal value.
0x0013
0x0444
0x1234
0xffff
0x8000
For the positive values (i.e. , leftmost bit 0),
convert to bits and then,
for any \( i \)th bit which is 1,
sum the \( 2^i \) values.
For the negative values,
convert them (using two-complement)
to their corresponding value,
and then apply the strategy
for positive values.
\( \mathtt{0x0013}
= {0000\,0000\,0001\,0011}_2
= 2^4 + 2^1 + 2^0
= 19 \)
\( \mathtt{0x0444}
= {0000\,0100\,0100\,0100}_2
= 2^{10} + 2^6 + 2^2
= 1092 \)
\( \mathtt{0x1234}
= {0001\,0010\,0011\,0100}_2
= 2^{12} + 2^9 + 2^5 + 2^4 + 2^2
= 4660 \)
\( \mathtt{0xffff}
\Rightarrow -1 \times (\mathtt{0x0000} + 1)
= -1 \)
\( \mathtt{0x8000}
\Rightarrow -1 \times (\mathtt{0x7fff} + 1)
\to -((2^{15}-1)+1)
= -32768 \)
Give a representation
for each of the following decimal values
in 16-bit twos-complement bit-strings.
Show the value in binary, octal and hexadecimal.
1
100
1000
10000
100000
-5
-100
What decimal numbers do the following
single-precision IEEE 754-encoded bit-strings represent?
0 00000000 00000000000000000000000
1 00000000 00000000000000000000000
0 01111111 10000000000000000000000
0 01111110 00000000000000000000000
0 01111110 11111111111111111111111
0 10000000 01100000000000000000000
0 10010100 10000000000000000000000
0 01101110 10100000101000001010000
Each of the above is a single 32-bit bit-string,
but partitioned to show
the sign, exponent and fraction parts.
All values are computed by the formula
$$ \mathsf{sign} \times (1 + \mathsf{frac}) \times 2^{\mathsf{exp} - 127} $$
where
\(\mathsf{sign}\) is 1
if the most significant bit (m.s.b) is 0,
or -1 if the m.s.b is 1
\(\mathsf{exp}\) is determined by
the 8 bits following the sign bit
(as a value in the range 0..255)
\(\mathsf{frac}\) is determined by
the least significant 23 bits
(\( \mathsf{bit}_{22}\times 2^{-1}
+ \mathsf{bit}_{21}\times 2^{-2}
+ \cdots
+ \mathsf{bit}_{1}\times 2^{-22}
+ \mathsf{bit}_{0}\times 2^{-23} \))
\(0.0000
= (1 + 0.0) \times 2^{0 - 127}
= 1.0 \times 2^{-127} \)
(... which is close to zero)
\(-0.0000\)
... same as above, but the sign bit is 1, so -ve
\(1.5
= (1 + 0.5) \times 2^{127-127}
= 1.5 \times 2^0 \)
\(0.5
= (1 + 0.0) \times 2^{126-127}
= 1.0 \times 2^{-1} \)
\(0.999999
= (1 + 0.999999) \times 2^{126-127}
= 1.999999 \times 2^{-1} \)
(... but we're limiting this to the
approximately 7 digits we can represent here)
\(2.75
= (1 + 0.375) \times 2^{128-127}
= 1.375 \times 2^1 \)
\(3145728.00
= (1 + 0.5) \times 2^{148-127}
= 1.5 \times 2^{21} \)
\(0.0000124165
= (1 + 0.627451) \times 2^{110-127}
= 1.627451 \times 2^{-17} \)
(... again, limited to approximately 7 digits)
Convert the following decimal numbers
into IEEE 754-encoded bit-strings:
2.5
0.375
27.0
100.0
We need to first express the number \(k\)
as \( (1+\mathsf{frac}) \times 2^n \).
To work out the fraction,
we divide \( k \) by the largest
\(2^n\) that is smaller than \(k\).
0 10000000 01000000000000000000000
\(= 1.25 \times 2^1
= (1 + 0.25) \times 2^{128-127} \)
0 01111101 10000000000000000000000
\(= 1.5 \times 2^{-2}
= (1 + 0.5) \times 2^{125-127} \)
0 10000011 10110000000000000000000
\(= 1.6875 \times 2^4
= (1 + 0.6875) \times 2^{131-127} \)
where \( 0.6875 = 2^{-1} + 2^{-3} + 2^{-4} \)
0 10000101 10010000000000000000000
\(= 1.5625 \times 2^6
= (1 + 0.5625) \times 2^{133-127} \)
where \( 0.5625 = 2^{-1} + 2^{-4} \)
Write a C function, six_middle_bits
,
which, given a uint32_t
,
extracts and returns the middle six bits.
Draw diagrams to show the difference between
the following two data structures:
struct {
int a ;
float b ;
} x1 ;
union {
int a ;
float b ;
} x2 ;
If x1
was located at &x1 == 0x1000
and x2
was located at &x2 == 0x2000
,
what would be the values of
&x1.a
,
&x1.b
,
&x2.a
, and
&x2.b
?
The struct
contains two separate components.
The union
contains two components
that occupy the same memory space.
&x1.a==0x1000
,
&x1.b==0x1004
,
&x2.a==0x2000
,
&x2.b==0x2000
How large (#bytes) is each of the following C union
s?
union { int a ; int b ; } u1 ;
union { unsigned short a ; char b ; } u2 ;
union { int a ; char b [ 12 ]; } u3 ;
union { int a ; char b [ 14 ]; } u4 ;
union { unsigned int a ; int b ; struct { int x ; int y ; } c ; } u5 ;
You may assume
sizeof(char)
== 1,
sizeof(short)
== 2,
sizeof(int)
== 4.
The size of a union
is the size of its largest variant:
sizeof(u1) == 4
sizeof(u2) == 2
sizeof(u3) == 12
sizeof(u4) == 16
, with padding on string
sizeof(u5) == 8
Note that the above results may vary
depending on the machine architecture
and the compiler.
Consider the following C union
union _all {
int ival ;
char cval ;
char sval [ 4 ];
float fval ;
unsigned int uval ;
};
If we define a variable union _all var;
and assign the following value var.uval = 0x00313233;
,
then what will each of the following printf(3) s produce:
printf("%x\n", var.uval);
printf("%d\n", var.ival);
printf("%c\n", var.cval);
printf("%s\n", var.sval);
printf("%f\n", var.fval);
printf("%e\n", var.fval);
You can assume that bytes are arranged from right-to-left
in increasing address order.
This is just a matter of interpreting the bytes/words in
terms of the appropriate data type:
printf("%x\n", var.uval);
gives 313233
printf("%d\n", var.ival);
gives 3224115
printf("%c\n", var.cval);
gives 3 (based on the byte ordering)
printf("%s\n", var.sval);
gives 321 (based on the byte ordering)
printf("%f\n", var.fval);
gives 0.000000 (actually a number very close to zero)
printf("%e\n", var.fval);
gives 4.517947e-39
The floating-point interpretation here
is a sub-normal value,
which we know because its exponent is all zero.
Notably, the value of a subnormal does not have
a leading one added to its mantissa,
and use an exponent of \( 2^{-126} \).
We don't really cover sub-normal values,
but they're excellent for understanding
the floating-point interpretation rules.
Note also that float
values
are always promoted to double
when passed to a function.
See the C standard for details.
(ISO 9899:2018, §6.5.2.2 ¶6)