In addition to ChrisWue's comments ChrisWue's comments, I'd add:
In addition to ChrisWue's comments, I'd add:
In addition to ChrisWue's comments, I'd add:
The comment for
Rational_Construct
says that it returns:A Rational object X such that X.Top == Numerator and X.Bottom = Denominator.
but in fact this is not true, since the denominator may be negated. Instead it should say something like:
A Rational object R such that R.Top/R.Bottom == Numerator/Denominator.
- Your operator implementations don't call
Rational_Construct
: they just build a newRational
structure however they please. This means that the consistency checks inRational_Construct
(such as making sure that the denominator is positive) are skipped.
It would result in shorter and more reliable code if you always called Rational_Construct
. For example:
Rational Rational_Negate(const Rational R) {
return Rational_Construct(-R.Top, R.Bottom);
}
Rational Rational_Add(const Rational Left, const Rational Right) {
return Rational_Construct(Left.Top * Right.Bottom + Right.Top * Left.Bottom,
Left.Bottom * Right.Bottom);
}
Even small computations with your rationals result in integer overflow. For example, consider the following program:
int main(int argc, char **argv) { Rational sixteenth = Rational_Construct(1, 16); Rational sum = Rational_Construct(0, 1); int i; for (i = 0; i < 10; ++i) { sum = Rational_Add(sum, sixteenth); printf("%d/%d\n", sum.Top, sum.Bottom); } return 0; }
Now, because you're using C's fixed-size integers, there are bound to be computations whose results are too large to be represented. But it would be better to raise an error when the result is too large, rather than causing undefined behaviour. And you should at the very least make some effort to ensure that integer overflow doesn't happen in small examples like this: when we add 1/16 ten times we ought to be able to get 10/16.
An easy way to avoid integer overflow in small examples is to always reduce rationals to their lowest terms (thus turning 2/4 as 1/2). For example, you could do this:
#include <assert.h> /* for the assert() prototype */
#include <limits.h> /* for INT_MIN */
#include <stdlib.h> /* for the abs() prototype */
Rational Rational_Construct(int Numerator, int Denominator) {
/* Ensure that Denominator is positive */
assert(Denominator != 0);
if (Denominator < 0) {
assert(Numerator != INT_MIN);
assert(Denominator != INT_MIN);
Numerator = -Numerator;
Denominator = -Denominator;
}
/* Find the greatest common divisor of Numerator and Denominator. */
int a = abs(Numerator), b = Denominator;
while (b) {
int c = a % b;
a = b;
b = c;
}
/* Reduce the fraction to lowest terms. */
Rational newRational;
newRational.Top = Numerator / a;
newRational.Bottom = Denominator / a;
return newRational;
}
Now the program I gave above prints:
1/16
1/8
3/16
1/4
5/16
3/8
7/16
1/2
9/16
5/8
(Of course this doesn't solve the integer overflow problem in the general case, but at least it allows small programs like this to complete successfully.)
Even small computations with your rationals result in integer overflow. For example, consider the following program:
int main(int argc, char **argv) { Rational sixteenth = Rational_Construct(1, 16); Rational sum = Rational_Construct(0, 1); int i; for (i = 0; i < 10; ++i) { sum = Rational_Add(sum, sixteenth); printf("%d/%d\n", sum.Top, sum.Bottom); } return 0; }
Now, because you're using C's fixed-size integers, there are bound to be computations whose results are too large to be represented. But it would be better to raise an error when the result is too large, rather than causing undefined behaviour. And you should at the very least make some effort to ensure that integer overflow doesn't happen in small examples like this: when we add 1/16 ten times we ought to be able to get 10/16.
The comment for
Rational_Construct
says that it returns:A Rational object X such that X.Top == Numerator and X.Bottom = Denominator.
but in fact this is not true, since the denominator may be negated. Instead it should say something like:
A Rational object R such that R.Top/R.Bottom == Numerator/Denominator.
- Your operator implementations don't call
Rational_Construct
: they just build a newRational
structure however they please. This means that the consistency checks inRational_Construct
(such as making sure that the denominator is positive) are skipped.
It would result in shorter and more reliable code if you always called Rational_Construct
. For example:
Rational Rational_Negate(const Rational R) {
return Rational_Construct(-R.Top, R.Bottom);
}
Rational Rational_Add(const Rational Left, const Rational Right) {
return Rational_Construct(Left.Top * Right.Bottom + Right.Top * Left.Bottom,
Left.Bottom * Right.Bottom);
}
Even small computations with your rationals result in integer overflow. For example, consider the following program:
int main(int argc, char **argv) { Rational sixteenth = Rational_Construct(1, 16); Rational sum = Rational_Construct(0, 1); int i; for (i = 0; i < 10; ++i) { sum = Rational_Add(sum, sixteenth); printf("%d/%d\n", sum.Top, sum.Bottom); } return 0; }
Now, because you're using C's fixed-size integers, there are bound to be computations whose results are too large to be represented. But it would be better to raise an error when the result is too large, rather than causing undefined behaviour. And you should at the very least make some effort to ensure that integer overflow doesn't happen in small examples like this: when we add 1/16 ten times we ought to be able to get 10/16.
An easy way to avoid integer overflow in small examples is to always reduce rationals to their lowest terms (thus turning 2/4 as 1/2). For example, you could do this:
#include <assert.h> /* for the assert() prototype */
#include <limits.h> /* for INT_MIN */
#include <stdlib.h> /* for the abs() prototype */
Rational Rational_Construct(int Numerator, int Denominator) {
/* Ensure that Denominator is positive */
assert(Denominator != 0);
if (Denominator < 0) {
assert(Numerator != INT_MIN);
assert(Denominator != INT_MIN);
Numerator = -Numerator;
Denominator = -Denominator;
}
/* Find the greatest common divisor of Numerator and Denominator. */
int a = abs(Numerator), b = Denominator;
while (b) {
int c = a % b;
a = b;
b = c;
}
/* Reduce the fraction to lowest terms. */
Rational newRational;
newRational.Top = Numerator / a;
newRational.Bottom = Denominator / a;
return newRational;
}
Now the program I gave above prints:
1/16
1/8
3/16
1/4
5/16
3/8
7/16
1/2
9/16
5/8
(Of course this doesn't solve the integer overflow problem in the general case, but at least it allows small programs like this to complete successfully.)
In addition to ChrisWue's comments, I'd add:
In this block of code:
if (Numerator < 0 && Denominator < 0) { Numerator = -Numerator; Denominator = -Denominator; } else if (Numerator >= 0 && Denominator < 0) { Numerator = -Numerator; Denominator = -Denominator; }
both branches do the same thing, so you could combine them into one condition:
if (Numerator < 0 && Denominator < 0 || Numerator >= 0 && Denominator < 0) {
Numerator = -Numerator;
Denominator = -Denominator;
}
Now both parts of the condition have Denominator < 0
, so you could write:
if ((Numerator < 0 || Numerator >= 0) && Denominator < 0) {
Numerator = -Numerator;
Denominator = -Denominator;
}
Now it's clear that the condition Numerator < 0 || Numerator >= 0
is always true, so it can be dropped, leaving:
if (Denominator < 0) {
Numerator = -Numerator;
Denominator = -Denominator;
}
In the comment for
Rational_Construct
you specify a preconditionDenominator != 0
. But if the caller passes in zero, then you just print a message to standard output. It would be better to write:assert(Denominator != 0)
so that the program aborts. Having rationals with denominator 0 leads to many sorts of trouble: for example the rational 0/0 compares equal to every other rational.
Even small computations with your rationals result in integer overflow. For example, consider the following program:
int main(int argc, char **argv) { Rational sixteenth = Rational_Construct(1, 16); Rational sum = Rational_Construct(0, 1); int i; for (i = 0; i < 10; ++i) { sum = Rational_Add(sum, sixteenth); printf("%d/%d\n", sum.Top, sum.Bottom); } return 0; }
The program looks as if it is supposed to add 1/16 ten times, getting the result 10/16. But it actually has undefined behaviour due to signed integer overflow. On my computer it prints:
1/16
32/256
768/4096
16384/65536
327680/1048576
6291456/16777216
117440512/268435456
-2147483648/0
0/0
0/0
Now, because you're using C's fixed-size integers, there are bound to be computations whose results are too large to be represented. But it would be better to raise an error when the result is too large, rather than causing undefined behaviour. And you should at the very least make some effort to ensure that integer overflow doesn't happen in small examples like this: when we add 1/16 ten times we ought to be able to get 10/16.