tsh73
Senior Member
    
member is offline


Gender: 
Posts: 932
|
 |
saving number as string WITH PRECISION
« Thread started on: Oct 29th, 2009, 09:50am » |
|
The problem: sometimes we have to convert number to string. Will it be just string containing many different values to be used as object Or just number to be saved to the file in order to be read later. So, JB supplies two nice and easy functions: val(x$) and str$(x). Now, where is the problem? Let's see. Code:
x = 1.2345
x$ = str$(x)
x1=val(x$)
print x, x$, x-x1
results: Code: Everything looks OK. Now, let's take something more interesting. (as you should remember, 1/3 in decimal is infinite periodic fraction, that is 0.3333333333333 ... ad infinum) Code:
x = 1/3
x$ = str$(x)
x1=val(x$)
print x, x$, x-x1
results: Code:
0.33333333 0.33333333 0.33333333e-8
If we get rid of conversion and just put that long fraction themselves Code:
x = 1/3
x1=0.3333333333333333333
print x, x-x1
we'll get: Code:
0.33333333 -0.55511151e-16
That is, then converting to string and back, we lost precision from 16 significant digits to just 8 (from double precision to single, pardon my Klatchian QBasic).
What function is responcible, val() or str$()? Obviously str$() is guilty - it show us only 8 '3's, not 16. Check if val() works ok: Code:
x = 1/3
x$ = "0.3333333333333333333"
x1=val(x$)
print x, x$, x-x1
results: Code:
0.33333333 0.3333333333333333333 -0.55511151e-16
Indeed, given long string, val() returns full supported precision.
So, in order to save and restore number to / from string, we need replacement for str$().
Take 1 Wait! We already have a function that returns more decimal. It is using. Let's try that: Code:
x = 1/3
x$ = using("#.###############",x)
x1=val(x$)
print x, x$, x-x1
results: Code:
0.33333333 0.333333333333333 0.33306691e-15
It seems that with 15 '#' we got all right digits ('3'), if adding more '#' we just getting garbage (unrelated digits). (well, I do not know how to explain this but with 18 '#' or more x-x1 prints as 0. Probably we should use 18 '#' then?)
But we used format string exactly tailored to our number. What happens if we multiply it to 1000000? Code:
x = 1/3*1000000
x$ = using("#.###############",x) '15 #
x1=val(x$)
print x, x$, x1, x-x1, (x-x1)/x
Now I add to output to see what we get as converted value (x1) and how difference relates to x (relative error. It should be around 1e-15.) results: Code:
333333.333 %333333.333333333311488 0 333333.333 1.0
It breaks - because number did not fit to format (too big), JB added '%' in front, and that made val() return 0. Total failure, data lost. Well, can't it be helped? We can just chage #.############### to #######.############### Result will became Code:
333333.333 333333.333333333311488 333333.333 0.0 0.0
- that is, converted value exactly equal source one. (but pay attention to last digits of x$ printed: obviously garbage). This again was modification for this particular case. As you should remember, JB handles real numbers up to somewhere 10^307. So to make it work for 1/3*10^100, we need to add 100 '#'? Indeed. But will it work? Code:
fmt$ = ""
for i = 1 to 308
fmt$ = fmt$ +"#"
next
x = 1/3*10^100
x$ = using(fmt$+".###############",x) '15 #
x1=val(x$)
print x, trim$(x$), x1, x-x1, (x-x1)/x
Indeed it does work. Results are: Code:
3.33333333e99 3333333333333333020656266301054156018810300136799977219037663452567373199587417942557784766487504288.335158099050496 3.33333333e99 0.0 0.0
Of that very long number only first 16 digits valid, rest is garbage. And indeed if we will save it to file, it will be there taking space. (just not nice).
Take 2 Actually we can shorten our program a bit. Instead of busily building 308 '#' we can allow error happen then strip that %. I'll show it for 1/3*1000000, just because that 10^100 string is too long ;) (and I'll use 18 '#' after decimal point). Code:
x = 1/3*1000000
x$ = using("#.##################",x) '18 #
if left$(x$, 1) = "%" then x$ = mid$(x$,2) 'strip first %
x1=val(x$)
print x, trim$(x$), x1, x-x1, (x-x1)/x
Results are Code:
333333.333 333333.333333333260632064 333333.333 0.58207661e-10 0.17462298e-15
- realtive error ~1e-15, so we consider it working. And all the garbage still here (it's just not as much as for 10e100). So, can we live with it? Probably NO: if we try to put really small number, like 1/3*1e-20, using will return bunch of 0's. Code:
x = 1/3*1e-20
x$ = using("#.##################",x) '18 #
if left$(x$, 1) = "%" then x$ = mid$(x$,2) 'strip first %
x1=val(x$)
print x, trim$(x$), x1, x-x1, (x-x1)/x
Result: Code:
0.33333333e-20 0.000000000000000000 0 0.33333333e-20 1.0
Total failure, data lost.
Take 3 So, are we doomed? ;) Not quite. There is a function here Scientific format in printing what will save us: Code:
x = 1/3*10^100
x$ = usingS$(x,15)
x1=val(x$)
print x, trim$(x$), x1, x-x1, (x-x1)/x
x = 1/3*1e-20
x$ = usingS$(x,15)
x1=val(x$)
print x, trim$(x$), x1, x-x1, (x-x1)/x
function usingS$(n,prec)
if n = 0 then usingS$="0e+0":exit function
fmt$ = "#"+left$(".",prec>0)+left$("#################",prec) 'fmt of mantissa
s$=left$("-",n<0)
n=abs(n)
log10=log(n)/log(10)
e=int(log10)-(log10<0) 'QB like INT. Makes mantissa for negative exponents start from digit (not 0 as JB do)
p=10^e
if left$(using(fmt$,n/p),1)="%" then p=p*10:e = e+1
usingS$=s$+using(fmt$,n/p) +"e"+left$("+",e>0) +str$(e) 'Excel always shows "+" for exponent
End function
Results: Code:
3.33333333e99 3.333333333333333e+99 3.33333333e99 0.0 0.0
0.33333333e-20 3.333333333333333e-21 0.33333333e-20 0.0 0.0
Additional bonus - no garbage digits in output.
Hope it will be of use to someone ;)
|
| « Last Edit: Oct 29th, 2009, 09:55am by tsh73 » |
Logged
|
Q: "And if I took your codes and compile them, and sell them for a profit"? A: Go ahead. I had my share of good then I coded it for fun, if you can make better use of it - please do.
|
|
|