;;;
;;; Name: imath-test.scm
;;; Purpose: Code to generate random rational number test cases.
;;; Notes: Written for DrRacket (nee PLT Scheme)
;;;
(require (lib "27.ss" "srfi"))
;; Generate a random natural number with the specified number of digits.
(define (random-big-natural digits)
(let loop ((d "") (digits digits))
(if (zero? digits)
(string->number d 10)
(let ((rnd (random 10)))
(loop (string-append d (list->string
(list
(integer->char
(+ rnd
(char->integer #\0))))))
(- digits 1))))))
;; Generate a random integer with the specified number of digits and
;; probability (0..1) of being negative.
(define (random-big-integer digits pneg)
(let ((base (random-big-natural digits)))
(if (< (random-real) pneg)
(* base -1)
base)))
;; Generate a random rational number with the specified number of numerator and
;; denominator digits, and probability pneg (0..1) of being negative.
(define (random-big-rational n-digits d-digits pneg)
(let ((num (random-big-natural n-digits))
(den (random-big-natural d-digits)))
(if (zero? den)
(random-big-rational n-digits d-digits pneg)
(if (< (random-real) pneg)
(- (/ num den))
(/ num den)))))
;; Create a rational generator with a fixed negative probability.
;; Always generates rationals.
(define (make-rat-generator prob-neg)
(lambda (n-digits d-digits num)
(random-big-rational n-digits d-digits prob-neg)))
;; Create a rational generator with a fixed negative probability. With
;; probability prob-backref, generates a back-reference to an earlier input
;; value, rather than a new value. This is used to make sure argument
;; overlapping works the way it should.
(define (make-backref-generator prob-neg prob-backref)
(lambda (n-digits d-digits num)
(if (and (> num 1)
(< (random-real) prob-backref))
(let ((ref (+ (random (- num 1)) 1)))
(string-append "=" (number->string ref)))
(random-big-rational n-digits d-digits prob-neg))))
;; Just like make-backref-generator, except the second argument is always an
;; integer, and the backreference can only be to the first argument.
(define (make-backref-generator-2 prob-neg prob-backref)
(lambda (n-digits d-digits num)
(case num
((1) (random-big-rational n-digits d-digits prob-neg))
((2) (random-big-integer n-digits prob-neg))
(else
(if (< (random-real) prob-backref)
"=1"
(random-big-rational n-digits d-digits prob-neg))))))
(define (make-output-test-generator prob-neg max-dig)
(lambda (n-digits d-digits num)
(cond ((= num 1)
(random-big-rational n-digits d-digits prob-neg))
((= num 2)
(let loop ((radishes '(10 16 8 4 2)))
(cond ((null? radishes)
(+ (random 34) 2))
((< (random-real) 0.3)
(car radishes))
(else
(loop (cdr radishes))))))
(else
(random max-dig))
)))
;; Given a test name, an argument generator, and an operation to compute the
;; desired solution, return a function that generates a random test case for a
;; given number of digits of precision in the numerator and denominator.
(define (make-test-case-generator name arg-gen op)
(lambda (n-digits d-digits)
(let ((args (list (arg-gen n-digits d-digits 1)
(arg-gen n-digits d-digits 2)
(arg-gen n-digits d-digits 3))))
(let* ((arg1 (car args))
(arg2 (if (equal? (cadr args) "=1")
arg1 (cadr args)))
(soln (if (and (eq? op /)
(zero? arg2))
"$MP_UNDEF"
(op arg1 arg2))))
(list
name
args
(list soln))))))
;; Glue strings together with the specified joiner.
(define (join-strings joiner lst)
(cond ((null? lst) "")
((null? (cdr lst)) (car lst))
(else
(string-append (car lst) joiner
(join-strings joiner (cdr lst))))))
;; Convert a test case generated by a test case generator function into a
;; writable string, in the format used by imtest.c
(define (test-case->string tcase)
(let ((s (open-output-string))
(stringify (lambda (v)
(let ((s (open-output-string)))
(display v s)
(get-output-string s)))))
(display (car tcase) s)
(display ":" s)
(display (join-strings "," (map stringify (cadr tcase)))
s)
(display ":" s)
(display (join-strings "," (map stringify (caddr tcase)))
s)
(get-output-string s)))
(define qadd (make-test-case-generator
'qadd (make-backref-generator 0.3 0.2) +))
(define qsub (make-test-case-generator
'qsub (make-backref-generator 0.3 0.2) -))
(define qmul (make-test-case-generator
'qmul (make-backref-generator 0.3 0.2) *))
(define qdiv (make-test-case-generator
'qdiv (make-backref-generator 0.3 0.2) /))
(define qtodec (make-test-case-generator
'qtodec (make-output-test-generator 0.3 25)
(lambda (a b) '?)))
(define qaddz (make-test-case-generator
'qaddz (make-backref-generator-2 0.3 0.2) +))
(define qsubz (make-test-case-generator
'qsubz (make-backref-generator-2 0.3 0.2) -))
(define qmulz (make-test-case-generator
'qmulz (make-backref-generator-2 0.3 0.2) *))
(define qdivz (make-test-case-generator
'qdivz (make-backref-generator-2 0.3 0.2) /))
(define (write-test-cases test-fn lo-size hi-size num-each fname)
(let ((out (open-output-file fname)))
(do ((num lo-size (+ num 1)))
((> num hi-size) (void))
(do ((den hi-size (- den 1)))
((< den lo-size) (void))
(do ((ctr 1 (+ ctr 1)))
((> ctr num-each) (void))
(display (test-case->string (test-fn num den)) out)
(newline out))))
(close-output-port out)))
(define (write-lots-of-tests)
(write-test-cases qadd 1 20 2 "qadd.tc")
(write-test-cases qsub 1 20 2 "qsub.tc")
(write-test-cases qmul 1 20 2 "qmul.tc")
(write-test-cases qdiv 1 20 2 "qdiv.tc")
(write-test-cases qtodec 1 20 2 "qtodec.tc")
(write-test-cases qaddz 1 20 2 "qaddz.tc")
(write-test-cases qsubz 1 20 2 "qsubz.tc")
(write-test-cases qmulz 1 20 2 "qmulz.tc")
(write-test-cases qdivz 1 20 2 "qdivz.tc"))