How Many Digits to Write?
Recently, my colleague Rob Comer and I were talking about how to write out a number, in decimal, so that if it were read back into MATLAB, would retain its full precision. The question is how many digits to write out. The number depends on several things, including the datatype the value is stored in. In addition, it may depend on the precision of the value - i.e., was it data collected during an experiment in which only two significant figures were recorded? Today I'll post about the solution Rob and I came up with for choosing the number of digits so if you write out the data as a string, you can read it back in to MATLAB with full precision retained.
Contents
Create Some Values
Let's first create some values, both single and double versions of pi.
format long g dblpi = pi snglpi = single(pi)
dblpi = 3.14159265358979 snglpi = 3.141593
Figure Out Number of Digits
To figure out the number of digits to print, we need to know what the floating point accuracy, sometimes called eps for the number of interest.
eps(snglpi) eps(dblpi)
ans = 2.384186e-007 ans = 4.44089209850063e-016
As makes sense, we can see that the accuracy of the single precision value is larger than that for the "equivalent" double precision value. That means that the number next closest to the single precision value is farther away than the number next closest to the double precision value.
Number of Digits
We can use eps(x) to help us figure out how many digits to print after the decimal place. First find total number of digits, base 10:
log10(eps(snglpi)) log10(eps(dblpi))
ans = -6.62266 ans = -15.352529778863
To get to a positive number of digits, simply negate the results.
-log10(eps(snglpi)) -log10(eps(dblpi))
ans = 6.62266 ans = 15.352529778863
And round up to get make sure we don't miss any accuracy.
ceil(-log10(eps(snglpi))) ceil(-log10(eps(dblpi)))
ans = 7 ans = 16
Let's convert the results to a string. We are taking advantage of the ability to control the number of digits using * in sprintf.
snglpistr = sprintf('%.*f', ceil(-log10(eps(snglpi))), snglpi) dblpistr = sprintf('%.*f', ceil(-log10(eps(dblpi))), dblpi)
snglpistr = 3.1415927 dblpistr = 3.1415926535897931
Now we've captured each value so if written out as a string, and read back into MATLAB, the accuracy is preserved.
Convert to a Function
Taking what we know for finding the number of digits, let's make a function that we can use to test it out.
digits = @(x) ceil(-log10(eps(x)));
printdigs = @(x) sprintf('%.*f', digits(x), x);
Try Some Values
printdigs(pi) printdigs(2/3) printdigs(1000*pi) printdigs(pi/1000)
ans = 3.1415926535897931 ans = 0.6666666666666666 ans = 3141.5926535897929 ans = 0.0031415926535897933
Some Magic Now
Rob created the necessary magic for getting rid of trailing zeros after the decimal point, while leaving at least one digit to the right of the decimal.
x = 1/2000 str = printdigs(x) strout = stripzeros(str)
x = 0.0005 str = 0.0005000000000000000 strout = 0.0005
Let's try some more values. First create a function to help us again.
strippedStringValues = @(x) stripzeros(printdigs(x)); vals = [ 100/289, -1/17, 1/2000, 0, 500 -200, 123.4567] for k = vals strippedStringValues(k) end
vals = Columns 1 through 2 0.346020761245675 -0.0588235294117647 Columns 3 through 4 0.0005 0 Columns 5 through 6 500 -200 Column 7 123.4567 ans = 0.34602076124567471 ans = -0.058823529411764705 ans = 0.0005 ans = 0.0 ans = 500.0 ans = -200.0 ans = 123.4567
Here's the magic code for stripping the zeros, for those who are interested.
dbtype stripzeros
1 function str = stripzeros(strin) 2 %STRIPZEROS Strip trailing zeros, leaving one digit right of decimal point. 3 % Remove trailing zeros while leaving at least one digit to the right of 4 % the decimal place. 5 6 % Copyright 2010 The MathWorks, Inc. 7 8 str = strin; 9 n = regexp(str,'\.0*$'); 10 if ~isempty(n) 11 % There is nothing but zeros to the right of the decimal place; 12 % the value in n is the index of the decimal place itself. 13 % Remove all trailing zeros except for the first one. 14 str(n+2:end) = []; 15 else 16 % There is a non-zero digit to the right of the decimal place. 17 m = regexp(str,'0*$'); 18 if ~isempty(m) 19 % There are trailing zeros, and the value in m is the index of 20 % the first trailing zero. Remove them all. 21 str(m:end) = []; 22 end 23 end
How Do You Control Printed Digits?
Are you able to just use the default printing from MATLAB for your values? Do you use disp, leave off the semi-colon (;), use one of the *printf functions? What customizations do you need to make to print out values? Let me know here.
- Category:
- Numerical Accuracy,
- Strings