解決策

解決策

ナノ秒精度で日付を取得します。

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

...そして印刷してください:

$ echo ${start}
1662664850.030126174

今まではそんなに良くなった。しかし、ランダムに大きな精度でprintfを実行すると、どのような結果が出るのか見てみましょう。

1662664850.0301261739805340766906738281250000000000000000000000000

Q1. dateコマンドは実際に開始変数にその分の情報を入力しますか、それとも数字がゴミですか?

これが質問の2番目の部分です。私がしばらく数学をしたいとしましょう。 「終了」タイムスタンプを作成します。

$ 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話  
Python 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元の数字の2進近似を10進数で表したものです。

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.

これが正確なタイムスタンプを扱うほとんどのものが実際にタイムスタンプを2つの整数、つまり秒とマイクロ秒(struct timevalシステムコールから返された値gettimeofday())またはナノ秒(struct timespecシステムコールで返された値clock_gettime())で表す理由の1つです。

これがGNUが浮動小数点バリアントの代わりにおよびをdate持ち、zshが浮動小数点の代わりに配列を持つ理由です。%s%N%s$epochtime

これらの高精度タイムスタンプで計算を実行するには、言語/ツールがlong doubleを使用しないか、またはCPU浮動小数点演算(たとえばbc

ほとんどのシェルは整数演算に64ビット整数を使用します。longこれは最大9223372036854775807までの数字を受け入れることができます。

たとえば、2つのタイムスタンプ(各タイムスタンプを(秒、ナノ秒)タプルとして表示)の差を計算するには、ナノ秒(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秒とナノ秒(ゼロパディング)整数を中間値に連結して構築されているため、完全な精度がありますが、.計算を実行するために変換すると一部の精度が失われます。これはおそらく十分です。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小数点数ではありません。それぞれは、間に点がある2つの整数の文字列連結です。

私たちはそれらを(シェルから直接)分離し、数学を実行するためにほとんどすべてを使用できます。整数シェル数学も可能です:

信頼できない数字

何人かの人々は与えられた数を最もよく表現するために数学的に正確な結果を得ることができると主張するかもしれません。

はい、私達は多くの2進数を計算できます:

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

339個の2進数です。

しかし、とにかく私たちはそれをfloatのメモリ空間に入れなければなりません(複数のメモリ表現を持つことができますが、おそらくlong doubleでしょう)。

x86システムで最も一般的なLinuxコンパイラが使用する64個の2進数(拡張浮動小数点、Intel FP-87の80ビット)を保持できる浮動小数点表現について説明することを選択できますgcc。他のコンパイラでは、64ビット二重浮動小数点に対して53ビットの歌手などの他のものを使用できます。

次に、上記の2進数を次の2つの数字のいずれかにスラッシュする必要があります。

110010.0000011110110110010110010101010000010101011001110010101110
110010.0000011110110110010110010101010000010101011001110010101111

どちらのオリジナルに最も近い最高表現する。

2つの数値の正確な(数学的)10進値は次のとおりです。

50.030126173999999998620591412645808304660022258758544921875000000
50.030126174000000002090038364599422493483871221542358398437500000 

元の番号との違いは次のとおりです。

00.000000000000000001379408587354191695339977741241455078125000000
00.000000000000000002090038364599422493483871221542358398437500000

したがって、数学的観点から見ると、最良の数字はゼロで終わる数字です。

これが何人かの人々が結果を数学的に計算できると考える理由です。

これが本当ですが、ここに問題があります。それは近似、良い近似、最高の近似ですが、それにもかかわらず近似です。

さらに、元の数字と近似値の間の距離の正確なサイズを事前に知ることはできません(実数を2進数に変換する前):近似エラー。

距離はほぼランダムです。エラーサイズはほぼランダムな数値です。

そのため、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「マイクロ秒精度は最新ハードウェアの典型です」。

2つの独立した時間部分の分解能に関係なく、.それを小数点で囲んで2進数で再分析すると、一緒に続く値の精度は15ビットまたは16ビットにすぎません。

今の時代以降の日付は10桁なので、時間の小数部分は5桁または6桁の精度にすぎません。

関連情報