2011-09-19

SICP Exercise 2.1: More Rational Rational Numbers

Define a better version of make-rat that handles both positive and negative arguments. Make-rat should normalize the sign so that if the rational number is positive, both the numerator and denominator are positive, and if the rational number is negative, only the numerator is negative.

Here's the original make-rat:
(define (make-rat n d)
  (let ((g (gcd n d)))
    (cons (/ n g) (/ d g))))
Before we start into changing this, first let's define what we mean by a positive versus a negative rational number. It's all in the signs... A rational number is positive if the numerator and denominator have the same sign and negative if the numerator and denominator have different signs.

So if the arguments to make-rat are either both positive or both negative then we want make-rat to convert both the numerator and denominator to their absolute values. If, on the other hand, the arguments have different signs then we want make-rat to ensure that the numerator has a negative sign and converts the denominator to its absolute value.

Looking at the denominator first, we always want that to be converted to its absolute value. This can be easily achieved by applying abs to the passed denominator argument, d.

As for the numerator, we know we can change the sign of a number by multiplying it by -1 and that multiplying a number by 1 leaves the sign unchanged. Let's look at what we'd have to multiply the numerator by in the various cases to get the effect we want:

Numerator SignDenominator SignDesired Numerator SignMultiplicand
+ve+ve+ve1
-ve-ve+ve-1
-ve+ve-ve1
+ve-ve-ve-1

As you can see, the multiplicand we want to use corresponds to the sign of the denominator. Handily there's a standard mathematical function, the sign, or signum, function, that will return 1, 0 or -1 depending upon whether the argument is positive, zero or negative. So we can apply this to the denominator and multiply the numerator by the result.

Unfortunately signum is not a standard scheme function. DrRacket, the interpreter I'm using, has a predefined procedure, sng, that provides this functionality. If you're using something else then you can define it as:
(define (sgn x)
  (if (= x 0)
      0
      (/ (abs x) x)))
Assuming we've got sgn to hand we can then define our updated make-rat as:
(define (make-rat n d)
  (let ((g (gcd n d)))
    (cons (/ (* n (sgn d)) g) (/ (abs d) g))))
And then, assuming we've also got print-rat and the supporting procedures from section 2.1.1, we can put it into practice:
> (print-rat (make-rat 4 5))
4/5
> (print-rat (make-rat 4 -5))
-4/5
> (print-rat (make-rat -4 5))
-4/5
> (print-rat (make-rat -4 -5))
4/5

No comments:

Post a Comment