Обычный реляционный оператор '

Обычный реляционный оператор '

У меня есть такой файл:

0.2
0.2
0.2
0.2
0.2
0.2
0.2024
0.2025
0.2027
0.2027
0.2029
0.2059
0.2059
0.2059
0.2059
0.2099
0.2099
0.2099
0.2105
0.2113
0.2113
0.2195
0.2198
0.2206
0.2206
0.2206
0.2989
0.2989
0.2989
0.3
0.3

И я хотел бы подсчитать количество значений, которые включены в диапазон, например:

0.2 18
0.21 5
0.22 3
0.23 0
0.24 0
0.25 0
0.26 0
0.27 0
0.28 0
0.29 3
0.3 2

Как видите, я использую интервал 0,01. Я использую awkдля достижения этого, но получаю странное поведение:

awk 'BEGIN {for (i=0;i<1.01;i+=0.01) a[i]=0} {
    for (j=0;j<=1;j+=0.01) 
      if($1>=j && $1<j+0.01) {
        a[j]+=1
      }
    } 
    END {for (k=0;k<1.01;k+=0.01) print k,a[k]}' test_OH.txt

Результат:

0.19 6
0.2 12
0.21 5
0.22 3
0.23 0
0.24 0
0.25 0
0.26 0
0.27 0
0.28 0
0.29 5
0.3 0

Кто-нибудь может мне помочь? Я думаю, <что это не работает так, как ожидалось, потому что это удовлетворяет, когда $1 == j+0.01. Конечно, я что-то не учитываю. Спасибо!

решение1

awk -v s=0.2 -v e=0.3 -v d=0.01 '
   BEGIN { m = 1/d }
   { a[int($1*m)]++ }
   END{ e *= m; for(s = int(s*m); s <= e; s++) print s*d, a[s]+0 }
' test_OH.txt

0.2 18
0.21 5
0.22 3
0.23 0
0.24 0
0.25 0
0.26 0
0.27 0
0.28 0
0.29 3
0.3 2

Переменные s(начало), e(конец) и d(дельта/шаг) можно корректировать по мере необходимости.


  1. Создание диапазона путем многократного сложения 0.01— это практически хрестоматийный пример того, чего НЕ следует делать с числами с плавающей точкой, поскольку их 0.01невозможно точно представить в двоичной системе счисления, и ошибка будет накапливаться с каждым сложением.

  2. Сканирование всего диапазона для каждой строки неэффективно и бессмысленно.

  3. Переменные в awk не обязательно должны быть инициализированы как ""или 0.

решение2

Попробуй это:

awk '
  BEGIN {for (i=0;i<1.01;i+=0.01) a[i]=0}
  {
    n = int($1 * 100) / 100
    a[n] += 1
  }
  END {for (k=0;k<1.01;k+=0.01) print k,a[k]}'

или вот это, что я нахожу менее понятным:

awk 'BEGIN {for (i=0;i<1.01;i+=0.01) a[i]=0} {
    for (j=0;j<=1;j+=0.01)
      if(("X" $1 >= "X" j) && ("X" $1 < "X" j+0.01)) {
        a[j]+=1
      }
    }
    END {for (k=0;k<1.01;k+=0.01) print k,a[k]}'

О причине, по которой оригинал не сработал, см. замечание Квазимодо выше.

решение3

Не уверен, следует ли мне публиковать это как ответ или комментарий, но вот короткая демонстрация неточности и полученных в этом случае чисел на моем компьютере:

$ awk 'BEGIN { d = 0.01; printf "%.20f\n", d; for (i = 0; i < 30; i++) a += d; printf "%.20f\n%.20f\n", 0.3, a }'   
0.01000000000000000021
0.29999999999999998890
0.30000000000000009992

Первое число — это то, как на самом деле хранится 0,01. Оно неточно, поскольку 1/100 включает в себя множитель 1/5, а его невозможно представить в двоичном виде.

Во-вторых, это то, как хранится 0,3, а в-третьих, это 0,01, добавленное само к себе 30 раз. (Это даже не то же самое, 0.01 * 30потому что на каждом шаге есть промежуточное округление, я полагаю.)

У других ответов есть решения, проголосуйте за них.

Связанный контент