
Importance of Satoshis: Precision in Blockchain Transactions
Almost every public blockchain has its native token to represent value. The Bitcoin blockchain has its native token, bitcoin, Ethereum has ether, Solana has sol and so on. Typically blockchains have native tokens due to several reasons, including:
- Incentivize miners or validators to participate in network.
- Charge a transaction fee, which keeps spammers from spamming the network.
- Represent value in a way, provide that value to users and transfer that value.
Why blockchains need a smaller token unit?
Many blockchains have smaller units of its native token. In most cases, users won’t have to deal with these types of hidden details, but for implementation, we have to understand how this works. If you want to transfer 1 bitcoin, it’s simple and doesn’t need any smaller unit. But what if you want to transfer 0.02 bitcoins? You would say that programming languages do support float
and double
values and it could be stored as (in Rust programming language)
let total_bitcoins:f64 = 21_000_000.0;
let value_to_transfer:f64 = 0.02;
f64
is the type of the value being stored. You can run Rust programs directly on Rust Playground
You’d think that problem has been solved. Right? Let’s take a look at another example.
let a: f64 = 0.1;
let b: f64 = 0.2;
let sum: f64 = a + b;
// This will likely print something like 0.30000000000000004, not exactly 0.3
println!("0.1 + 0.2 = {:.17}", sum);
Output is not exactly the 0.3, which it should be. Let’s extend this example a bit to clarify this problem
if sum == 0.3 {
println!("The sum is exactly 0.3");
} else {
println!("The sum is NOT exactly 0.3");
}
If you run this, you’ll see the output
> The sum is NOT exactly 0.3
Why is that? It can make a huge impact when we want to deal with real money. Let’s explore this a bit more.
What is precision and floating-point error?
If you have a scale that marks inches (1, 2, 3), that would be easy to measure inches. A lot of scales also divide inches into smaller segments and mark those segments, so it would be easy to measure 1.4 inches or 3.7 inches. If you want to measure something that’s 1.4567 inches long, your ruler with only tenth-inch markings, can’t show you the exact value.
If you’re a programmer, then you probably would’ve guessed that f32
uses 32-bits to store floating-point numbers and f64
uses 64-bits. So that’s not an unlimited amount of memory. f32
offers less precision than f64
because f64
takes more memory. But it’s still limited memory and anything stored in f64
exceeding its capacity to handle precision, will cause a loss of precision. Let’s take a look at another example
let u64_max: u64 = u64::MAX;
let f64_from_u64_max: f64 = u64_max as f64;
println!("u64::MAX: {}", u64_max);
println!("f64 from u64::MAX: {:.0}", f64_from_u64_max);
The output you’ll see would likely be
u64::MAX: 18446744073709551615
f64 from u64::MAX: 18446744073709551616
We’re getting the MAX
number a 64-bit unsigned integer can store. u64::MAX
value is 264-1. Then we’re converting it to 64-bit floating-point number. You see the last digit is different and we’ve lost the precision. f64
can’t convert to exactly what u64
represented. That difference of ‘1’ is known as precision error. It’s larger than u64::MAX
because it has to round up for nearest possible value, which is 264. f64
can store values upto 253 precisely (due to the 53-bit mantissa for double-precision floats). Beyond that, it approximates values leading to a precision gap, which is quite large because u64
can store upto 264-1.
How does Bitcoin (or other blockchains) handle this?
So we can’t use the float
or double
datatype because blockchains don’t want something that’s unpredictable. And we just saw that it can happen when we deal with the float
or double
datatype. So what’s the solution?
Why not create a smaller unit? That’s why bitcoin has satoshis. But how would it work? 1 bitcoin is equal to 100 million satoshis. If you want to send 1 btc, just send 100 million satoshis, and you even don’t have to calculate it. Wallets and nodes would do all the work.
1 btc = 100_000_000 sats
0.1 btc = 10_000_000 sats
0.01 btc = 1_000_000 sats
Everything is calculated in sats under the hood. It doesn’t make sense to send 0.1 sat or 0.02571 sats, because it doesn’t have any good value. Problem solved and we don’t have to deal with floating-point errors.
Now as we all know bitcoin has only 21 million of max supply. There will be no bitcoin over 21,000,000. How do we store it in a data type? Well let’s do some calculations
1 btc = 108 satoshis Max btc supply = 21 x 106
So maximum satoshis that can exists are 21 x 10 6 x 10 8 = 21 x 10 14 = 2,100,000,000,000,000
That large number is 2.1 quadrillion. A 32-bit datatype can’t store this value, because a u32
will be able to store a number upto 4,294,967,295, which is only 4.2 billion, a much smaller amount. Let’s double the bits and use u64
which will be able to store the number upto 18,446,744,073,709,551,615. Bitcoin only needs 2.1 quadrillion and this is much larger than that. So it makes sense to use this datatype because it’s more than enough for bitcoin’s needs.
Let’s take a look at Ethereum’s example.
Ethereum has Wei as its smallest unit. 1 eth is equal to 1018 wei. It uses data type equivalent to u256
to store the value in 256-bit integer. There are several reasons for that design choice.
- Ethereum doesn’t have fixed maximum supply. It could grow and larger data type provides more headroom.
- Let’s say
u128
was sufficient for their needs. But if you multiplyu128
withu128
, value might be larger thanu128
. - Ethereum virtual machine operates on word size of 256-bit. It’s optimized to process the data in 256-bit chunks. This makes it more efficient to use
u256
.
Conclusion
Every blockchain wants to avoid unpredictable outcomes and this impacts design choices. One can avoid precision and floating-point errors by introducing smaller units of its native token. This all is a part of tokenomics and depends what kind of problem your blockchain solves. That’s the reason bitcoin, ethereum and solana, all of them have different tokenomics and different way of handling its native tokens and smaller units.