Втрата точності за допомогою постійного струму


12

Я хочу використовувати dcдля обробки деяких базових 16 чисел із шістнадцятковими точками, але у мене виникають проблеми з точністю. Наприклад, нижче я множачи F423F.FDна 100, як шестигранник. Очікувана відповідь F423FFD, натомість вона дає F423FFA.E1, близьку, але недостатньо точну навіть після округлення.

$ dc
16 d i o F423F.FD 100 * p
F423FFA.E1

Я читав, що dcце був необмежений калькулятор точності, і це далеко не велика кількість. Чи є щось, що я роблю неправильно?

Дякую за відповіді. З огляду на проблеми dc, я кусав кулю і написав власний аналізатор реальних цифр на інших базах. Якщо когось цікавить код, я можу розмістити його тут.

Відповіді:


6

Зауважте, що тільки надрукуючи оригінальний номер, видно, що він округлений:

$ dc <<<'16 d i o F423F.FD p'
F423F.FA

Ви можете його обійти, додавши багато кінцевих нулів для більшої точності:

$ dc <<<'16 d i o F423F.FD000000 100 * p'
F423FFD.0000000

Дякую. Я думаю, що в кінцевому підсумку знадобиться більше коду для масажу цифр для dcвикористання, а потім просто написати парсер прямо! (Введення може бути, а може і не мати десяткових значень, а може бути і в інших базах, тому кількість прокладки змінюється.)
Yimin Rong

2
Я позначу це як прийняту відповідь. Люди, відповідальні за підтримку, dcвідповіли: Для правильного поводження з недесятковими дробовими цифрами знадобиться зовсім інша модель, ніж модель з десятковою шкалою, що використовується dc і bc (як це продиктовано POSIX для bc, і історична традиція для обох). , настільки технічно це можна було б виправити dc, але це, ймовірно, зламається bc, так що класифікується як WONTFIX.
Імін Ронг

8

Виражений у вигляді десяткових dcзначень (використовуючи для перетворення), це відповідає 999999,98 (округлено вниз) × 256, тобто 255999994,88, що в шістнадцятковому значенні F423FFA.E1.

Таким чином, різниця походить від dcповедінки округлення: замість обчислення 256 × (999999 + 253 ÷ 256), що дало б 255999997, вона округляє 253 ÷ 256 і зменшує результат.

dc- це довільний калькулятор точності, що означає, що він може обчислити до будь-якої потрібної точності, але ви повинні сказати, що це таке. За замовчуванням його точність дорівнює 0, тобто поділ дає лише цілі значення, а множення використовує кількість цифр у введенні. Щоб встановити точність, використовуйте k(і майте на увазі, що точність завжди виражається десятковими цифрами, незалежно від вхідного чи вихідного радіусів):

10 k
16 d i o
F423FFD 100 / p
F423F.FD0000000
100 * p
F423FFD.000000000

(Точності 8 цифр буде достатньо, оскільки саме це вам потрібно представити 1 ÷ 256 у десятковій частині.)


1
Це може здатися абсолютно несподіваним результатом для калькулятора "довільної точності"?
Імін Ронг

3
Він все одно втрачає точність, коли kвстановлено: 10 k 16 d i o F423F.FD pF423F.FA, тому мені доведеться масштабувати всі числа перед тим, як використовувати їх dc. По суті, це попередній аналіз їх у будь-якому випадку.
Імін Ронг

2
@Yimin так, на жаль, dcмасштабує свій вхід, використовуючи лише кількість цифр, що мені здається помилкою (оскільки кількість цифр обчислюється за допомогою вхідного радіусу, але застосовується до десяткового значення).
Стівен Кітт

1
@dhag саме те, що POSIX визначає (для bcчого dcґрунтується): "Внутрішні обчислення проводяться так, як у десятковій формі , незалежно від вхідних та вихідних баз, до визначеної кількості десяткових цифр."
Стівен Кітт

1
Це справді проблема того, як розбирається константа. Спробуйте 20 k 16 d i o 0.3 1 / p (які друкує .19999999999999999). Зрозумійте, що операція просто ділиться 0.2на 1(що теоретично не повинно змінювати значення). Поки 20 k 16 d i o 0.3000 1 / p(правильно) друкує .30000000000000000. (
Проти

1

Питання

Проблема полягає в тому, як 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

Було показано, що кожен двійковий дріб - це десятковий дріб з однаковою кількістю цифр.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.