---
title: Engineering Challenges of Cryptocurrency Decimal Precision: Choosing Correct Data Types for Bitcoin, Ethereum, and Others
created: 2026-03-04
updated: 2026-03-04
type: summary
tags: ["crypto", "engineering", "notes", "precision", "telegram"]
external: http://go/ui/posts/2113/
---

# Bitcoin & Cryptocurrency Decimal Precision

Great question! Bitcoin actually has **8 decimal places**, not 9. The smallest unit is called a **Satoshi**. Let me break this down:

---

## Bitcoin's Smallest Unit: The Satoshi

| Unit | Value in BTC | Satoshis |
|---|---|---|
| 1 BTC | 1.00000000 | 100,000,000 |
| 1 mBTC (millibit) | 0.00100000 | 100,000 |
| 1 μBTC (microbit / "bit") | 0.00000100 | 100 |
| 1 Satoshi (sat) | 0.00000001 | 1 |

> Named after Bitcoin's pseudonymous creator, **Satoshi Nakamoto**.

So: **1 BTC = 100,000,000 Satoshis (10⁸)**

---

## Other Cryptocurrencies Have Even More Decimal Places

| Cryptocurrency | Smallest Unit | Decimal Places | Smallest Unit Name |
|---|---|---|---|
| **Bitcoin (BTC)** | 10⁻⁸ | **8** | Satoshi |
| **Ethereum (ETH)** | 10⁻¹⁸ | **18** | Wei |
| **Solana (SOL)** | 10⁻⁹ | **9** | Lamport |
| **Cardano (ADA)** | 10⁻⁶ | **6** | Lovelace |
| **Polkadot (DOT)** | 10⁻¹⁰ | **10** | Planck |
| **USDC / USDT (ERC-20)** | 10⁻⁶ | **6** | — |

> ⚠️ Ethereum's **18 decimal places** is the extreme case. 1 ETH = 1,000,000,000,000,000,000 Wei (10¹⁸).

---

## The Engineering Challenge

With 18 decimals (Ethereum), the numbers become **enormous** when stored as integers:

```
1 ETH = 1,000,000,000,000,000,000 Wei
```

This **exceeds the range** of many standard integer types:

| Type | Max Value | Enough for ETH (Wei)? |
|---|---|---|
| `int32` | ~2.1 × 10⁹ | �� No |
| `int64` / `long` | ~9.2 × 10¹⁸ | ⚠️ Barely (single values yes, but sums overflow quickly) |
| `uint64` | ~1.8 × 10¹⁹ | ⚠️ Same problem with aggregation |
| `BigInteger` / `BigInt` | Arbitrary precision | ✅ Yes |
| `NUMERIC(78,0)` in SQL | Up to 78 digits | ✅ Yes |

### Why `int64` Isn't Safe for Ethereum

```python name=overflow_example.py
# 1 ETH in Wei
one_eth = 1_000_000_000_000_000_000  # 10^18

# int64 max
int64_max = 9_223_372_036_854_775_807  # ~9.2 × 10^18

# So 1 ETH fits, but...
# A wallet with 10 ETH:
ten_eth = 10_000_000_000_000_000_000  # 10^19 — OVERFLOWS int64!

# A DeFi protocol holding 1,000,000 ETH:
# 1,000,000 × 10^18 = 10^24 — way beyond int64
```

---

## The Right Approach by Currency Type

### Bitcoin (8 decimals) — `int64` is fine

```python name=bitcoin_money.py
# Bitcoin: 8 decimal places — int64 is safe
# Max BTC supply: 21,000,000 BTC = 2,100,000,000,000,000 satoshis (2.1 × 10^15)
# int64 max: 9.2 × 10^18 — plenty of room

@dataclass
class BitcoinAmount:
    satoshis: int  # int64 is sufficient
    
    @classmethod
    def from_btc(cls, btc: str) -> "BitcoinAmount":
        # Use string input to avoid float precision issues
        from decimal import Decimal
        return cls(satoshis=int(Decimal(btc) * 100_000_000))
    
    def to_btc_display(self) -> str:
        return f"{self.satoshis / 1e8:.8f} BTC"

# 0.00150000 BTC
amt = BitcoinAmount.from_btc("0.0015")
# amt.satoshis = 150000
```

### Ethereum (18 decimals) — Must use BigInteger / arbitrary precision

```python name=ethereum_money.py
# Ethereum: 18 decimal places — MUST use arbitrary precision

@dataclass
class EthereumAmount:
    wei: int  # Python int is arbitrary precision by default
    
    @classmethod
    def from_eth(cls, eth: str) -> "EthereumAmount":
        from decimal import Decimal
        return cls(wei=int(Decimal(eth) * 10**18))
    
    def to_eth_display(self) -> str:
        from decimal import Decimal
        eth = Decimal(self.wei) / Decimal(10**18)
        return f"{eth:.18f} ETH"

# 1.5 ETH
amt = EthereumAmount.from_eth("1.5")
# amt.wei = 1500000000000000000
```

```java name=EthereumAmount.java
import java.math.BigInteger;
import java.math.BigDecimal;

public class EthereumAmount {
    private static final BigInteger WEI_PER_ETH = new BigInteger("1000000000000000000");
    
    private final BigInteger wei;  // MUST be BigInteger, not long
    
    public EthereumAmount(BigInteger wei) {
        this.wei = wei;
    }
    
    public static EthereumAmount fromEth(String eth) {
        BigDecimal ethDecimal = new BigDecimal(eth);
        BigInteger weiValue = ethDecimal.multiply(new BigDecimal(WEI_PER_ETH)).toBigIntegerExact();
        return new EthereumAmount(weiValue);
    }
    
    // 1.5 ETH → wei = 1500000000000000000
}
```

```go name=ethereum_amount.go
package money

import "math/big"

// Go: Use math/big.Int for Ethereum
type EthereumAmount struct {
    Wei *big.Int
}

func NewFromWei(wei *big.Int) *EthereumAmount {
    return &EthereumAmount{Wei: wei}
}

// 1 ETH
// wei := new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)
```

---

## Database Storage

```sql name=crypto_schema.sql
-- For Bitcoin (8 decimals): BIGINT is fine
CREATE TABLE bitcoin_transactions (
    id              BIGSERIAL PRIMARY KEY,
    amount_satoshis BIGINT NOT NULL,           -- max ~9.2 × 10^18, BTC supply fits easily
    created_at      TIMESTAMP NOT NULL
);

-- For Ethereum (18 decimals): Use NUMERIC with enough precision
CREATE TABLE ethereum_transactions (
    id              BIGSERIAL PRIMARY KEY,
    amount_wei      NUMERIC(78, 0) NOT NULL,   -- 78 digits, no decimals (storing in Wei)
    created_at      TIMESTAMP NOT NULL
);

-- Or store as TEXT if your application handles all arithmetic
CREATE TABLE ethereum_transactions_alt (
    id              BIGSERIAL PRIMARY KEY,
    amount_wei      TEXT NOT NULL,              -- "1500000000000000000"
    created_at      TIMESTAMP NOT NULL
);
```

> **Why `NUMERIC(78, 0)`?** The max value of a `uint256` (used in Ethereum/Solidity) is 2²⁵⁶ - 1, which is a 78-digit number. This covers any possible on-chain value.

---

## Summary: Choosing the Right Type

| Currency | Decimals | `int64` Safe? | Recommended Type |
|---|---|---|---|
| USD, EUR, GBP | 2 | ✅ Yes | `int64` (cents) |
| BHD, KWD | 3 | ✅ Yes | `int64` (fils) |
| Bitcoin | 8 | ✅ Yes | `int64` (satoshis) |
| Solana | 9 | ✅ Yes | `int64` (lamports) |
| Cardano | 6 | ✅ Yes | `int64` (lovelace) |
| Polkadot | 10 | ⚠️ Careful with sums | `BigInteger` recommended |
| Ethereum | 18 | ❌ No | `BigInteger` / `NUMERIC(78,0)` |

### The Universal Rule Still Applies

> **Store as integer in the smallest unit. Never use floats. Convert to display format only at the presentation layer.**

The only difference with crypto is that some currencies (especially Ethereum) require **arbitrary-precision integers** instead of `int64`.

---

You may have been thinking of **9 decimal places**, which matches **Solana's Lamports** — so you weren't far off! Would you like to explore how to handle cross-currency conversions between fiat and crypto, or how to design a unified money type that handles both?
