Почему AWK печатает «0xffffffffbb6002e0» как «ffffffffbb600000» с помощью printf?

Вы можете использовать вспомогательную переменную для создания имени целевой переменной, а затем использовать косвенное обращение к переменной:

zmx_var=zmx_$1
echo -e "${!zmx_var} $tag $1${zmx_no_color}: $line"
23
11.05.2021, 10:12
3 ответа

Числа в AWK по умолчанию являются числами с плавающей запятой -, и ваше значение превышает доступную точность. 0xffffffffbb6002e0в конечном итоге представляется как 0 10000111110 1111111111111111111111111111111101110110110000000000в формате IEEE -754 binary64(double -Precision ), который представляет целочисленное значение 0xffffffffbb600000. Обратите внимание на изменение младших 12 бит, округленное до нуля.

Наименьшее положительное целое число, при преобразовании которого в doubleполучается любая ошибка округления, равно 2 53 + 1. Чем больше число, тем больший разрыв между значениями может представлять double. (Шаги 2, затем 4, затем 8 и т. д.; вот почему младшие шестнадцатеричные цифры вашего числа округляются до нуля.)


При использовании GAWK, если он построен с помощью MPFR и MP (, как в случае с Debian ), вы можете принудительно установить произвольную точность вместо этого с помощью параметра -M:

$ awk -M 'BEGIN { x=0xffffffffbb6002e0; printf("%x\n", x); }'
ffffffffbb6002e0

Для вычислений по умолчанию будет использоваться та же 53-битная точность, что и для IEEE -754 double, но для управления этим параметром можно использовать переменную PREC. Подробные сведения см. в руководстве по ссылке выше.

Существует разница в обработке больших целых чисел и -значений точек с плавающей запятой, требующих большей точности, чем стандартная, что может привести к неожиданному поведению; большие целые числа анализируются правильно с -Mи его настройками по умолчанию (PREC)влияет только на последующие вычисления, тогда как значения с плавающей запятой -сохраняются с точностью, определенной во время их анализа, что означает PRECдолжен быть установлен соответствующим образом заранее:

# Default settings, integer value too large to be exactly represented by a binary64
$ awk 'BEGIN { v=1234567890123456789; printf "%.20f\n", v }'
1234567890123456768.00000000000000000000
# Forced arbitrary precision, same integer value stored exactly without rounding
$ awk -M 'BEGIN { v=1234567890123456789; printf "%.20f\n", v }'
1234567890123456789.00000000000000000000
# Default settings, floating-point value requiring too much precision
$ awk 'BEGIN { v=123456789.0123456789; printf "%.20f\n", v }'
123456789.01234567165374755859
# Forced arbitrary precision, floating-point parsing doesn’t change
$ awk -M 'BEGIN { v=123456789.0123456789; printf "%.20f\n", v }'
123456789.01234567165374755859
# Forced arbitrary precision, PREC set in the BEGIN block, no difference
$ awk -M 'BEGIN { PREC=94; v=123456789.0123456789; printf "%.20f\n", v }'
123456789.01234567165374755859
# Forced arbitrary precision, PREC set initially
$ awk -M -vPREC=94 'BEGIN { v=123456789.0123456789; printf "%.20f\n", v }'
123456789.01234567890000000000

При чтении входных значений AWK распознает только десятичные значения как числа; для обработки не -десятичных значений (восьмеричных или шестнадцатеричных )полей следует обрабатывать с помощью функции GAWK strtonum.

38
28.07.2021, 11:33

Чтобы преобразовать строку (, которая выглядит как число )в awk:

  1. Его можно присвоить переменной как программной константе.
  2. Функция strtonum()могла преобразовать текст.
  3. Awk можно было вызывать с параметром -n(, который теперь считается устаревшим ).

После преобразования в число в большинстве awk (gawk, mawk, nawk, bawk )оно сохраняется как 64-битное число с плавающей запятой. Эти числа могут включать только 53 бита мантиссы. Любые дополнительные биты усекаются. Это допускает 53/4 = 13 шестнадцатеричных цифр (ну, технически, 1 как целое число и 13 цифр после точки ).

Вы использовали шестнадцатеричное число 0xffffffffbb6002e0в двоичном формате:

bc <<<"obase=2;ibase=16;FFFFFFFFBB6002E0"
1111111111111111111111111111111110111011011000000000001011100000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<== up to here 53 bits.

Все дробные числа и большинство целых чисел в awk хранятся как числа с плавающей запятой. Единственный другой вариант с GNU awk — это использование произвольной точности, вариант -M. Использование этой опции означает, что сразу же все целые числа представляются таким количеством цифр, которое необходимо и позволяет память компьютера.

$ awk -M 'BEGIN{print 3^4^5}'
373391848741020043532959754184866588225409776783734007750636931722079040617265251229993688938803977220468765065431475158108727054592160858581351336982809187314191748594262580938807019951956404285571818041046681288797402925517668012340617298396574731619152386723046235125934896058590588284654793540505936202376547807442730582144527058988756251452817793413352141920744623027518729185432862375737063985485319476416926263819972887006907013899256524297198527698749274196276811060702333710356481

Это позволит без проблем использовать ваше целое число, если оно используется только в вычислениях с другими целыми числами. Нет деления.

$ awk -M 'BEGIN{x=strtonum(0xffffffffbb6002e0); y=x+234; z=x/77; printf("%x\n%x\n%f\n",x,y,z)}'
ffffffffbb6002e0
ffffffffbb6003ca
239568104838418400.000000

Правильный результат из x/77должен быть 239568104838418388.36363636363636363636в соответствии с bc.

Если вам нужны числа с дробной частью, для которых требуется более 53 бит (, что является точностью, сохраняемой даже с -M), вам нужно сделать переменную PRECбольше 53 по мере необходимости:

$ awk -M -vPREC=200 'BEGIN{x=strtonum(0xffffffffbb6002e0); y=x+234; z=x/77; printf("%x\n%x\n%f\n",x,y,z)}'
ffffffffbb6002e0
ffffffffbb6003ca
239568104838418388.363636

Надеюсь, это поможет.


Код для всех претензий:

Использование оболочки для переносимости и использование %a, более близкого к внутреннему представлению поплавков,53 бита — это 13 цифр:

$ dash -c 'printf "%a\n" 0x1.12345678901234567890123'
0x1.1234567890123p+0

Другие оболочки (и некоторые awk )могут использовать 80-битное число с 64-битной мантиссой, которая может использовать до 16 цифр:

ksh -c 'printf "%a\n" 0x1.12345678901234567890123'
0x1.1234567890123456000000000000p+0

Awk ограничен тем, что он может принять как шестнадцатеричный (в качестве программной константы(x=)).

$ awk 'BEGIN { x=0x1fffffffffffff ; y=0x3fffffffffffff; printf("%18s %16x\n%18s %16x\n", x, x+0,y,y+0); }'
  9007199254740991   1fffffffffffff
 18014398509481984   40000000000000

$ mawk -vx=$(printf '%d\n' 0xffffffff) 'BEGIN{y=x*2;printf("%18s %16x\n%18s %16x\n", x, x+0,y,y+0); }'
        4294967295         7fffffff
       8.58993e+09         7fffffff

$ bawk 'BEGIN { x=2147483647 ; y=x*2+1; printf("%18s %16x\n%18s %16x\n", x, x+0,y,y+0); }'
        2147483647         7fffffff
        4294967295         80000000

Кроме того, ввод из файла и/или пользователь не может принимать шестнадцатеричные числа, если только не используется опция -n(, которая уже устарела ), или функция strtonum()(, рекомендованная ):

.
$ awk '{x=$1; printf "%s %x\n",x,x}' <<<0x123
0x123 0

$ awk -n '{x=$1; printf "%s %x\n",x,x}' <<<0x123
0x123 123

$ awk -n '{x=strtonum($1); printf "%s %x\n",$1,x}' <<<0x123
0x123 123

При первом вводе awk читает только первое 0и отбрасывает все после x, потому что это выглядит как слово. Он работает правильно в двух других случаях.

Таким образом, мы должны использовать десятичное число, чтобы упростить работу с awk. Если ваш printf ограничен, используйте bc:

$ val=$(printf "%d" 0x1234567890)
$ awk -vx="$val" 'BEGIN{printf "%s %x\n", x,x}'
78187493520 1234567890

$ val=$(bc <<<'ibase=16;1234567890')
$ awk -vx="$val" 'BEGIN{printf "%s %x\n", x,x}'
78187493520 1234567890

Тем не менее, awk ограничен:

$ val=$(bc <<<'ibase=16; 12345678901234')
$ awk -vx="$val" 'BEGIN{printf "%s %x\n", x,x}'
5124095575331380 12345678901234

$ val=$(bc <<<'ibase=16; 123456789012345')
$ awk -vx="$val" 'BEGIN{printf "%s %x\n", x,x}'
81985529205302085 123456789012340

Здесь обрезается последний 5, так как он не может быть представлен в виде числа с плавающей запятой из 53 бит.

Возможность обработки больших чисел улучшается, если используется опцияbignum(-M)для произвольной точности, но только для целых чисел:

$ val=$(bc <<<'ibase=16; 12345678901234567890123456789')" 
$ awk    -vx="$val" 'BEGIN{printf "%s %x\n", x,x}'
5907679980460342222050878921467785 5.90768e+33

$ awk -M -vx="$val" 'BEGIN{printf "%s %x\n", x,x}'
5907679980460342222050878921467785 12345678901234567890123456789

Если вам действительно нужно работать с большими числами и длинными десятичными знаками, вам необходимо также изменить используемый PREC (53 по умолчанию ).

$ awk -M -vx='12345678901234567890123456789' 'BEGIN{printf "%s \n%f\n", x,x/100}'
12345678901234567890123456789 
123456789012345678152597504.000000

$ awk -M -vPREC=500 -vx='12345678901234567890123456789' 'BEGIN{printf "%s \n%f\n", x,x/100}'
12345678901234567890123456789 
123456789012345678901234567.890000
9
28.07.2021, 11:33

Способ, которым я работаю с различными уровнями точности gawk, mawk134 и mawk2, заключается в написании функции-оболочки для инкапсуляции выполнения подпрограммы -оболочки gawk. Поэтому всякий раз, когда какая-либо функция обнаруживает, что ввод выше, чем точность ее текущей среды, она будет вызывать себя через эту оболочку, чтобы gawk -Mв оболочке sub -выполнить ее, и вернуть результат, используя инкапсулированную getline (. через оболочку, которая также обрезает последний одиночный конец \n ).

Скажите, хочу ли я разложить 2^190 - 1на простые множители. Я цитирую их и передаю в виде строки в свои функции, поэтому оболочка sub -по-прежнему может видеть все это вместо того, чтобы урезать точность pre -, тем самым сводя на нет точку подоболочки.

Как часть оболочки, я также делаю наилучшую оценку того, какой PREC мне нужно объявить для оболочки sub -, а затем добавляю фиксированное заполнение поверх этого, чтобы быть уверенным.

0
28.07.2021, 11:33

Теги

Похожие вопросы