Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Two Files 100 Nanoseconds Apart Got the Same Timestamp in the Browser: JSON.parse and Integers Past MAX_SAFE_INTEGER

2026年10月20日に「Dev.to」が公開したITニュース「Two Files 100 Nanoseconds Apart Got the Same Timestamp in the Browser: JSON.parse and Integers Past MAX_SAFE_INTEGER」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

JavaScriptの`JSON.parse`は、ナノ秒タイムスタンプのような非常に大きな整数を正確に扱えず、数値が丸められる問題がある。これにより、ファイル更新時刻などの比較が誤り、未変更なものが変更されたと判断される可能性がある。この精度低下を防ぐには、数値を`BigInt`型で扱うか、文字列として送受信する必要がある。

ITニュース解説

今回のニュース記事は、ウェブアプリケーションを開発する上で、特に日付や時刻のような「大きな数値」を扱う際に発生しやすい、数値の精度に関する重要な問題を取り上げている。システムエンジニアを目指す皆さんにとって、このようなデータの正確性を保証するメカニズムを理解することは、信頼性の高いシステムを構築するために不可欠だ。

この問題は、ファイルの更新時刻を非常に精密なナノ秒単位で管理しているシステムで発生した。具体的には、サーバー側がPythonで実装されており、ファイルのリストとそのナノ秒単位の最終更新時刻をJSON形式でウェブブラウザに提供していた。しかし、ブラウザ側でJavaScriptがこのJSONデータを受け取り、JSON.parseという関数で解析したところ、驚くべきことに、わずか100ナノ秒しか離れていない2つのファイルの更新時刻が「全く同じ値」として認識されてしまったのだ。

この認識のずれが引き起こした具体的な問題は、ブラウザがサーバーに対してファイルの変更状況を確認するプロセスで露呈した。ブラウザが「このファイルは、私が最後に見たこのタイムスタンプから変更されていないか?」という問い合わせを、自分が認識しているタイムスタンプを添えてサーバーに送った。しかし、サーバー側で元の正確なタイムスタンプと比較すると、ブラウザが送ってきたタイムスタンプは異なっていたため、サーバーは「ファイルは変更された」と誤って回答してしまった。実際にはファイルは何一つ変更されていなかったにもかかわらず、システムの誤認識によってデータの一貫性が損なわれたのである。

このような現象が起こる根本的な原因は、JavaScriptの数値の表現方法にある。JavaScriptの「Number」型は、国際標準であるIEEE 754倍精度浮動小数点数という形式で数値を扱っている。この形式は非常に広い範囲の数値を表現できる一方で、全ての整数を完全に正確に表せるわけではないという特性がある。特に、正確に表現できる整数の範囲には上限があり、これは「Number.MAX_SAFE_INTEGER」という定数で示される値(約9007兆)だ。しかし、現代のシステムで使われるナノ秒単位のタイムスタンプは、この安全な範囲を大きく超える、非常に大きな整数値になることが一般的だ。

Number型で安全な範囲を超えた大きな整数値を扱うと、JavaScriptはその値を、最も近い「表現可能な」値に丸めてしまう。今回のケースでは、たった100ナノ秒というわずかな差が、JavaScriptのNumber型にとっては無視できる範囲であり、異なる2つのタイムスタンプが同じ値に丸められてしまったのだ。記事の実験では、この丸めにより、実際の値とJavaScriptが保持する値の間に最大で256ナノ秒ものギャップが生じていることが示されている。

この問題を解決するためのアプローチがいくつか提案されている。まず一つは、JavaScriptが提供する新しい数値型「BigInt」の活用だ。BigIntは、Number型では扱いきれないような非常に大きな整数値を、精度を全く損なわずに正確に表現できる。JSON.parse関数には、データが解析された直後にその値を加工できる「reviver」という特別な引数がある。Node.js 22以降のような新しい実行環境では、このreviver関数に渡される「context.source」という引数に、元のJSONテキストの文字列がそのまま含まれる。これを利用することで、丸められてしまったNumber型の値の代わりに、元の正確な文字列からBigInt型の値を作り直すことができる。

しかし、BigIntを使う際には次の課題も考慮する必要がある。JavaScriptの標準的なJSON.stringify関数は、BigInt型の値を直接JSONに変換できないため、エラーが発生してしまう。この問題を解決するために、「JSON.rawJSON」という比較的新しいAPIが提案されている。これはBigInt型の値を、JSONの通常の数値として精度を保ったまま文字列化し、出力することを可能にする。ただし、BigIntやJSON.rawJSONは新しい機能であるため、古いブラウザや特定の実行環境ではサポートされていない可能性がある。システムを開発する際には、ターゲットとする環境がこれらの新機能をサポートしているか事前に確認することが重要だ。

もし新しいAPIのサポート状況が不確実な場合や、より幅広い環境で確実に動作させたい場合には、伝統的な二つの方法が有効だ。一つは、サーバー側で大きな整数値をJSONの「数値型」としてではなく、「文字列型」として送信する方法だ。文字列として送信されたデータは、どのJSONパーサーでもその内容が正確に保持される。ブラウザ側では、その文字列を必要に応じてBigIntに変換し、数値計算に利用できる。ただし、文字列のままでは数値としての大小比較(例えばソート処理)が期待通りに機能しない場合があるため、注意が必要だ。もう一つは、タイムスタンプの精度を意図的に落とし、ミリ秒単位など「Number.MAX_SAFE_INTEGER」の範囲内に収まるように丸めてから送信する方法だ。この方法であればJavaScriptのNumber型でも安全に扱えるが、元のナノ秒単位の細かい精度は失われるため、同じミリ秒内のわずかな違いを検出することはできなくなる。ファイルの変更検知のように厳密なチェックが必要な用途には適さないだろう。

この事例から学ぶべき教訓は、数値を扱う際にはその「精度」と「表現可能な範囲」を常に意識することの重要性だ。特に、ID、タイムスタンプ、ファイルサイズなど、巨大な整数値になる可能性のあるデータをJSONでやり取りする際には、安易にNumber型に依存せず、その特性を理解した上で適切なデータ型を選択する必要がある。

システムエンジニアを目指す皆さんへの実践的な推奨事項としては、まず、サーバー側でNumber.MAX_SAFE_INTEGERを超える可能性のある整数値(ナノ秒タイムスタンプ、64ビットID、巨大ファイルのオフセットなど)は、原則としてJSONの「文字列型」として送信することを基本とすることだ。このアプローチは最も堅牢で、全てのクライアント環境で正確なデータ処理を保証する。次に、ブラウザ側では、受け取った文字列をBigInt型に変換して使うのは、数値としての厳密な比較や計算が必要な場合に限定し、それ以外の場面では文字列のままで扱っても問題ないことが多い。もし既存のAPIが数値をそのまま送っており、その変更が困難な場合は、JSON.parseのreviver関数とcontext.sourceを利用してBigIntに変換し、サーバーに送り返す際にはJSON.rawJSONを使う方法を検討する。ただし、この方法はターゲットとするブラウザや実行環境がこれらの新機能をサポートしているかを十分に確認することが不可欠だ。最後に、開発中のテストプロセスにNumber.isSafeInteger関数を使ったチェックを組み込むことを強く推奨する。これにより、システムが意図せず安全でない整数値を扱ってしまっていないか、開発の早い段階で検出し、問題の発生を未然に防ぐことができるだろう。データの正確な処理はシステムの信頼性の基礎となることを忘れずに、日々の開発に取り組んでほしい。

関連コンテンツ

関連IT用語