lib/decimals.ss
(module decimals mzscheme

  (require
   (lib "string.ss" "srfi" "13")
   (lib "contract.ss"))

  (provide/contract
   [number->decimal-string
    ;; convert a number into a decimal string
    (number? . -> . any)]
   [number->format-decimal
    ;; convert a number into a formatted decimal string
    ((flat-named-contract "<Real or Rational>"
      (lambda (x)
        (or (inexact? x) (integer? x) (real? x))))
     natural-number/c
     . ->d .
     (lambda (_ n) ;; is the decimal point at the right place in the string
       (lambda (str)
         (and (string? str)
              (char=? (string-ref str (- (string-length str) n 1)) #\.)))))]
   [integer->format-decimal
    ;; convert a number into a formatted decimal string
    (integer?
     natural-number/c
     . ->d .
     (lambda (_ n) ;; is the decimal point at the right place in the string
       (lambda (str)
         (and (string? str)
              (char=? (string-ref str (- (string-length str) n 1)) #\.)))))])


  ;; Number N -> String
  ;; turn the number x into a string with num digits after the decimal point
  (define (number->format-decimal x num)
    (if (integer? x)
      (integer->format-decimal x num)
      (let* ([str (number->decimal-string x)]
             [split (regexp-match "([0-9]*)\\.([0-9]*)" str)])
        (cond
          ((pair? split)
           (format "~a.~a" (cadr split)
                   (string-pad-right (caddr split) num #\0)))
          (else
            (error 'number->format-decimal
                   "whoops, can't handle this: ~a -> ~a"
                   x split))))))

  ;; Integer -> String
  ;; As above, but just for integers.
  (define (integer->format-decimal n num)
    (format "~a.~a" n (make-string (inexact->exact num) #\0)))

  ; ---------------------------------------------------------------------------
  ; Number -> String
  ; turns a number into a string with decimal representation
  ; Matthew's code
  (define (number->decimal-string x)
    (cond
      [(or (inexact? x) (integer? x)) (number->string x)]
      [(not (real? x)) ;; complex
       (let ([r (real-part x)]
             [i (imag-part x)])
         (format "~a~a~ai"
                 (number->decimal-string r)
                 (if (negative? i) "" "+")
                 (number->decimal-string i)))]
      [else
       (let ([n (numerator x)]
             [d (denominator x)])
         ;; Count powers of 2 in denomintor
         (let loop ([v d][2-power 0])
           (if (and (positive? v) (even? v))
               (loop (arithmetic-shift v -1) (add1 2-power))
               ;; Count powers of 5 in denominator
               (let loop ([v v][5-power 0])
                 (if (zero? (remainder v 5))
                     (loop (quotient v 5) (add1 5-power))
                     ;; No more 2s or 5s. Anything left?
                     (if (= v 1)
                         ;; Denominator = (* (expt 2 2-power) (expt 5 5-power)).
                         ;; Print number as decimal.
                         (let* ([10-power (max 2-power 5-power)]
                                [scale (* (expt 2 (- 10-power 2-power))
                                          (expt 5 (- 10-power 5-power)))]
                                [s (number->string (* (abs n) scale))]
                                [orig-len (string-length s)]
                                [len (max (add1 10-power) orig-len)]
                                [padded-s (if (< orig-len len)
                                              (string-append
                                               (make-string (- len orig-len) #\0)
                                               s)
                                              s)])
                           (format "~a~a.~a"
                                   (if (negative? n) "-" "")
                                   (substring padded-s 0 (- len 10-power))
                                   (substring padded-s (- len 10-power) len)))
                         ;; d has factor(s) other than 2 and 5.
                         ;; Print as a fraction.
                         (number->string x)))))))])))