The problem is the way in which dc (and bc) understand numeric constants.
For example, the value (in hex)
0.3 (divided by 1) gets transformed into a value close to
$ dc <<<"20k 16 d i o 0.3 1 / p"
In fact, the plain constant
0.3 also gets changed:
$ dc <<<"20 k 16 d i o 0.3 p"
It seems that it is in an odd way, but it is not (more later).
Adding more zeros makes the answer approach the correct value:
$ dc <<<"20 k 16 d i o 0.30 p"
$ dc <<<"20 k 16 d i o 0.300 p"
$ dc <<<"20 k 16 d i o 0.3000 p"
The last value is exact and will stay exact no matter how may more zeros are added.
$ dc <<<"20 k 16 d i o 0.30000000 p"
The problem is also present in bc:
$ bc <<< "scale=20; obase=16; ibase=16; 0.3 / 1"
$ bc <<< "scale=20; obase=16; ibase=16; 0.30 / 1"
$ bc <<< "scale=20; obase=16; ibase=16; 0.300 / 1"
$ bc <<< "scale=20; obase=16; ibase=16; 0.3000 / 1"
One digit per bit?
The very non intuitive fact for floating point numbers is that the number of digits required (after the dot) is equal to the number of binary bits (also after the dot). A binary number 0.101 is exactly equal to 0.625 in decimal. The binary number 0.0001110001 is (exactly) equal to
0.1103515625 (ten decimal digits)
$ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
Also, for a floating point number like 2^(-10), which in binary has only one (set) bit:
$ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
Has the same number of binary digits
.0000000001 (10) as decimal digits
.0009765625 (10). That may not be the case in other bases but base 10 is the internal representation of numbers in both dc and bc and therefore is the only base that we really need to care about.
The math proof is at the end of this answer.
The number of digits after the dot could be counted with the built-in function
scale() form bc:
$ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
As shown, 2 digits is insufficient to represent the constant
And, also, just counting the number of characters used after the dot is a very incorrect way to report (and use) the scale of the number. The scale of a number (in any base) should calculate the number of bits needed.
Binary digits in a hex float.
As it is known, each hex digit use 4 bits. Therefore, each hex digit after the decimal dot require 4 binary digits, which due to the (odd?) fact above also require 4 decimal digits.
Therefore, a number like
0.FD will require 8 decimal digits to be represented correctly:
$ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
The math is straightforward (for hex numbers):
- Count the number of hex digits (
h) after the dot.
h by 4.
h×4 - h = h × (4-1) = h × 3 = 3×h zeros.
In shell code (for sh):
a=$a$(printf '%0*d' $((3*h)) 0)
echo "obase=16;ibase=16;$a*100" | bc
echo "20 k 16 d i o $a 100 * p" | dc
Which will print (correctly both in dc and bc):
$ sh ./script
Internally, bc (or dc) could make the number of digits required match the number calculated above (
3*h) to convert hex floats to the internal decimal representation. Or some other function for other bases (assuming the number of digits is finite in relation to base 10 (internal of bc and dc) in such other base). Like 2i (2,4,8,16,...) and 5,10.
The posix specification states that (for bc, which dc is based on):
Internal computations shall be conducted as if in decimal, regardless of the input and output bases, to the specified number of decimal digits.
But "… the specified number of decimal digits." could be understood to be " … the needed number of decimal digits to represent the numeric constant" (as described above) without affecting the "decimal internal computations"
bc <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
bc is not really using 50 ("the specified number of decimal digits") as set above.
Only if divided it is converted (still incorrectly as it uses an scale of 2 to read the constant
0.FD before expanding it to 50 digits):
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
However, this is exact:
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
Again, reading numeric strings (constants) should use the correct number of bits.
In two steps:
A binary fraction can be written as a/2n
A binary fraction is a finite sum of negative powers of two.
= 0.00110101101 =
= 0. 0 0 1 1 0 1 0 1 1 0 1
= 0 + 0×2-1 + 0×2-2 + 1×2-3 + 1×2-4 + 0×2-5 + 1×2-6 + 0×2-7 + 1×2-8 + 1×2-9 + 0×2-10 + 1×2-11
= 2-3 + 2-4 + 2-6 + 2-8 + 2-9 + 2-11 = (with zeros removed)
In a binary fraction of n bits, the last bit has a value of 2-n, or 1/2n. In this example: 2-11 or 1/211.
= 1/23 + 1/24 + 1/26 + 1/28 + 1/29 + 1/211 = (with inverse)
In general, the denominator could become 2n with a positive numerator exponent of two. All terms can then be combined into a single value a/2n. For this example:
= 28/211 + 27/211 + 25/211 + 23/211 + 22/211 + 1/211 = (expressed with 211)
= (28 + 27 + 25 + 23 + 22 + 1 ) / 211 = (extracting common factor)
= (256 + 128 + 32 + 8 + 4 + 1) / 211 = (converted to value)
= 429 / 211
Every Binary Fraction Can Be Expressed As b/10n
Multiply a/2n by 5n
/5n, getting (a×5n)/(2n×5n) = (a×5n)/10n = b/10n, where b = a×5n. It has n digits.
For the example, we have:
(429·511)/1011 = 20947265625 / 1011 = 0.20947265625
It has been shown that every binary fraction is a decimal fraction with the same number of digits.