awk 배열을 사용할 때 해당 문제에 대해 큰 숫자를 합산하고 모든 소수점으로 결과를 인쇄합니다.

awk 배열을 사용할 때 해당 문제에 대해 큰 숫자를 합산하고 모든 소수점으로 결과를 인쇄합니다.

아래에는 세 번째 열의 날짜를 기준으로 여러 파일로 분할해야 하는 입력 파일이 있습니다. 기본적으로 동일한 날짜의 모든 거래는 특정 날짜의 파일로 분할되어야 합니다. 분할한 후에는 제목과 예고편을 만들어야 합니다. 예고편의 네 번째 열에는 레코드 수와 금액 합계(해당 날짜의 금액 합계)가 포함되어야 합니다. 이런 경우, 위에서 말했듯이 숫자가 매우 큰데, 아래 코드에서 bc 를 어떻게 통합할 수 있을까요?

입력 파일

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^8|~^xxx|~^123670130.37256

출력 파일 20190305.txt

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^4|~^xxx|~^107707.068

출력 파일 20190306.txt

H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
T|~^20200425|~^4|~^xxx|~^123562423.30456

내가 사용하고 있는 코드(PS: 커뮤니티 회원 중 한 명이 제안함) awk해결 방법은 다음과 같습니다.

awk -F'\\|~\\^' '{ 
            if($1=="H"){ 
                head=$0
            }
            else if($1=="T"){
                foot=$1"|~^"$2
                foot4=$4
            }
            else{
                date=$3;
                sub("T.*","", date);
                data[date][NR]=$0;
                sum[date]+=$4; 
                num[date]++
            }
           }
           END{
            for(date in data){
                file=date".txt";
                gsub("-","",file); 
                print head > file; 
                for(line in data[date]){
                    print data[date][line] > file
                } 
                printf "%s|~^%s|~^%s|~^%s\n", foot, num[date], 
                                              foot4, sum[date] > file
            }
           }' file 

코드가 아주 잘 실행됩니다. 하지만 이 단계에서는

sum[date]+=$4;

큰 수를 합칠 수는 없습니다. 마지막 단계에서 사용하고 있기 때문에 %s트레일러 합계가 지수 값으로 인쇄됩니다.

printf "%s|~^%s|~^%s|~^%s\n", foot, num[date], 
                                                  foot4, sum[date] > file

여기서는 큰 숫자에 합계를 적용하고 정확한 합계를 인쇄하고 싶습니다. (여기서 bc(bash 계산기)를 시도했지만 합계가 배열을 기반으로 하고 특정 날짜를 기준으로 추가되기 때문에 멈췄습니다.) 이 문제를 해결하도록 도와주세요

또한 "%.15g"트레일러 단계를 시도했습니다.

printf "%s|~^%s|~^%s|~^%.15g\n", foot, num[date], 
                                                  foot4, sum[date] > file

여기서 결과가 15자리(소수점 포함)이면 정확한 합을 구할 수 있습니다. 합계 결과가 15자리를 초과하는 경우 이 방법은 효과가 없습니다. 도와주세요

답변1

awk큰 숫자 문제를 무시하고 다음과 같은 프로그램을 작성하겠습니다 .

BEGIN {
        FS = "\\|~\\^"
        OFS= "|~^"
}

$1 == "H" {
        header = $0
}

$1 == "R" {
        name = $3
        sub("T.*", "", name)

        sum[name] += $4
        cnt[name] += 1

        if (cnt[name] == 1)
                print header >name ".txt"

        print >name ".txt"
}

$1 == "T" {
        for (name in sum)
                print $1, $2, cnt[name], $4, sum[name] >name ".txt"
}

편의상 출력 필드 구분자 OFS|~^. 이렇게 하면 출력 필드 사이에 삽입하는 것에 대해 걱정할 필요가 없습니다. 입력 필드 구분 기호 FS는 이 문자열과 일치하는 정규식으로 설정됩니다.

그런 다음 세 가지 주요 코드 블록이 있습니다.

  1. 하나는 행을 구문 분석하는 데 사용됩니다 H. 그 중 하나만 있고 처음에 발생한다고 가정합니다. 이것은 단지 헤더 행을 변수에 저장합니다 header.

  2. 하나는 R라인 구문 분석을 위한 것입니다. 각 레코드에는 세 번째 필드에서 출력 파일 이름으로 사용해야 하는 날짜가 포함되어 있습니다. 그것은 당신과 같은 방식으로 구문 분석됩니다. 해당 날짜의 합계가 누적되고 카운터가 증가합니다.

    카운터가 1이면, 즉 특정 날짜를 처음 본 경우 해당 날짜와 관련된 출력 파일에 헤더를 씁니다. 그런 다음 현재 레코드를 파일에 씁니다.

  3. 마지막 블록은 행을 T구문 분석합니다. 그 중 하나만 있고 마지막에 나타난다고 가정합니다. 이는 단순히 각 개별 날짜의 누적 합계 및 개수를 T원래 행의 일부 데이터와 함께 해당 날짜와 관련된 파일로 출력합니다.

임의로 큰 숫자를 지원합니다(예:다른 곳에서숫자를 저장하는 데 100비트 이상이 필요하여 오버플로되는 경우( 의 정수) 임의 정밀도 계산기를 "코프로세스"(컴퓨팅 서비스) awk로 사용합니다 . bc라인은 sum[name] += $4다음으로 대체됩니다.

if (sum[name] == "") sum[name] = 0
printf "%s + %s\n", sum[name], $4 |& "bc"
"bc" |& getline sum[name]

이를 위해서는 GNU awk(대부분의 Unix 시스템에서 어떤 방식으로든 사용 가능)가 필요합니다.

이것이 수행하는 작업은 현재 날짜의 합계가 아직 없는 경우 먼저 현재 날짜의 합계를 0으로 초기화하는 것입니다. 우리가 이렇게 하는 이유는 초기 합계를 제공해야 하기 때문입니다 0.bc

bc그런 다음 awkGNU 특정 파이프를 사용하여 평가해야 하는 표현식을 인쇄하여 |&보조 프로세스에 씁니다. 이 bc유틸리티는 스크립트와 병렬로 시작 및 실행되고 awk계산을 수행한 다음 getline다른 파이프의 출력을 직접 .bc|&sum[name]

내가 이해한 바에 따르면 GNU는 각 합계에 대해 별도의 프로세스를 생성 awk하지 않고 공동 프로세스로 실행되는 프로세스를 유지합니다. 따라서 이는 로컬에서 계산을 수행하는 것보다 느리지만 각 합계에 대해 별도의 계산을 생성하는 것보다 훨씬 빠릅니다.bcbcawkbc

주어진 데이터에 대해 다음 두 파일이 생성됩니다.

$ cat 2019-03-05.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^4|~^xxx|~^107707.068
$ cat 2019-03-06.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
T|~^20200425|~^4|~^xxx|~^123562423.30456

답변2

나는 이미 하나를 썼다이 문제를 해결하기 위한 awk 코드여기에 제시된 코드보다 빠르게 실행됩니다.

당신은 과거에 많은 숫자를 합산하는 것에 관해 질문을 했고 부정확한 답변을 얻은 적이 있습니다. 이 질문은 다른 질문과 매우 유사합니다.이 두 sum 명령 사이에 차이점이 있는 이유는 무엇입니까?.

이 문제의 파일 크기는 20MB이고 700줄이 넘습니다.
귀하는 파일의 순서가 다음과 같다고 명시했습니다.파일 크기는 약 500~600MB입니다.. 이렇게 하면 행 수가 천만 행 범위로 늘어납니다.

문제는 추가할 숫자입니다.

  • 매우 다양할 수 있습니다. 범위는 3자리에서 12.828자리입니다 1245637.34526234567299999999.

  • 28자리 숫자를 천만 번 더하면 28 + 7 = 35자리가 필요합니다. 이는 숫자가 모두 소수 또는 정수가 아니라고 가정합니다. 이런 일이 발생하면 약 70자리(정수 35개 + 소수 35개)에 해당합니다.

  • 부동 소수점 숫자의 근본적인 문제는 부동 소수점 숫자의 표현이 항상 정확한 숫자의 근사치라는 것입니다. 정확한 합계가 필요한 경우에는 모두 정수로 더해야 합니다.

문제에 대한 해결책으로 더 긴 비트 수의 GNU awk를 사용하는 것일 수 있습니다. awk의 기본 부동 소수점 숫자는 53비트 가수를 사용하며 이는 15자리 숫자에만 작동합니다.

MPFR(Reliable Multi-Precision Floating Point) 및 GMP(GNU Multi-Precision Arithmetic Library)로 컴파일된 GNU AWK를 사용하는 경우 --version 텍스트의 결과에 이 정보가 포함되어야 합니다(execute awk --version). 이 경우 더 많은 비트를 사용할 수 있습니다. 40비트 부동 소수점(위에서 계산된 35자리 + 일부 안전 마진)을 유지하려면 다음이 필요합니다.

b = ceil(d log2(10)) + 1

b = ceil( 40 * 3.321928 ) + 1 = 133 + 1 = 134 binary digits (bits)

따라서 awk 호출은 다음과 같아야 합니다.

 awk -M -v PREC=134 

경고: 더 많은 숫자를 사용하면 프로그램 속도가 느려집니다.

그리고 여전히 같은 awk 프로그램을 사용하고 있습니다

awk -M -v PREC=134 '

     BEGIN  { FS="\\|~\\^"; OFS="|~^" }
     $1=="H"{ header=$0; hdr=$2 }
     $1=="R"{
              t=gensub(/-/, "","g",$3)
              file=gensub(/T.*/,"",1,t);
              sum[file]+=$4
              if(count[file]==0){ print header >file }
              count[file]++
              print $0 >>file
            }
     END    {
              for( i in sum ){
                  printf "T %s %10d xxx %45.25f",hdr,count[i],"xxx",sum[i] >> i;
                  close(i)
                  }
            }
' "inputfile"

참고: 거의 같은 질문을 반복해서 하시는군요.

관련 정보