Потеря точности с постоянным током

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
meuh
источник
Благодарю. Я думаю, что в конечном итоге потребуется больше кода для массажа чисел, которые dcможно использовать, а затем просто написать парсер напрямую! (Входные данные могут иметь или не иметь десятичную дробь, и могут быть в других базах, поэтому величина заполнения варьируется.)
Иминь Ронг
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. (Продолжение)
NotAnUnixNazi
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 и, следовательно, является единственной базой, о которой нам действительно нужно заботиться.

Математическое доказательство находится в конце этого ответа.

шкала до н.э.

Количество цифр после точки можно посчитать с помощью встроенной функции 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

Было показано, что каждая двоичная дробь является десятичной дробью с одинаковым количеством цифр.

NotAnUnixNazi
источник