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