Bill McCarthy wrote:
> Ben, Bram & Tony,
> 
> I sent the following to the list yesterday, but forgot to
> copy you - the recipients of the initial draft of this
> function.  I've enhanced it by making it more customizable -
> something the default fp output should be.

The original still hasn't appeared in my inbox. Maybe it didn't send or
something. At any rate, I've been fiddling with this too, for a couple
of days, and was making these comments and counter-proposal which I was
just about to send when your newer function arrived. I've now revised
them in light of your later version.

I do like the idea of having more precision in the default display. I
think it shouldn't output what is invalid in Vim syntax, except +-inf
and nan, which can be easily special-cased (unlike e.g. 1e+20).

Unfortunately though Bill, your approach, though simple, is not actually
accurate: log10 will not always give the accurate result you
desire/assume, and rounding can screw up the number of significant
digits after you decide upon it from an unrounded value also. There is
also the underflow case, where less digits are represented in the double
when the exponent gets very negative. You also neglect negative zero. A
few examples, using your later version:

     :" Inaccurate due to underflow
     :echo PG(1.0e-323)
     9.88131291682e-324
     :" Negative zero incorrect
     :echo PG(-0.0)
     0
     :" Too many digits
     :echo PG(999999999999.5)
     1000000000000

After much thinking, testing and multiple attempts, I did figure out a
way to reliably print with maximum accuracy, including taking into
account the underflow case. But it's far from simple. I did it by
processing the output of %.15e which can at least reliably generate
digits and return them in a consistent form, and then doing all the
rounding and final display mechanics in the function. The many
subtleties and unknowns regarding different library and processor
implementations made this preferable. The rounding used is like round().
'Round to even' rounding like %g uses isn't feasible, nor all that
useful in a display routine.

It naturally has an adjustable number of significant figures; I also
chose 12 as a default! It's big enough to show that you're not using
single precision floats, but not the maximum so that a little roundoff
error won't cause nasty display. It also has adjustable thresholds for
when to use scientific notation for both the lower and higher ends. My
defaults are below 0.001 and at or above 10000000; counting digits gets
tiresome when there are more than about 7, and it's nice to be able to
represent down to thousandths, i.e. one lower metric magnitude, without
using scientific notation. It also allows fully accurate scientific
notation by teaching it that trailing zeros without a decimal point do
not count as significant figures--this is not the default, though, as
most people would prefer to see 1000, not 1e3. This only has an effect,
of course, when Vim syntax is turned off anyway, which is another
option, ensuring all floats output have a decimal place, and optionally,
even output +-inf and nan as valid Vim expressions, so you could use
this output to build expressions if desired. Trimming of trailing zeros
is also able to be turned on and off.

I don't always show a sign in my exponent, and I don't have an option to
omit a single zero preceding the decimal point. This would be pretty
easy to add (easier than most stuff I've added!).

The globals that are used are not nicely named yet, and the
documentation is still a little slim, but it's well on the way and, most
importantly, I think it is always accurate. Here are some examples with
the default settings:

     :echo PrintFloat(1.0e-323)
     0.1e-322
     :echo PrintFloat(-0.0)
     -0.0
     :echo PrintFloat(999999999999.5)
     1.0e12

I have avoided using regexes for the ease of translating the routine
into C. I can do this if Bram is open to having such a routine used for
the default display of floats in Vim. I think it should have hardcoded
parameters in that case, not all the flexibility I have built into this
one, unless one added Vim option or something (comma separated
parameters perhaps).

As it's 150 lines long, I've attached it. I have tested it fairly
thoroughly manually, but it could do with some more testing and some
automated testing, but this isn't all that easy to arrange either.

Smiles,

Ben.




--~--~---------~--~----~------------~-------~--~----~
You received this message from the "vim_dev" maillist.
For more information, visit http://www.vim.org/maillist.php
-~----------~----~----~----~------~----~------~--~---

" g:sigfigs : (1:15) number of significant figures (where possible)
let g:sigfigs = 12
" g:scientificbelow : (<=0) exponent below which to use scientific notation
let g:scientificbelow = -3
" g:scientificabove : (>=-1) exponent above which to use scientific notation
let g:scientificabove = 6
" g:zerossig : whether trailing zeros with no decimal point are significant
let g:zerossig = 1
" g:vimsyntax : 0 : nicely human readable
"               1 : valid Vim float syntax, except for +-inf and nan
"               >1 : valid Vim float syntax
let g:vimsyntax = 1
" g:trim : whether or not to trim trailing zeros where possible
let g:trim = 1

func! PrintFloat(f)
  " Ensure f is a float.
  let f = type(a:f) == 5 ? a:f : str2float(a:f)
  " Special cases.
  if f == 1.0 / 0.0
    return g:vimsyntax > 1 ? '(1.0 / 0.0)' : 'inf'
  elseif f == -1.0 / 0.0
    return g:vimsyntax > 1 ? '(-1.0 / 0.0)' : '-inf'
  elseif f != f
    return g:vimsyntax > 1 ? '(0.0 / 0.0)' : 'nan'
  endif
  " Ensure precision is valid.
  let sigfigs = g:sigfigs > 15 ? 15 : (g:sigfigs < 1 ? 1 : g:sigfigs)
  let above = g:scientificabove
  let below = g:scientificbelow
  if g:vimsyntax
    if above >= sigfigs - 1
      let above = sigfigs - 2
    elseif above < 0
      let above = -1
    endif
  else
    if g:zerossig && above >= sigfigs
      let above = sigfigs - 1
    elseif above < 0
      let above = -1
    endif
  endif
  let below = (below > 0 ? 0 : below)
  " Get one more digit of precision than can be accurately represented,
  " so that the rounding can be fully under our control. We should always
  " get a number of the form [+-]?\d\.\d\{15}e[+-]?\d\+
  let printed = printf("%.15e",f)
  " Parse the result into mantissa and exponent, count the number of
  " trailing nines so we know how far we have to carry if rounding up,
  " and trailing zeros so we know how much to omit if rounding down,
  " and the next digit after we have reached the precision we want, so we
  " can determine which way to round.
  let mantissa = ""
  let exponent = ""
  let nines = 0
  let zeros = 0
  let next = 0
  let figs = 0
  let neg = 0
  let i = 0
  let l = strlen(printed)
  while i < l && figs < sigfigs
    if printed[i] == '-' | let neg = 1 | endif
    if printed[i] >= '0' && printed[i] <= '9'
      if printed[i] == '9'
        let nines += 1
        let zeros = 0
      elseif printed[i] == '0'
        let zeros += 1
        let nines = 0
      else
        let nines = 0
        let zeros = 0
      endif
      let mantissa .= printed[i]
      let figs += 1
    endif
    let i += 1
  endwhile
  if printed[i] == '.' | let i += 1 | endif
  let next = printed[i]
  while i < l && printed[i] != "e"
    let i += 1
  endwhile
  let i += 1
  while i < l
    if printed[i] != "+"
      let exponent .= printed[i]
    endif
    let i += 1
  endwhile
  let exponent = str2nr(exponent)
  " Reduce significant figures if we've underflown.
  if exponent < -308 && sigfigs > exponent + 324
    let sigfigs = exponent + 324
    let figs = 0
    while figs < sigfigs
      if mantissa[figs] == '9'
        let nines += 1
        let zeros = 0
      elseif mantissa[figs] == '0'
        let zeros += 1
        let nines = 0
      else
        let nines = 0
        let zeros = 0
      endif
      let figs += 1
    endwhile
    let next = mantissa[figs]
    if sigfigs == 0 || (next >= 5 && nines == sigfigs)
      let mantissa = '0'.mantissa
      let sigfigs += 1
      let exponent += 1
    endif
  endif
  " Round the number appropriately.
  if next >= 5 && nines == sigfigs
    let digits = '1'.repeat('0',sigfigs-1)
    let exponent += 1
    let zeros = sigfigs-1
    echo mantissa
    echo zeros
    echo nines
  elseif next >= 5
    if sigfigs-nines-2 >=0
      let digits = mantissa[0:sigfigs-nines-2]
    else
      let digits = ""
    endif
    let digits .= (mantissa[sigfigs-nines-1]+1) . repeat('0',nines)
    let zeros = nines
  else
    let digits = mantissa
  endif
  " Determine which notation to use and output the number.
  if exponent < below || exponent > above
    \ || (! g:zerossig && sigfigs - exponent - 1 == 0)
    if sigfigs == 1
      if g:vimsyntax
        let digits = '0' . digits
        let exponent += 1
        let sigfigs += 1
        if zeros > 0 | let zeros -= 1 | endif
      endif
    endif
    if ! g:trim
      let zeros = 0
    elseif zeros >= sigfigs - 1
      if g:vimsyntax
        return (neg?'-':"") . digits[0] . ".0" . "e" . exponent
      else
        return (neg?'-':"") . digits[0] . "e" . exponent
      endif
    endif
    return (neg?'-':"") . digits[0] . "." . digits[1:sigfigs-zeros-1] .
                          \ "e" . exponent
  else
    while exponent < 0
      let digits = '0' . digits
      let exponent += 1
      let sigfigs += 1
      if zeros > 0 | let zeros -= 1 | endif
    endwhile
    if exponent > sigfigs - 1
      return (neg?'-':"") . digits[0:sigfigs - 1] .
                          \ repeat('0',exponent-sigfigs+1)
    endif
    if ! g:trim
      let zeros = 0
    elseif zeros >= sigfigs - exponent - 1
      if g:vimsyntax || ! g:zerossig
        return (neg?'-':"") . digits[0:exponent] . ".0"
      else
        return (neg?'-':"") . digits[0:exponent]
      endif
    endif
    return (neg?'-':"") . digits[0:exponent] . "." .
                          \ digits[exponent+1:sigfigs-zeros-1]
  endif
endfunc

" double range with decimal precision: 1e-323 1.79769313486232e+308

Raspunde prin e-mail lui