awk는 왜 합계를 0으로 만들지 않지만 아주 작은 숫자로 만들까요?

awk는 왜 합계를 0으로 만들지 않지만 아주 작은 숫자로 만들까요?

이 파일이 있는데 첫 번째 열의 모든 숫자를 합산하고 싶습니다. 단순한:

awk '{s+=$1;print $1,s}' file
0.1048 -1.2705
0.4196 -0.8509
0.4196 -0.4313
0.2719 -0.1594
0.0797 -0.0797
0.0797 -5.55112e-17   #Notice this line

알다시피, 마지막 값은 0이어야 합니다. 나는 그것이 e-170이라는 것을 알고 있지만 때로는 출력이 정확히 0입니다. 0이 아닌 경우 출력은 음수 또는 양수 기호로 표시되는 e-15to 범위 에 있습니다. e-17이 문제를 해결하려면 절대값을 사용해야 합니다.

awk '{s+=$1;if (sqrt(s^2)<0.01) s=0;print $1,s}' file

왜 이런 일이 일어나는지 아십니까?

답변1

이는 컴퓨터가 숫자를 처리할 때 정밀도가 제한되어 있기 때문에 발생합니다. 그리고 사용 가능한 정밀도는 이진 형식을 사용하여 숫자를 나타냅니다.

이로 인해 우리의 십진법에서는 사소해 보이는 숫자가 근사치로만 표현됩니다(참조:위키피디아 항목이를 위해): 예를 들어 0.1(예: 1/10)은 실제로 0.100000001490116119384765625컴퓨터에 있는 것과 유사하게 저장됩니다.

따라서 모든 전화번호는 실제로 단 하나의 전화번호로 처리됩니다.근사치를 내다(운이 좋지 않고 0.5그러한 숫자가 나타낼 수 있는 숫자 가 있는 경우는 제외)정확히).

이러한 대략적인 숫자를 모두 더하면 결국 오류가 발생할 수 있습니다 != 0.

답변2

이 문제에 대한 해결 방법으로 다음과 같은 산술 연산을 처리하도록 특별히 설계된 프로그램을 사용할 수 있습니다 bc.

$ awk '{printf "%s + ",$1}' file | sed 's/\+ $/\n/' | bc
0

(그렇다고 생각되는 경우) 고정된 소수 자릿수가 있는 경우 이를 제거하여 정수를 처리하고 끝에 다시 추가할 수 있습니다.

$ awk '{sub("0.","",$1);s+=$1;}END{print s/10000}' file
0

또는

$ perl -lne 's/0\.//; $s+=$_; END{print $s/10000}' file
0

답변3

대부분의 버전에는 명령이 awk있습니다 printf. 바꾸다

print $1,s

사용

printf "%.4f %.4f\n",$1,s

출력은 소수점 이하 4자리로 반올림됩니다. 이렇게 하면 대부분의 반올림 오류가 표시되지 않습니다.

답변4

귀하의 질문은 "왜 이런 일이 발생합니까?"이지만 귀하의 암시적 질문(다른 사람들이 해결한)은 "이 문제를 어떻게 해결합니까?"입니다. 귀하는 댓글에서 요청한 방법을 찾았습니다.

그럼 점을 없애기 위해 1000을 곱하면 정확한 결과를 얻을 수 있겠죠?

예. 음, 10,000입니다. 왜냐하면 소수점 이하 4자리가 있기 때문입니다. 생각해 보세요:

awk '{ s+=$1*10000; print $1, s/10000 }'

안타깝게도 토큰(문자열)을 십진수로 해석하자마자 손상이 이미 발생했기 때문에 이는 작동하지 않습니다. 예를 들어, printf "%.20f\n"입력 데이터가 0.4157 실제로 0.41570000000000001394로 해석되는 것을 보여줍니다. 이 예에서는 10000을 곱하면 4157이라는 결과가 나옵니다. 그러나 예를 들어 0.5973= 0.59730000000000005311에 10000을 곱하면 5973.00000000000090949470이 됩니다.

그래서 우리는 노력한다

awk '{ s+=int($1*10000); print $1, s/10000 }'

정수여야 하는 숫자(예: 5973.00000000000090949470)를 해당 정수(5973)로 변환합니다. 그러나 때로는 변환 오류가 음수이기 때문에 실패합니다. 예를 들어 0.71300.71299999999999996714입니다. 그리고 awk의 함수는 7129처럼 반올림하는 대신 (0을 향해) 자릅니다.int(expr)int(7129.99999999)

그러므로 인생이 당신에게 레몬을 주면 레모네이드를 만드는 것입니다. 도구가 절단 기능을 제공하면 반올림을 위해 0.5를 추가할 수 있습니다. 7129.99999999+0.5≒7130.49999999, 물론 int(7130.49999999)7130입니다. 하지만 기억하세요: int()잘라내기0이 되는 경향이 있다, 입력 내용에 음수가 포함되어 있습니다. -7129.99999999를 -7130으로 반올림하려면 다음이 필요합니다.마이너스0.5는 –7130.49999999를 얻습니다. 그래서,

awk '{ s+=int($1*10000+($1>0?0.5:-0.5)); print $1, s/10000 }'

$1*10000$1가 0 이하 이면 –0.5를 더합니다.

관련 정보