awk 고정밀 연산

awk 고정밀 연산

대체 연산 중에 awk에게 고정밀 연산을 수행하도록 지시하는 방법을 찾고 있습니다. 여기에는 파일에서 필드를 읽고 이를 값의 1% 증분으로 바꾸는 작업이 포함됩니다. 그러나 나는 거기에서 정밀도를 잃고 있습니다. 다음은 문제를 단순화하여 재현한 것입니다.

 $ echo 0.4970436865354813 | awk '{gsub($1, $1*1.1)}; {print}'
   0.546748

여기에서는 소수점 이하 16자리의 정밀도가 있지만 awk는 6자리만 제공합니다. printf를 사용하면 동일한 결과를 얻습니다.

$ echo 0.4970436865354813 | awk '{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}'
0.546748

필요한 정확성을 얻는 방법에 대한 제안 사항이 있습니까?

답변1

$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g '{gsub($1, $1*1.1)}; {print}'
0.54674805518902947

아니면 여기가 더 낫습니다:

$ echo 0.4970436865354813 | awk '{printf "%.17g\n", $1*1.1}'
0.54674805518902947

아마도 당신이 달성할 수 있는 최고의 목표일 것입니다. 임의의 정밀도를 위해 bc.

$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943

답변2

(GNU) awk(bignum으로 컴파일)를 사용하여 더 높은 정밀도를 얻으려면 다음을 사용하십시오.

$ echo '0.4970436865354813' | awk -M -v PREC=100 '{printf("%.18f\n", $1)}'
0.497043686535481300

PREC=100은 기본 53비트가 아닌 100비트를 의미합니다.
awk를 사용할 수 없으면 bc를 사용하십시오.

$ echo '0.4970436865354813*1.1' | bc -l
.54674805518902943

아니면 수레의 본질적인 부정확성을 감수하는 법을 배워야 합니다.


원래 라인에는 몇 가지 문제가 있습니다.

  • 1.1배는 1%가 아니라 10% 증가를 의미합니다(1.01배수여야 함). 저는 10%를 사용하겠습니다.
  • 문자열을 (부동 소수점) 숫자로 변환하는 형식은 CONVFMT에 의해 제공됩니다. 기본값은 입니다 %.6g. 이는 값을 소수점 이하 6자리(점 뒤)로 제한합니다. 이는 gsub 변경 결과에 적용됩니다 $1.

    $ a='0.4970436865354813'
    $ echo "$a" | awk '{printf("%.16f\n", $1*1.1)}'
    0.5467480551890295
    
    $ echo "$a" | awk '{gsub($1, $1*1.1)}; {printf("%.16f\n", $1)}'
    0.5467480000000000
    
  • printf 형식은 g후행 0을 제거합니다.

    $ echo "$a" | awk '{gsub($1, $1*1.1)}; {printf("%.16g\n", $1)}'
    0.546748
    
    $ echo "$a" | awk '{gsub($1, $1*1.1)}; {printf("%.17g\n", $1)}'
    0.54674800000000001
    

    두 문제 모두 다음을 통해 해결될 수 있습니다.

    $ echo "$a" | awk '{printf("%.17g\n", $1*1.1)}'
    0.54674805518902947
    

    또는

    $ echo "$a" | awk -v CONVFMT=%.30g '{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}'
    0.54674805518902947 
    

그러나 이것이 더 큰 정확성을 의미한다고 생각하지 마십시오. 내부 숫자 표현은 여전히 ​​두 배 크기의 부동 소수점 숫자입니다. 이는 53자리의 정밀도를 의미하므로 최대 17자리까지 여러 번 정확해 보이더라도 올바른 십진수는 15자리만 결정할 수 있습니다. 그것은 신기루입니다.

$ echo "$a" | awk -v CONVFMT=%.30g '{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}'
0.546748055189029469325134868996

올바른 값은 다음과 같습니다.

$ echo "scale=18; 0.4970436865354813 * 1.1" | bc
.54674805518902943

bignum 라이브러리가 다음과 같이 컴파일된 경우 계산에 (GNU) awk를 사용할 수도 있습니다.

$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000

답변3

내 awk 스크립트는 한 줄의 코드 이상이므로 Stéphane Chazelas와 Isaac의 답변을 결합했습니다.

  1. CONVFMT출력 형식을 처리하기 위해 전역 변수를 설정했습니다.
  2. 나는 또한 bignum 매개변수 -MPREC변수를 사용합니다.

예제 스니펫:

#!/usr/bin/awk -M -f
BEGIN {
  FS="<|>"
  CONVFMT="%.18g"
  PREC=100
}
{
  if ($2 == "LatitudeDegrees") {
    CORR = $3 // redacted specific corrections
    print("     <LatitudeDegrees>" CORR "</LatitudeDegrees>");
  } else if ($2 == "LongitudeDegrees") {
    CORR = $3 // redacted specific corrections
    print("     <LongitudeDegrees>" CORR "</LongitudeDegrees>");
  } else {
    print($0);
  }
}
END {
}

OP는 그의 예를 단순화했지만 awk 스크립트가 한 줄 스크립트가 아닌 경우 s로 오염시키고 싶지 않고 printf변수에서 이와 같이 형식을 지정합니다. 정밀도가 동일하므로 실제 명령줄 호출에서 손실되지 않습니다.

관련 정보