Cross Rates Explained: How to Calculate an Exchange Rate Between Any Two Currencies

6 min read

Cross Rates Explained: How to Calculate an Exchange Rate Between Any Two Currencies

A cross rate is an exchange rate between two currencies that is calculated from each currency's rate against a third one, usually USD or EUR. If you need BRL to JPY and nobody quotes that pair directly, you divide one USD rate by the other. That is the whole idea, and the rest of this article is about doing it correctly.

Cross rates matter because the number of possible pairs among about 170 currencies is huge, while only a small set of them trades with real liquidity. Everything else is derived. If your checkout, invoice or dashboard supports "any currency to any currency," you are already relying on cross rates, whether you calculate them yourself or get them from an API.

What a cross rate is, with an example

A direct rate comes from a market where the two currencies are traded against each other, such as EUR/USD. A cross rate is built from two direct rates that share a common currency, often called the pivot or vehicle currency.

Say you want to know how many yen one Brazilian real buys. Suppose these are the example rates (illustrative numbers, not live data):

  • 1 USD = 5.00 BRL
  • 1 USD = 150 JPY

Going from BRL to JPY means converting BRL to USD, then USD to JPY:

  1. 1 BRL = 1 / 5.00 = 0.20 USD
  2. 0.20 USD x 150 = 30 JPY

So the BRL to JPY cross rate is 150 / 5.00 = 30. In one line: the quote-side rate divided by the base-side rate, when both are expressed per one unit of the pivot.

The three formulas you need

Almost every cross-rate calculation is one of three shapes. Write A→B for "units of B per one unit of A."

SituationFormulaExample
InverseB→A = 1 / (A→B)If EUR→USD is 1.0842, USD→EUR is about 0.9223
Both quoted against the pivot P as P→A and P→BA→B = (P→B) / (P→A)USD→BRL 5.00, USD→JPY 150, so BRL→JPY = 30
Both quoted as A→P and P→BA→B = (A→P) x (P→B)EUR→USD 1.0842 and USD→JPY 150 give EUR→JPY 162.63

The second and third rows are where most bugs live. Market conventions differ: EUR/USD is quoted as USD per EUR, while USD/JPY is quoted as JPY per USD. If you copy rates from different sources and multiply when you should divide, the result is wrong by a large factor, and it will often still look plausible enough to ship. Always normalize every input into the same "units of quote per one unit of base" direction before combining.

Why the choice of pivot currency matters

You can usually route a pair through more than one pivot. BRL to JPY can go via USD or via EUR. In a frictionless market the answers agree closely, because arbitrage keeps them aligned. In practice they can differ slightly because:

  • The inputs were observed at slightly different moments.
  • Some pairs are thinner than others and update less often.
  • Each leg may carry its own small spread in the underlying market.

For a pair like BRL/JPY, the most liquid legs (USD/BRL and USD/JPY) usually give the most reliable result. For some pairs, such as a crypto asset against a small fiat currency, the route might be asset to BTC to USD to fiat. The more legs you chain, the more inputs can be out of date, and the more rounding opportunities you create.

Two rules keep this safe:

  1. Do not round between legs. Keep full precision until the final amount.
  2. Treat the result as being as old as its oldest input. A cross rate built from a 5-second-old leg and a 4-minute-old leg is a 4-minute-old rate.

Calculating cross rates in code

Never use binary floats for this. Division is exactly where floating-point error shows up, and it then multiplies through every amount. Use a decimal type. Here is a minimal Python version, with inputs passed as strings:

from decimal import Decimal, getcontext

getcontext().prec = 28

def cross_via_pivot(pivot_to_base: str, pivot_to_quote: str) -> Decimal:
    """Both rates are 'units per one pivot unit', e.g. USD->BRL and USD->JPY."""
    return Decimal(pivot_to_quote) / Decimal(pivot_to_base)

# Example inputs, not live data
brl_jpy = cross_via_pivot("5.00", "150")
print(brl_jpy)  # 30

For the final money amount, multiply the full-precision rate by the amount and round once, using the rules for the currency you are displaying. The article on currency conversion rounding without floating-point errors covers that last step in detail.

This works, but it means your code has to know which pivot to use, fetch two rates, handle the inverse direction, and track the age of both inputs. That is a fair amount of logic to own for something that is not your product.

Getting a derived rate from one API call

GetRatesAPI does the pivoting for you. Any pair works even when no market quotes it directly: missing pairs are derived through USD, EUR or BTC, and each response tells you which kind you got in the type field.

curl -H "Authorization: ApiKey $GETRATESAPI_KEY" \
  "https://api.getratesapi.com/v1/rates/BRL/JPY"

A response looks like this (example values):

{
  "base": "BRL",
  "quote": "JPY",
  "rate": "30.0412",
  "as_of": "2026-01-02T10:00:00Z",
  "status": "fresh",
  "type": "derived"
}

Three fields are worth reading in your code:

  • type is "direct" or "derived". Useful for logging and for explaining to finance teams where a number came from.
  • as_of is the time of the observation. For a derived rate it is the oldest input, which matches the rule above, so you never see a cross rate that looks fresher than its weakest leg.
  • status is "fresh" or "stale". Fiat is stale after 5 minutes and crypto after 1 minute. Stale rates are flagged rather than withheld, so your code decides what to do with them.

The rate is a JSON string, and it is not rounded. Parse it with a decimal type and round once at the end:

import os
from decimal import Decimal, ROUND_HALF_UP
import requests

resp = requests.get(
    "https://api.getratesapi.com/v1/rates/BRL/JPY",
    headers={"Authorization": f"ApiKey {os.environ['GETRATESAPI_KEY']}"},
    timeout=5,
)
resp.raise_for_status()
data = resp.json()

rate = Decimal(data["rate"])
amount = Decimal("2499.90")  # BRL
jpy = (amount * rate).quantize(Decimal("1"), rounding=ROUND_HALF_UP)  # JPY has no minor unit

if data["status"] == "stale":
    print("warning: rate is stale, as_of =", data["as_of"])
print(jpy)

If you only need the converted amount, the convert endpoint skips the multiplication: GET /v1/convert?from=BRL&to=JPY&amount=2499.90 returns an exact converted_amount for 1 request unit. Details of the single-rate call are in the current rate documentation.

Common cross-rate mistakes

  • Multiplying when you should divide. Check the direction of each input before combining. A quick sanity test: converting 1 unit there and back should return roughly 1.
  • Rounding the rate to 4 decimals before using it. For low-value currencies or large amounts, that shifts the result noticeably. Round the final amount, not the rate.
  • Ignoring the age of the slower leg. A "fresh" headline timestamp on a chained rate is misleading if one leg is old. Use the oldest input's time.
  • Assuming a symmetric inverse is a tradable price. 1 / rate is a mathematical inverse of a market rate. Real bank or card conversions add their own spread and are a different thing from market data.
  • Hardcoding a pair list. Supported codes change. The asset catalog endpoints are free, so validate user-selected currencies against them instead of a static list.

Try it on your own pairs

If you have a pair that your current source does not cover, or you are tired of maintaining pivot logic, you can test it in a few minutes. Create a free account, no credit card needed, at app.getratesapi.com/signup, and use the dashboard playground to request pairs like BRL/JPY or an obscure crypto-to-fiat combination. The full list of supported codes is on the currencies page, and plan limits are on the pricing page.

All articles →
How to Cache Exchange Rates Without Showing Stale Prices

6 min read

How to Cache Exchange Rates Without Showing Stale Prices

Calling a rates API on every page view wastes quota and slows pages. Here is how to cache exchange rates with the right TTL for each use case, and how to fall back safely when the API is unavailable.

Cross Rates: Calculate Any Currency Pair — GetRatesAPI