Питання
Проблема полягає в тому, як 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, і тому є єдиною базою, про яку нам дійсно потрібно піклуватися.
Математичний доказ знаходиться в кінці цієї відповіді.
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 i (2,4,8,16, ...) та 5,10.
поз
Специфікація 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
використання, а потім просто написати парсер прямо! (Введення може бути, а може і не мати десяткових значень, а може бути і в інших базах, тому кількість прокладки змінюється.)