0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

暗号資産の数量で string・decimal・最小単位整数をどう使い分けるか

0
Posted at

はじめに

この記事では、暗号資産の数量を扱うときに stringdecimal、最小単位の整数をどう使い分けるべきかを整理します。

この記事でいう decimal は、浮動小数点ではなく任意精度の decimal 型や decimal ライブラリを指します。
DB の DECIMAL 型は保存設計の話として後半で扱います。

暗号資産の実装では、見た目は単なる数値でも、実際には次の違いを意識する必要があります。

  • 表示用の単位か
  • 計算用の最小単位か
  • 外部APIで受け渡す値か
  • DBに保存する値か

ここを曖昧にすると、次のような問題が起きます。

  • 小数が消える
  • 丸め誤差が出る
  • JavaScript の number で桁が壊れる
  • トークンごとの decimal 桁数違いで計算を誤る

特に暗号資産では、1 ETH をそのまま持つのではなく、1000000000000000000 wei のような最小単位で扱う場面が多くなります。

そのため、単に数値として扱うだけでは足りません。どの場面で、どの表現を使うのかを分けて設計する必要があります。

先に結論

実務では、次の分担にすると壊れにくくなります。

  • 数量の入力と出力: string
  • 小数を含む計算: decimal
  • 内部数量: 最小単位の整数

つまり、1つの型で全部やろうとしないことが重要です。

なぜ暗号資産は普通の数値より難しいのか

暗号資産では、銘柄ごとに最小単位が違います。

  • BTC: 1 BTC = 100000000 satoshi
  • ETH: 1 ETH = 1000000000000000000 wei
  • USDC: 1 USDC = 10^6 の最小単位

ここで重要なのは、1.23 という見た目の値だけでは意味が確定しないことです。

  • 1.23 BTC なのか
  • 1.23 ETH なのか
  • USDC の 1.23 なのか
  • それは表示用なのか最小単位なのか

数量だけを裸の数値で持つと、この文脈が消えます。

最小単位を基準にする

暗号資産の数量管理では、内部表現を最小単位に寄せるのが基本です。

例えば 1.5 ETH は内部では次のように持ちます。

1.5 ETH
= 1500000000000000000 wei

この形にすると、少なくとも数量の加算・減算では小数誤差を避けられます。

Mermaid で書くと、流れは次のようになります。

最小単位の整数で持つ

暗号資産の内部数量は、最小単位の整数で持つ方が壊れにくいです。

例えば weisatoshi は、すでに整数として意味が確定しています。

次の用途では、この表現が向いています。

  • オンチェーン数量
  • 残高
  • 送金額
  • 手数料の最小単位
  • コントラクト呼び出し前後の整数計算

TypeScript では bigint を使うことが多いです。

const amountWei = 1500000000000000000n
const feeWei = 21000000000000n
const totalWei = amountWei + feeWei

この形なら、浮動小数点の丸め誤差は入りません。

ただし、最小単位の整数表現にも注意点があります。

  • 小数は持てない
  • JSON へそのままは載せられない
  • 銘柄ごとの decimal 桁数を別で持たないと表示できない

つまり、内部数量には向いていますが、API や画面の表現にはそのまま使いにくいです。

decimal の役割

decimal は、小数を壊さず扱いたい計算で使います。

例えば次のような場面です。

  • 暗号資産と法定通貨の換算
  • レート計算
  • 手数料率の計算
  • 約定価格と数量からの金額算出

例えば ETH価格 × ETH数量 = USD金額 のような計算は、最小単位の整数だけでは扱いにくくなります。

価格: 3521.1284 USD
数量: 0.015 ETH
金額: 52.816926 USD

この種の計算を float64 や JavaScript の number で雑に扱うと、丸め誤差が混ざります。

そのため、レート計算系は decimal ライブラリを使う方が安全です。

ただし decimal も、暗号資産の内部残高表現そのものに使うとは限りません。

実務では次のように分ける方が管理しやすいです。

  • 残高・送金額: 最小単位の整数
  • 価格・換算・表示用の小数計算: decimal

DB保存で気をつけること

DBに保存する場合も、型は慎重に決める必要があります。

最小単位の整数で持つ方針でも、単純に BIGINT へ入れれば十分とは限りません。

例えば ETH の wei やトークン数量は桁が大きくなりやすく、ユースケースによっては BIGINT の範囲に収まらないことがあります。

そのため、保存方法は次のどちらかで検討することが多いです。

  • DECIMAL(65,0) のような整数用 decimal カラムで保存する
  • 文字列として保存する

どちらを選ぶにしても、重要なのは「表示用の小数値」をそのまま曖昧に保存しないことです。

保存時には、表示単位なのか最小単位なのかを明確にしておく必要があります。

string の役割

string は雑に見えますが、境界では非常に重要です。

特に次の場面では、境界データを string のまま扱った方が安全です。

  • API リクエスト
  • API レスポンス
  • フォーム入力
  • CSV 取り込み
  • 外部システム連携

理由は単純で、文字列なら情報が落ちないからです。ドメイン層では、責務に応じて最小単位の整数や decimal へ早めに変換します。

例えばフロントエンドで数量入力を受けるときに、すぐ number に変換すると危険です。

const raw = "0.000000000000000001"
const parsed = Number(raw)

この時点では見た目上問題ないように見えても、別の値や別の計算経路で精度を失う可能性があります。

また、JavaScript の number は整数でも 2^53 - 1 を超えると安全に表現できません。

そのため、フロントからバックエンドへの数量受け渡しは、次の形が扱いやすいです。

{
  "asset": "ETH",
  "amount": "0.015"
}

decimals をクライアントから送らせると、改ざんや不整合の原因になります。
decimal 桁数は、基本的にサーバー側が資産マスタから解決する方が安全です。

あるいは最小単位で統一するなら、次のように送る方法もあります。

{
  "asset": "ETH",
  "amountBaseUnit": "15000000000000000"
}

どちらにしても、JSON では数値ではなく文字列で送る方が安全です。

やってはいけない扱い

暗号資産の数量で壊れやすいパターンはだいたい決まっています。

JavaScript の number にすぐ変換する

これは最も典型的です。

  • 大きい整数で壊れる
  • 小さい小数で丸め誤差が出る
  • 途中の四則演算で意図しない値になる

表示専用なら許容できる場面もありますが、残高計算や送金額計算に使ってはいけません。

decimal 桁数を固定だと思い込む

EVM 系トークンを触っていると、18 decimals を前提にしがちです。

しかし実際には次のようにばらつきます。

  • ETH: 18
  • USDC: 6
  • USDT: 6
  • WBTC: 8

さらに、同名資産でもチェーンが変わると decimal が異なる場合があります。

parseUnits(value, 18) を固定で書くと、USDC のようなトークンで数量が壊れます。

数量には必ず次の文脈を持たせる必要があります。

  • 銘柄
  • decimal 桁数
  • その値が表示単位か最小単位か

最小単位の整数をそのまま画面やAPIに流す

内部では正しくても、境界で扱いにくくなります。

  • JSON 化で失敗する
  • フロント側で扱いづらい
  • 人間に読めない

最小単位の整数は内部表現として使い、境界では string に変換する方が安全です。

string のまま計算し続ける

文字列は情報保持には向いていますが、計算器ではありません。

入力境界で string を受け取り、必要に応じて次へ変換します。

  • 最小単位計算なら整数型
  • 小数計算なら decimal

ここを曖昧にすると、実装のあちこちで変換が始まり、責務が散らばります。

実務で決めておくべきこと

暗号資産の数量設計では、型そのものより先にルールを決める必要があります。

最低限、次の点は固定した方がよいです。

  • 内部の正規形は表示単位か最小単位か
  • API では数量を string で返すか
  • DB には最小単位整数で保存するか
  • decimal 桁数をどこで管理するか
  • 価格計算にどの decimal ライブラリを使うか

特に重要なのは、「数量」と「価格」を同じ型で雑に扱わないことです。

例えば次の分離は有効です。

  • 数量: AssetAmount
  • 価格: Price
  • 法定通貨金額: Money

型名や構造体を分けるだけでも、誤用はかなり減ります。

1つの現実的な設計例

迷ったら、次の運用から始めると安定しやすいです。

  1. 画面やAPIでは数量を string で受け取る
  2. 銘柄の decimal 桁数を見て最小単位の整数へ変換する
  3. 残高や送金額は最小単位の整数のまま保持・保存する
  4. レート計算や法定通貨換算は decimal で行う
  5. 画面表示時だけ最小単位の整数を decimal 文字列へ整形する

この流れなら、入力、内部計算、外部出力の責務を分離しやすくなります。

まとめ

暗号資産の数量で重要なのは、数値を1種類の型で統一することではありません。

重要なのは、値の意味ごとに型を分けることです。

  • 数量の境界入力は string
  • 小数計算は decimal
  • 内部数量は最小単位の整数

そして、どの値にも次の文脈を持たせる必要があります。

  • どの銘柄か
  • decimal は何桁か
  • 表示単位か最小単位か

暗号資産の実装は、数値が壊れてもコンパイルでは気づきにくいです。

だからこそ、「どの型を使うか」よりも先に、「どの意味の値を、どの型で持つか」を決めておくことが重要です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?