sicp-ex-2.1



<< Previous exercise (1.46) | Index | Next exercise (2.2) >>


  
 ;; ex 2.1 
  
 (define (numer x) (car x)) 
  
 (define (denom x) (cdr x)) 
  
 (define (print-rat x) 
   (newline) 
   (display (numer x)) 
   (display "/") 
   (display (denom x))) 
  
 (define (make-rat n d) 
   (let ((g ((if (< d 0) - +) (gcd n d)))) 
     (cons (/ n g) (/ d g)))) 
  
 ;; Testing 
 (print-rat (make-rat 6 9)) ; 2/3 
 (print-rat (make-rat -6 9)) ; -2/3 
 (print-rat (make-rat 6 -9)) ; -2/3 
 (print-rat (make-rat -6 -9)) ; 2/3 
  

There is a bug in the solution above. If gcd is defined as described in 1.2.5, it will have sign depending on the number of iterations it runs and the signs of a and b. For example:

(gcd 1 -2) ; 1
(gcd 6 -9) ; -3

Thus:

(print-rat (make-rat 1 -2)) ; 1/-2
(print-rat (make-rat 6 -9)) ; -2/3

To fix either make gcd return an absolute value or get the absolute value in make-rat (this is implemented below):

(define (make-rat n d)
  (let ((g ((if (< d 0) - +) (abs (gcd n d)))))
    (cons (/ n g) (/ d g))))

category-learning-scheme category-texts


Comment on how the above solution works:

n d g n/g d/g rat
+ + +  +   +   +
- + +  -   +   -
+ - -  -   +   -
- - -  +   +   +

If we don't want to consider the sign of gcd, and still get the right answer, we could implement the following:

(define (make-rat n d)
  (define g (gcd n d))
  (cond ((> (* n d) 0) (cons (abs (/ n g)) (abs (/ d g))))
        ((< (* n d) 0) (cons (- (abs (/ n g))) (abs (/ d g))))))

Lily X. :)


We could save the simplified numbers as variables to make the program more readable too

 (define (make-rat n d) 
         (define g (gcd n d)) 
         (let        ((simple-n (abs (/ n g))) 
                      (simple-d (abs (/ d g)))) 
                 (cond 
                         ((> (* n d) 0) (cons simple-n simple-d)) 
                         ((< (* n d) 0) (cons (- simple-n) simple-d))))) 

Another solution follows:

 (define (make-rat n d) 
   (define (sign x) (if (< x 0) - +)) 
   (let ((g (gcd n d))) 
     (cons ((sign d) (/ n g)) 
               (abs (/ d g))))) 

Evan


Here's a solution that doesn't use conditionals.

 (define (make-rat n d) 
   (let ((g (abs (gcd n d))) 
        (abs-d (abs d))) 
     (cons (/ (* n (/ d abs-d)) g) 
           (/ abs-d g)))) 

Let g take the absolute value of the gcd expression, so its sign won't interfere with the final sign of n and d.

Then, when you give cons, as its first argument, the multiplication of n by (/ d abs-d), you get the following:

Giving abs-d to cons as its second argument removes the possible sign from the denominator.


I really don't see why all the solutions here should look as cluttered as they do.

We just define sign as

 (define (bool->number x) 
         (cond ((equal? x #t) 1) 
               ((equal? x #f) 0) 
               (else "Input must be a boolean!") 
         ) 
 ) 
  
 (define (sign x) 
         (- (bool->number (> x 0)) 
            (bool->number (< x 0)) 
         ) 
 ) 

And then make-rat as

 (define (make-rat n d) 
         (if (= d 0) (error "The denonimator must not be zero!")) 
          
         (define (rat-change x) 
                 (/ (* x (sign d)) (gcd n d)) 
         ) 
          
         (cons (rat-change n) (rat-change d )) 
 ) 

Entropy Donary

A little redundant but I think very readable:

  
 (define (make-rat n d) 
   (if (< d 0) 
       (make-rat (- n) (- d)) 
       (let ((g (abs (gcd n d)))) 
         (cons (/ n g) (/ d g))))) 

master

I also agree that the solutions here are generally too complicated. There are only two cases: The denominator is negative, in which case you should flip the sign of both the numerator and denominator, or the denominator is positive, in which case you do nothing. The solution above recognizes this fact but the solution is still very roundabout, although in a way sort of clever. Here's a straightforward solution:

 (define (make-rat n d) 
   (let ((g (gcd n d))) 
     (if (negative? d)  
         (cons (/ (- n) g) (/ (- d) g)) 
         (cons (/ n g) (/ d g)))))