Two plausible answers differ by more than a rounding error.
A release engineer needs to upload a 240 MiB build artifact through a link
described as 80 Mbit/s. One estimate says “about 25 seconds.” A deployment
note says “about 3 seconds.” Both numbers are familiar-looking. Neither should be accepted
until the dimensions match.
The difference matters operationally. If someone promises a three-second transfer and uses that number for a rollout window, the upload may become the critical path. If the 25-second estimate is treated as a guarantee, the team may miss sharing, retransmission, and processing time. We need an estimate that can be checked and interpreted.
- Artifact size
- 240 MiB, as reported by the build system.
- Link rate
- 80 Mbit/s, a nominal decimal bit rate.
- Observed clue
- A previous transfer was reported near 31 seconds; its exact byte count and rate source are not yet attached.
- Question
- What ideal time follows from these inputs, and what should we check if a real transfer takes longer?
The units should cancel to the quantity you need.
A dimension describes the kind of quantity: data amount, time, or rate. A unit gives that quantity a scale: byte, bit, second. The transfer question asks for time. The relationship is:
duration = data amount ÷ transfer rate
The file size is in bytes and the rate is in bits per second, so convert the file to bits first. A byte contains 8 bits:
240 MiB × 1,048,576 B/MiB × 8 bit/B ÷ 80,000,000 bit/s
= 2,013,265,920 bit ÷ 80,000,000 bit/s
= 25.165824 s
The unit check is the important part: MiB cancels with MiB, B cancels with B, and bit cancels with bit. Dividing by bit/s is multiplying by s/bit,
leaving seconds. If a calculation leaves bytes per second when you need seconds, the
conversion is incomplete.
| Step | Calculation | Result |
|---|---|---|
| Binary size to bytes | 240 MiB × 2²⁰ B/MiB | 251,658,240 B |
| Bytes to bits | 251,658,240 B × 8 bit/B | 2,013,265,920 bit |
| Bits to time | 2,013,265,920 bit ÷ 80,000,000 bit/s | 25.165824 s |
Similar abbreviations can encode different scales and different quantities.
The case uses two distinctions. First, lowercase b means bit and uppercase B means byte; 1 B = 8 bit. Second, SI decimal prefixes and IEC
binary prefixes are different: 1 MB = 1,000,000 B, while 1 MiB = 1,048,576 B. The BIPM defines mega as 10⁶; NIST lists
mebi as 2²⁰.
So 80 Mbit/s means 80 million bits per second. It does not mean 80 megabytes
per second. If the same rate were written as decimal megabytes per second, it would be 10 MB/s, since 80 Mbit/s ÷ 8 = 10 MB/s. For the exact artifact, 240 MiB = 251.65824 MB; dividing that by 10 MB/s again gives 25.165824 s.
Binary multiple of bytes: 2²⁰ B per MiB.
Decimal multiple of bits per second: 10⁶ bit/s per Mbit/s.
Convert the stored bytes to the bits counted by the link rate.
Divide bits by bits per second to leave a duration.
- BIPM SI prefixes lists decimal prefixes including mega = 10⁶.
- NIST binary prefixes defines MiB = 2²⁰ bytes and distinguishes binary from decimal multiples.
References checked 2026-10-01. The source definitions settle the unit scales; they do not tell us the link's usable throughput.
A 31-second upload is a clue about the assumptions, not a root cause.
The ideal estimate is about 25.17 seconds. If the observed transfer took around 31
seconds, that does not show that the arithmetic is wrong. The nominal 80 Mbit/s may not be fully available to this upload; the file may be sent with protocol overhead, retransmitted,
or competing with other traffic. There may also be time outside transfer itself, such as connection
setup, checksum verification, or storage writes.
The current clue is weak: “around 31 seconds” has no exact start and end events, byte count, or rate source. To distinguish explanations, keep the exact artifact checksum/size and timestamp boundaries, then measure application goodput for an isolated transfer. If goodput is near 80 Mbit/s but wall-clock time is longer, inspect time spent before and after transfer. If it is lower, inspect sharing, retransmission, and the path before changing compression or rollout policy.
Make the units part of the function contract.
The examples accept a byte count and a bit rate, reject negative sizes and non-positive rates, and return seconds. Each has an explicit MiB-to-byte conversion. Their output is an ideal lower-bound estimate, not observed transfer performance. The Go version uses integer storage for input quantities and floating point for the final duration; very large values may lose some integer precision at that conversion.
Both versions keep the byte-to-bit factor explicit and validate the rate.
/**
* Estimate an ideal lower-bound transfer time from a byte count and a decimal
* network rate. Real transfers also spend time on protocol and network effects.
*/
export function estimateTransferSeconds(bytes: number, bitsPerSecond: number): number {
if (!Number.isSafeInteger(bytes) || bytes < 0) {
throw new RangeError('bytes must be a non-negative safe integer');
}
if (!Number.isSafeInteger(bytes * 8)) {
throw new RangeError('converted bit count must be a safe integer');
}
if (!Number.isFinite(bitsPerSecond) || bitsPerSecond <= 0) {
throw new RangeError('bitsPerSecond must be a finite, positive number');
}
const bits = bytes * 8;
const seconds = bits / bitsPerSecond;
if (!Number.isFinite(seconds)) {
throw new RangeError('estimated duration is outside the supported numeric range');
}
return seconds;
}
export function mebibytesToBytes(mebibytes: number): number {
if (!Number.isFinite(mebibytes) || mebibytes < 0) {
throw new RangeError('mebibytes must be a finite, non-negative number');
}
const bytes = mebibytes * 2 ** 20;
if (!Number.isSafeInteger(bytes)) {
throw new RangeError('converted size must be a safe integer number of bytes');
}
return bytes;
}
const artifactBytes = mebibytesToBytes(240);
const idealSeconds = estimateTransferSeconds(artifactBytes, 80_000_000);
console.log(`${artifactBytes} B · ${idealSeconds.toFixed(2)} s ideal minimum`);
package main
import (
"fmt"
"math"
)
const bytesPerMebibyte int64 = 1 << 20
// EstimateTransferSeconds returns an ideal lower-bound estimate. The bandwidth
// argument is a decimal bit rate; real transfers include protocol and network effects.
func EstimateTransferSeconds(byteCount int64, bitsPerSecond int64) (float64, error) {
if byteCount < 0 {
return 0, fmt.Errorf("byte count must be non-negative")
}
if bitsPerSecond <= 0 {
return 0, fmt.Errorf("bit rate must be positive")
}
seconds := float64(byteCount) * 8 / float64(bitsPerSecond)
if math.IsInf(seconds, 0) || math.IsNaN(seconds) {
return 0, fmt.Errorf("estimated duration is outside the supported numeric range")
}
return seconds, nil
}
func main() {
const artifactMiB int64 = 240
artifactBytes := artifactMiB * bytesPerMebibyte
seconds, err := EstimateTransferSeconds(artifactBytes, 80_000_000)
if err != nil {
panic(err)
}
fmt.Printf("%d B · %.2f s ideal minimum\n", artifactBytes, seconds)
}
Carry the method to another unit label and a boundary case.
- Artifact
- 750 MB, explicitly decimal: 750,000,000 bytes.
- Link rate
- 100 Mbit/s, explicitly decimal: 100,000,000 bits per second.
- Task
- Calculate the ideal minimum in seconds. Then explain what an observed 74 seconds could and could not tell you.
Show a worked answer
750,000,000 B × 8 bit/B = 6,000,000,000 bit. Divide by 100,000,000 bit/s to get 60 s. This assumes all nominal bandwidth is available for payload
continuously.
An observed 74 seconds is consistent with lower effective throughput or time outside the transfer, but does not distinguish them. Capture bytes and stage timings, then measure payload throughput before recommending a change.