해결책

해결책

나노초 정밀도로 날짜를 얻습니다.

$ start=$(date '+%s.%N')

...그리고 인쇄하세요:

$ echo ${start}
1662664850.030126174

여태까지는 그런대로 잘됐다. 하지만 임의로 큰 정밀도로 printf를 실행하면 어떤 결과가 나오는지 살펴보세요.

1662664850.0301261739805340766906738281250000000000000000000000000

Q1. date 명령이 실제로 시작 변수에 그만큼의 정보를 입력합니까, 아니면 숫자가 쓰레기입니까?

이것이 질문의 두 번째 부분입니다. 제가 잠시 수학을 하고 싶다고 가정해 보겠습니다. "종료" 타임스탬프를 만듭니다.

$ end=$(date '+%s.%N')
$ echo ${end}
1662665413.471669572
$ printf "%.55f\n" ${end}
1662665413.4716695720562711358070373535156250000000000000000000000

이제 bc를 사용하여 예상한 결과를 얻습니다.

$ echo $(bc <<< ${end}-${start})
563.441543398

하지만 Python이나 Perl을 사용할 때 얻은 결과를 살펴보십시오.

$ echo $(python -c "print(${end} - ${start})")
563.441543579
$ echo $(perl -e "print(${end} - ${start})")
563.441543579102

어느 시점에서 이 숫자는 다음과 같이 표시됩니다.

기원전 563.441543398화  
파이썬 563.441543579화  
진주 563.441543579화102  

Q2. 숫자는 다양하지만 반올림으로 인한 결과는 예상한 것과 다릅니다. 무엇을 제공합니까?

시스템 정보:
Linux 3.10.0-1160.71.1.el7.x86_64 #1 SMP 2022년 6월 15일 수요일 08:55:08 UTC

명령 정보:
날짜(GNU coreutils) 8.22
bc 1.06.95
Python 2.7.5
Perl 5, 버전 16, Subversion 3(v5.16.3) x86_64-linux-thread-multi용으로 빌드됨

답변1

다른 사람들이 말했듯이, 부동 소수점 숫자의 대부분의 10진수 표현은 컴퓨터가 계산에 사용하는 이진 형식으로 정확하게 표현될 수 없습니다.

당신이 보는 것은 printf %.55f원래 숫자의 이진 근사치를 십진수로 표현한 것입니다.

printf(가능한) 내장 명령은 이진 표현으로 변환을 사용한 다음 이를 1662665413.471669572함수 ( 또는 계열의 다른 함수, 예를 들어 )에 전달하여 형식을 지정합니다( 로 변경 ).long doublestrtold()printf()snprintf()%.55f%.55Lf

표현은 long double시스템마다, C 컴파일러마다, C 컴파일러마다, CPU 아키텍처마다 다릅니다. amd64 하드웨어에서 GNU cc/shell로 컴파일된 GNU/Linux 시스템 의 경우 printf, long double은 현재 타임스탬프를 나노초 정밀도로 저장할 만큼 충분한 정밀도를 갖습니다.

그러나 대부분의 awk 구현, Perl, 대부분의 셸(bash가 아닌 부동 소수점 연산을 지원하는 경우)을 포함한 많은(대부분은 아니지만) 도구와 언어는 긴 double 대신 double을 사용하며 배정밀도 숫자는 항상 53자리의 정밀도만 갖습니다. , 그래서정밀도는 소수점 이하 15자리를 초과할 수 없습니다.1.

이것이 바로 고정밀 타임스탬프를 다루는 대부분의 것들이 실제로 타임스탬프를 두 개의 정수, 즉 초와 마이크로초( struct timeval시스템 호출에서 반환된 값 gettimeofday()) 또는 나노초( struct timespec시스템 호출에서 반환된 값 clock_gettime())로 표현하는 이유 중 하나입니다.

이것이 GNU가 부동 소수점 변형 대신 및 를 date갖고 있고 zsh가 부동 소수점 대신 배열을 갖는 이유 입니다.%s%N%s$epochtime

이러한 고정밀 타임스탬프로 계산을 수행하려면 언어/도구가 long double을 사용하지 않거나 CPU 부동 소수점 연산(예bc : .

대부분의 쉘은 정수 연산에 64비트 정수를 사용하며 long, 이는 최대 9223372036854775807까지의 숫자를 수용할 수 있습니다.

예를 들어 두 타임스탬프(각 타임스탬프를 (초, 나노초) 튜플로 표시) 간의 차이를 계산하려면 (sec2 - sec1) * 100000000 + nsec2 - nsec1나노초 단위로 차이를 얻을 수 있습니다.

예를 들어 zsh에서는 다음과 같습니다.

zmodload zsh/datetime
start=($epochtime)
uname
end=($epochtime)
print running uname took about $((
  (end[1] - start[1]) * 1_000_000_000 + end[2] - start[2] )) nanoseconds.

여기에 주어진:

Linux
running uname took about 2621008 nanoseconds.

내장되지 않은 명령(포함)을 실행하는 데 최소한 수천 나노초가 걸리는 셸에서 그렇게 높은 정밀도를 갖는 것은 date말이 되지 않는다고 주장할 수 있습니다.

그림:

zmodload zsh/datetime
start=$EPOCHREALTIME
uname
end=$EPOCHREALTIME
printf 'running uname took about %.5g seconds\n' $(( end - start ))

$EPOCHREALTIME초와 나노초(0-패딩된) 정수를 중간 값으로 연결하여 구축되었기 때문에 완전한 정밀도를 갖지만 .계산을 수행하기 위해 변환하면 일부 정밀도가 손실됩니다. 이는 아마도 충분할 것입니다.double

ksh93에서와 같이 여기서는 다음을 수행하는 것이 좋습니다.

typeset -F SECONDS=0
uname
printf "running uname took %g seconds\n" $SECONDS

( $SECONDS, 쉘이 시작된 이후 절약된 시간은 부동(및 스톱워치로 재설정)으로 설정될 수 있으며, 이 경우 마이크로초 정밀도를 얻을 수 있습니다.)


1 예를 들어, a of s 대신 s를 printf사용한다고 해서 동일한 숫자가 제공된다는 보장은 없습니다(예: and of 또는 을 사용해 보십시오 ) .doublelong doubleprintf %.16g <a-16-digit-number>9.999999999999919printfzshgawkperl

답변2

예, 부동 소수점 숫자를 사용할 때 발생하는 몇 가지 부정확성이 있습니다. 이는 실수를 표현하기 위해 제한된 비트 또는 바이트 세트를 사용하는 경우 피할 수 없는 결과입니다. 또는 소수점 이하 자릿수가 여러 개인 숫자라고도 합니다.

예를 들어, 사용한 번호의 더 짧은 버전을 사용하면 다음과 같습니다.

 ➤ printf '%.50f\n' 50.030126174

50.03012617399999999862059141264580830466002225875854

A1

Q1. date 명령이 실제로 시작 변수에 그만큼의 정보를 입력합니까, 아니면 숫자가 쓰레기입니까?

A1.1. 아니요, 날짜는 그 만큼의 정보로 값을 채우지 않습니다.

A1.2. 쓰레기? 글쎄요, 누구에게 물어보느냐에 따라 다릅니다. 하지만 나에게 있어서 그들은 거의 쓰레기입니다.

A2

Q2. 숫자는 다양하지만 반올림으로 인한 결과는 예상한 것과 다릅니다. 무엇을 제공합니까?

이는 전적으로 64비트 부동 소수점 숫자(53비트 가수)로 반올림한 결과입니다.

배정밀도 부동 소수점 숫자의 경우 소수점 이하 15자리 이하를 신뢰할 수 있는 것으로 간주해야 합니다.

해결책

bc가 잘 작동한다는 것을 이미 확인했지만 여기에 몇 가지 대안이 있습니다.

[date]
$ date -ud "1/1/1970 + $end sec - $start sec " +'%H:%M:%S.%N'
00:09:23.441543398

[bc]
$ bc <<<"$end - $start"
563.441543398

[awk] (GNU) 
$ awk -M -vPREC=200 -vend="$end" -vstart="$start" 'BEGIN{printf "%.30f\n",end - start}'
563.441543398000000000000000000000

[Perl]
$ perl -Mbignum=p,-50 -e 'print '"$end"' - '"$start"', "\n"'
563.44154339800000000000000000000000000000000000000000

[python]
$ python3 -c "from mpmath import *;mp.dps=50;print('%.30s'%(mpf('$end')-mpf('$start')));"
563.44154339800000000000000000

정수 수학

그러나 실제로 start는 둘 다 end부동 소수점 숫자가 아닙니다. 각각은 사이에 점이 있는 두 정수의 문자열 연결입니다.

우리는 그것들을 (쉘에서 직접) 분리하고 수학을 수행하기 위해 거의 모든 것을 사용할 수 있습니다. 심지어 정수 쉘 수학도 가능합니다:

신뢰할 수 없는 숫자

어떤 사람들은 주어진 숫자를 가장 잘 표현하기 위해 수학적으로 정확한 결과를 얻을 수 있다고 주장할 수도 있습니다.

예, 우리는 많은 이진수를 계산할 수 있습니다:

 ➤ bc <<<'scale=100; obase=2; 50.030126174/1'
110010.0000011110110110010110010101010000010101011001110010101110011\
00101110010000100101010100000110100011110000011110111100101011110011\
11111100000010000001101100100011111010100011011101100011001110110000\
01010011000100001110101110000010000100010000100100110100001010001001\
11011111101101001010100001100000001010000101011000101100110101001100

339개의 이진수입니다.

그러나 어쨌든 우리는 그것을 float의 메모리 공간에 넣어야 합니다(여러 메모리 표현을 가질 수 있지만 아마도 long double일 것입니다).

x86 시스템에서 가장 일반적인 Linux 컴파일러가 사용하는 64개의 이진수(확장 부동 소수점, Intel FP-87의 80비트)를 보유할 수 있는 부동 소수점 표현에 대해 논의하기로 선택할 수 있습니다 gcc. 다른 컴파일러에서는 64비트 이중 부동 소수점에 대한 53비트 가수와 같은 다른 것을 사용할 수 있습니다.

그런 다음 위의 이진수를 다음 두 숫자 중 하나로 슬래시해야 합니다.

110010.0000011110110110010110010101010000010101011001110010101110
110010.0000011110110110010110010101010000010101011001110010101111

둘 중 원본에 가장 가까운 것이최고표현하다.

두 숫자의 정확한(수학적) 십진수 값은 다음과 같습니다.

50.030126173999999998620591412645808304660022258758544921875000000
50.030126174000000002090038364599422493483871221542358398437500000 

원래 번호와의 차이점은 다음과 같습니다.

00.000000000000000001379408587354191695339977741241455078125000000
00.000000000000000002090038364599422493483871221542358398437500000

따라서 수학적 관점에서 볼 때 가장 좋은 숫자는 0으로 끝나는 숫자입니다.

이것이 일부 사람들이 결과를 수학적으로 계산할 수 있다고 생각하는 이유입니다.

이것이 사실이지만 여기에 문제가 있습니다. 그것은 근사치, 좋은 근사치, 최고의 근사치이지만 그럼에도 불구하고 근사치입니다.

더욱이, 원래 숫자와 근사치 사이의 거리의 정확한 크기를 미리 알 수 없습니다(실수를 이진수로 변환하기 전): 근사치 오류.

거리는 거의 무작위입니다. 오류 크기는 거의 임의의 숫자입니다.

그렇기 때문에 18자리(64개의 부동소수점) 이후의 숫자는 신뢰할 수 없다고 말하는 것입니다.

53비트(이중 정밀도)에서는 15자리를 초과하는 숫자는 신뢰할 수 없습니다.

$ bc <<<"scale=20;l2=l(2)/l(10); b=53 ;d=((b-1)*l2);scale=0;d/1"
15

공식은 1967년 DWMatula 논문에서 복사되었지만 C 표준인 C11 5.2.4.2.2p11에서 더 쉽게 찾을 수 있습니다.

제한이 15비트인 경우 잘라내려는 위치를 확인할 수 있습니다.

1662664850.030126174
1662665413.471669572
1234567890.12345
                ^----cut here!

이것이 Python과 Perl에서 일부 부정확성을 경험하는 이유입니다.

답변3

이진 부동 소수점 수학은 이와 같습니다. 대부분의 프로그래밍 언어에서는 IEEE 754 표준을 기반으로 합니다. 문제의 핵심은 숫자가 이 형식에서 2의 거듭제곱을 곱한 정수로 표현된다는 것입니다. 분모는 2의 거듭제곱인 유리수(예: 1/10인 0.1)가 아닙니다.정확하게 표현하지 못함.

자세한 설명은 참조하세요이 답변은 SO에 있습니다

답변4

추가적인 오류 원인이 있습니다. date반환 값은 서로 다른 하드웨어 시계에서 발생하며 둘 다 정수입니다 %s. %N나노초 값은 고해상도 타이머를 사용할 수 있는지 여부에 따라 달라집니다. 그렇더라도 man -s 7 time"마이크로초 정밀도는 최신 하드웨어의 전형입니다".

이 두 독립적인 시간 부분의 분해능에 관계없이 .이를 소수점으로 묶어서 이진수로 다시 분석하면 함께 이어진 값의 정밀도는 15비트 또는 16비트에 불과합니다.

이제 해당 시대 이후의 날짜는 10자리이므로 시간의 소수 부분은 이제 5자리 또는 6자리의 정밀도에 불과합니다.

관련 정보