fpprintprec and leading zeros



On Sun, Dec 29 2013, Robert Dodier wrote:

> On 2013-12-28, Robert Dodier <robert.dodier at gmail.com> wrote:
>
>> The logic for this stuff is found at lines 315--328 in src/commac.lisp
>> (function EXPLODEN). I looked at it for a few minutes but I don't have
>> an easy fix.
>
> Here is a simple-minded patch. Comments welcome. It just sticks a "0"
> on the result if it starts with ".".

Hi Robert,

The problem with that is that it still won't give the correct result for
0.1860.

I don't think that there is an easy way of tackling this problem with
FORMAT tricks, so I wrote up some simple code this morning for float
formatting, included below (yes, I know, maybe I care about this too
much :P).  I believe that when called with EFFECTIVE-PRINTPREC, it would
produce the correct formatting.

If you would be willing to include it in Maxima, I am happy to do some
extensive testing.

--8<---------------cut here---------------start------------->8---
(defun rational-exponent (positive-rational &optional (radix 10))
  "Return the largest integer EXPONENT such that

  (<= (EXPT RADIX EXPONENT) POSITIVE-RATIONAL)

Calculation is based on logarithms, but is corrected to be exact if necessary."
  (check-type radix (integer 2))
  (check-type positive-rational (rational (0)))
  (let ((exponent (truncate (log positive-rational radix)))) ; may not be exact
    (loop
      (let ((lower-limit (expt radix exponent)))
        (cond                           ; correction rarely needed
          ((< positive-rational lower-limit) (decf exponent))
          ((<= (* radix lower-limit) positive-rational) (incf exponent))
          (t (return exponent)))))))

(defun decompose-rational (rational digits &optional (radix 10))
  "Decompose a rational into a sign indicator, an exponent (see
RATIONAL-EXPONENT) and a mantissa (a string of digits), returning these as
values. Uses base RADIX."
  (check-type radix (integer 2))
  (check-type rational rational)
  (let* ((abs (abs rational))
         (exponent (rational-exponent abs radix))
         (mantissa (write-to-string (round (* abs (expt radix (- digits exponent)))
                                           radix)
                                    :base radix :radix nil :escape nil)))
    (values (minusp rational) exponent mantissa)))

(defun float-to-string (float significant-digits
                        &key (radix 10)
                             (min-exponent -7)
                             (max-exponent (min (1- significant-digits) 7))
                             (exponent-marker #\e)
                             (trailing-decimal-dot nil))
  "Return FLOAT printed as STRING with the given number of SIGNIFICANT-DIGITS.

Uses base RADIX, but radix markers are not printed, you have to prepend them.

When (LOG FLOAT RADIX) is between MIN-EXPONENT and MAX-EXPONENT, it is printed
using standard notation, otherwise using scientific notation with the given
EXPONENT-MARKER.

When TRAILING-DECIMAL-DOT, a decimal dot is always printed.

FLOAT is converted to a rational and then rounded to give the required number of
significant digits.  This means that some digits may be zeroed out because of
rounding, especially if MAX-EXPONENT is larger than SIGNIFICANT-DIGITS."
  (check-type significant-digits (integer 1))
  (multiple-value-bind (minusp exponent mantissa)
      (decompose-rational (rationalize float) significant-digits radix)
    (with-output-to-string (stream)
      (labels ((print% (object)
                 (princ object stream))
               (print0 (&optional (n 1))
                 (loop repeat n do (print% #\0)))
               (print-mantissa (dot-position)
                 (cond
                   ((<= dot-position 0)
                    (print% "0.")
                    (print0 (- dot-position))
                    (princ mantissa stream))
                   ((<= (length mantissa) dot-position)
                    (princ mantissa stream)
                    (print0 (- dot-position (length mantissa)))
                    (when trailing-decimal-dot
                      (print% #\.)))
                   (t
                    (print% (subseq mantissa 0 dot-position))
                    (print% #\.)
                    (print% (subseq mantissa dot-position))))))
        (when minusp
          (print% -))
        (cond
          ((<= min-exponent exponent max-exponent)
           (print-mantissa (1+ exponent)))
          (t (print-mantissa 1)
             (print% exponent-marker)
             (print% exponent)))))))

(defun test-float-to-string (float digits &key (min-power -10) (max-power 10))
  "Test FLOAT-TO-STRING, returning a list of strings, multiplying FLOAT by powers
of 10."
  (loop for power from min-power to max-power
        collect (float-to-string (* float (expt 10 power)) digits)))
--8<---------------cut here---------------end--------------->8---

Best,

Tamas