I can't provide a good solution to the problem, but I'll try to describe in depth what the problem is and I'll provide a partial solution.
The problem:
Floating point numbers on machines suffer from limited precision: in short, only a limited subset of floating point numbers [per each order of magnitude] is representable.
Floating point numbers on machines are represented closely following the normalized notation ± significand * base ^ exponent
(where base
= base of representation, significand
= any real number > 0 and <= the base of representation and where exponent
= order of magnitude): for example, on a 32-bit machine following the IEEE 754
standard, single-precision floating point numbers are represented using the first bit to represent the sign, the following 8 bits to represent the order of magnitude and the last 23 bits to represent the significand, while double-precision floating point numbers are represented using the first bit to represent the sign, the following 11 bits to represent the order of magnitude and the last 52 bits to represent the significand (the base, being always 2, is not represented). For this, a number's significand has to be represented always using 23 bits (using single-precision) or using 52 bits (using double-precision).
A property of this way of representing floating point numbers on a fixed number of bits is that being the number of representable significands per order of magnitude always the same, the average "distance" between representable floating point numbers with the same order of magnitude increases as the order of magnitude of the two increases.
For the above, the first problem is that if a floating point number's normalized notation's significand is not in the limited set of representable significands, it's rounded to the closest (higher or lower) significand representable.
Speaking of numbers represented with the same order of magnitude, a second problem is that even when a floating point number is representable precisely, adding / substracting another [precisely representable] floating point number to it might result in a not precisely representable floating point number, whose significand will be rounded to the closest (higher or lower) significand representable.
Finally, speaking of numbers represented with a different order of magnitude, the third problem (mostly due to the CPU architecture) is that in order to be able to perform additions / substractions between floating point numbers represented with a different order of magnitude, the numbers need to be first represented using the same order of magnitude; this implies that the smallest one's order of magnitude needs to be increased, and that (in order to balance this) its significand needs to be shifted to the right, with the consequent loss of the number of bits exceeding the 23 / 52 available; if this is not enough, floating point numbers with a significant difference in their order of magnitude might result, once added / substracted, exactly in the number with the highest absolute value, this for the already stated problem (not enough difference to step the non representable significand up / down to a different higher / lower representable significand) and increasingly worse as the order of magnitude of two numbers diverges further.
The implications of all of this are: you'll never be sure to get an accurate result using floating point math, however this can be mitigated by using an higher-precision representation.
The partial solution:
For the above, the results of these awk
one-liners are not precise; this could have been mitigated by the usage of double-precision in their printf
commands, but this is not supported.
This will decrease by 30
the value of the first 3 space-separated numbers in each line after the first line matching C
, keeping the numbers' format. Since the awk
version included in Ubuntu does not support in-place edits, you'll have to either use awk
and redirect its stdout
to a file using bash
's >
operator or use gawk
(GNU awk
) >= 4.10.0
;
Using awk
:
awk 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30}' data.txt > data_processed.txt
Using gawk
(GNU awk
) >= 4.10.0
gawk -i inplace 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30}' data.txt
NR==1, $0=="C";
: selects and prints all the records between the first and the first matching C
inclusive;
$0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30}
: selects all the records between the first matching C
and the last inclusive and prints the 1st, 2nd and 3rd field of each selected record not matching C
double-space separated and decreased by 30
keeping the original number's format;
Sample output:
~/tmp$ cat data.txt
TITLE
1.000000000000000
10.0000000000000000 0.0000000000000000 0.0000000000000000
0.0000000000000000 10.0000000000000000 0.0000000000000000
0.0000000000000000 0.0000000000000000 10.0000000000000000
U U
X X
C
0.2000000000000028 0.2000000000000028 0.2000000000000028
0.2967599999999990 0.0641000000000034 0.1551499999999990
0.1033699999999982 0.3361099999999979 0.244990000000001
~/tmp$ awk 'NR==1, $0=="C"; $0=="C", 0 {if ($0!="C") printf "%.16f %.16f %.16f\n", $1-30, $2-30, $3-30}' data.txt
TITLE
1.000000000000000
10.0000000000000000 0.0000000000000000 0.0000000000000000
0.0000000000000000 10.0000000000000000 0.0000000000000000
0.0000000000000000 0.0000000000000000 10.0000000000000000
U U
X X
C
-29.7999999999999972 -29.7999999999999972 -29.7999999999999972
-29.7032400000000010 -29.9358999999999966 -29.8448500000000010
-29.8966300000000018 -29.6638900000000021 -29.7550099999999986
0.0000000000000030
?