Проблема
Проблема в том, как dc (и bc) понимают числовые константы.
Например, значение (в шестнадцатеричном формате) 0.3
(разделенное на 1) преобразуется в значение, близкое к0.2
$ dc <<<"20k 16 d i o 0.3 1 / p"
.199999999999999999999999999
Фактически, простая константа 0.3
также изменяется:
$ dc <<<"20 k 16 d i o 0.3 p"
.1
Кажется, что это странно, но это не так (подробнее позже).
Добавление большего числа нулей приводит к правильному значению ответа:
$ dc <<<"20 k 16 d i o 0.30 p"
.2E
$ dc <<<"20 k 16 d i o 0.300 p"
.2FD
$ dc <<<"20 k 16 d i o 0.3000 p"
.3000
Последнее значение является точным и останется точным независимо от того, как можно добавить больше нулей.
$ dc <<<"20 k 16 d i o 0.30000000 p"
.3000000
Проблема также присутствует в BC:
$ bc <<< "scale=20; obase=16; ibase=16; 0.3 / 1"
.19999999999999999
$ bc <<< "scale=20; obase=16; ibase=16; 0.30 / 1"
.2E147AE147AE147AE
$ bc <<< "scale=20; obase=16; ibase=16; 0.300 / 1"
.2FDF3B645A1CAC083
$ bc <<< "scale=20; obase=16; ibase=16; 0.3000 / 1"
.30000000000000000
Одна цифра на бит?
Весьма неинтуитивный факт для чисел с плавающей запятой состоит в том, что количество требуемых цифр (после точки) равно количеству двоичных битов (также после точки). Двоичное число 0,101 точно равно 0,625 в десятичном виде. Двоичное число 0.0001110001 (точно) равно 0.1103515625
(десятичным десятичным цифрам)
$ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
.625000000000000000000000000000
.110351562500000000000000000000
.1234567890
Кроме того, для числа с плавающей запятой типа 2 ^ (- 10), которое в двоичном коде имеет только один (установленный) бит:
$ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
.0000000001000000000000000000000000000000000000000000000000000000000
.00097656250000000000
Имеет такое же количество двоичных цифр .0000000001
(10), что и десятичные цифры .0009765625
(10). Это может быть не так в других базах, но база 10 является внутренним представлением чисел в dc и bc и, следовательно, является единственной базой, о которой нам действительно нужно заботиться.
Математическое доказательство находится в конце этого ответа.
шкала до н.э.
Количество цифр после точки можно посчитать с помощью встроенной функции scale()
вида bc:
$ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
2
.FA
FA.E1
Как показано, 2 цифры недостаточно для представления константы 0.FD
.
И, кроме того, просто подсчет количества символов, используемых после точки, является очень неправильным способом сообщить (и использовать) масштаб числа. Шкала числа (в любой базе) должна вычислять количество необходимых битов.
Двоичные цифры в шестнадцатеричном формате.
Как известно, каждая шестнадцатеричная цифра использует 4 бита. Поэтому каждая шестнадцатеричная цифра после десятичной точки требует 4 двоичных разряда, что из-за (нечетного) факта выше также требует 4 десятичных разряда.
Следовательно, для подобного числа 0.FD
потребуется 8 десятичных цифр, которые будут представлены правильно:
$ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
8
.98828125
253.00000000
Добавить нули
Математика проста (для шестнадцатеричных чисел):
- Подсчитайте количество шестнадцатеричных цифр (
h
) после точки.
- Умножьте
h
на 4.
- Добавьте
h×4 - h = h × (4-1) = h × 3 = 3×h
нули.
В шелл-коде (для sh):
a=F423F.FD
h=${a##*.}
h=${#h}
a=$a$(printf '%0*d' $((3*h)) 0)
echo "$a"
echo "obase=16;ibase=16;$a*100" | bc
echo "20 k 16 d i o $a 100 * p" | dc
Который напечатает (правильно как в dc, так и в bc):
$ sh ./script
F423F.FD000000
F423FFD.0000000
F423FFD.0000000
Внутренне bc (или dc) может привести к тому, что требуемое количество цифр будет соответствовать числу, вычисленному выше ( 3*h
) для преобразования шестнадцатеричных чисел во внутреннее десятичное представление. Или какая-то другая функция для других баз (при условии, что число цифр конечно относительно базы 10 (внутренней от bc и dc) в такой другой базе). Как 2 я (2,4,8,16, ...) и 5,10.
POSIX
В спецификации posix говорится, что (для bc, на котором основан dc):
Внутренние вычисления должны проводиться как в десятичном виде, независимо от входных и выходных баз, до указанного количества десятичных цифр.
Но «... указанное количество десятичных цифр». можно понимать как «… необходимое количество десятичных цифр для представления числовой константы» (как описано выше) без влияния на «внутренние десятичные вычисления»
Так как:
bc <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
1.FA
bc на самом деле не использует 50 («указанное количество десятичных цифр»), как указано выше.
Только если оно разделено, оно преобразуется (по-прежнему неверно, так как использует шкалу 2 для считывания константы 0.FD
перед ее расширением до 50 цифр):
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
.FAE147AE147AE147AE147AE147AE147AE147AE147A
Однако это точно:
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
.FD0000000000000000000000000000000000000000
Снова, чтение числовых строк (констант) должно использовать правильное количество битов.
Математическое доказательство
В два этапа:
Двоичная дробь может быть записана как / 2 n
Бинарная дробь - это конечная сумма отрицательных степеней двойки.
Например:
= 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 = (с удалением нулей)
В двоичной дроби из n битов последний бит имеет значение 2 -n или 1/2 n . В этом примере: 2 -11 или 1/2 11 .
= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 = (с обратным)
В общем, знаменатель может стать 2 n с показателем положительного числа в два. Все члены могут быть затем объединены в одно значение a / 2 n . Для этого примера:
= 2 8 /2 11 + 2 7 /2 11 + 2 5 /2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 = (выраженное с 2 11 )
= (2 8 + 2 7 + 2 5 + 2 3 + 2 2 + 1) / 2 11 = (извлечение общего множителя)
= (256 + 128 + 32 + 8 + 4 + 1) / 2 11 = (преобразовано в значение)
= 429/2 11
Каждая бинарная фракция может быть выражена как b / 10 n
Умножим a / 2 n на 5 n
/ 5 n , получив (a × 5 n ) / (2 n × 5 n ) = (a × 5 n ) / 10 n = b / 10 n , где b = a × 5 n , Он имеет n цифр.
Для примера имеем:
(429 · 5 11 ) / 10 11 = 20947265625/10 11 = 0,20947265625
Было показано, что каждая двоичная дробь является десятичной дробью с одинаковым количеством цифр.
dc
можно использовать, а затем просто написать парсер напрямую! (Входные данные могут иметь или не иметь десятичную дробь, и могут быть в других базах, поэтому величина заполнения варьируется.)