대체 연산 중에 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의 답변을 결합했습니다.
CONVFMT
출력 형식을 처리하기 위해 전역 변수를 설정했습니다.- 나는 또한 bignum 매개변수
-M
와PREC
변수를 사용합니다.
예제 스니펫:
#!/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
변수에서 이와 같이 형식을 지정합니다. 정밀도가 동일하므로 실제 명령줄 호출에서 손실되지 않습니다.