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))))) 


LisScheSic

I give one summary of the above comments:

Comment on how the above solution works:

This is about the 1st solution

If we don't want to consider the sign of gcd ...
We could save the simplified numbers as variables to make the program more readable too

IMHO this is one straightforward translation of what the exercise says. The 1st solution is deciding whether to change the signs of both the numerator and denominator based on denominator instead of "the rational number".

Another solution follows:

Translation of what the exercise says by keeping the denominator positive.

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

just another way to implement "sign" based on its last comment.

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

Same as the 1st solution but manipulating with "the numerator and denominator" instead of `g`. IMHO this `sign` implementation is not readable although it works.

A little redundant but I think very readable:

Same as the 1st solution but uses `(- n) (- d)` to manipulate the signs of both "the numerator and denominator" argument of `cons`.

master's comment is same as this but without one redundant self call.