Cache Savings Calculator — C source
See what prompt caching saves — uncached vs cached cost over N requests, with the write-premium break-even point.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* cache_savings — uncached vs prompt-cached LLM cost comparison.
*
* Language: C (C11, standard library only)
* Source: CosmoDev polyglot showcase port of the Cache Savings Calculator
* tool, ported from src/lib/cacheSavings.ts (the canonical
* TypeScript implementation).
* Tool: https://dev.cosmolabs.org/tools/cache-savings-calculator
* License: display source — part of CosmoDev's polyglot tool pages.
*
* Design goals:
* - Pure + deterministic; never traps (plain double math, no allocation,
* no division that can fail — the only division guards uncached == 0).
* - Functionally equivalent to the TS reference: same inputs -> same outputs.
* - Self-contained: stdlib only (libm for fmax/ceil).
*
* The TS original takes a full AiModel record but reads only its four pricing
* rates, so this port narrows the parameter to exactly those fields. TS
* signals a missing rate with `null`; C has no nullable double, so every
* optional number is an `md` ("maybe double") {has, value} pair. Any missing
* rate makes every output absent — the caller renders an explanatory empty
* state instead of partial math. All rates are per-1M-token USD, mirroring
* the cost conventions of llmCost.ts.
*
* Numeric mapping: TS `number` is a double, so tokens and hits stay `double`
* (fractional hits clamp up to 1.0 exactly like `Math.max(1, hits)`);
* breakEvenHits is a whole hit count, so it lands in `long long` behind its
* own presence flag.
*/
#include <assert.h>
#include <math.h>
#include <stdio.h>
/* A maybe-double: `has` == 0 mirrors the TS `null` (absent rate). */
typedef struct {
int has;
double value;
} md;
static md md_none(void)
{
md r;
r.has = 0;
r.value = 0.0;
return r;
}
static md md_some(double v)
{
md r;
r.has = 1;
r.value = v;
return r;
}
/* The four per-1M-token USD pricing rates cache_math reads from the TS
* AiModel record. */
typedef struct {
md input_per_m; /* Uncached prompt (input) rate. */
md output_per_m; /* Completion (output) rate. */
md cache_read_per_m; /* Cached prompt read rate. */
md cache_write_per_m; /* Cache write premium rate. */
} model_rates;
/* Request shape (TS CacheInput). */
typedef struct {
double prompt_tokens; /* Prompt (input) tokens per request. */
double output_tokens; /* Completion (output) tokens per request. */
double hits; /* Requests reusing the cached prompt; < 1 counts as 1. */
} cache_input;
/* Result shape. Any missing rate makes every field absent. */
typedef struct {
md uncached; /* hits × (prompt·in$/M + output·out$/M) / 1e6. */
md cached; /* (prompt·write$/M + hits × (prompt·read$/M + output·out$/M)) / 1e6 —
one cache write, `hits` cache reads, output billed every request. */
md savings; /* uncached − cached (negative when caching costs more). */
md savings_pct; /* savings / uncached × 100; 0 when uncached is 0. */
int has_break_even; /* ceil(write$/M / read$/M) when read$/M > 0 — hits needed for
cumulative READ spend to equal ONE write premium; else absent. */
long long break_even_hits;
} cache_math_result;
/* The all-absent result used when any pricing rate is missing. */
static cache_math_result nulled(void)
{
cache_math_result r;
r.uncached = md_none();
r.cached = md_none();
r.savings = md_none();
r.savings_pct = md_none();
r.has_break_even = 0;
r.break_even_hits = 0;
return r;
}
/* Compare uncached vs prompt-cached cost for one model. Any missing rate
* (input, output, cache read, cache write) makes every output absent — the
* caller renders an explanatory empty state instead of partial math. */
cache_math_result cache_math(model_rates model, cache_input in)
{
if (!model.input_per_m.has || !model.output_per_m.has ||
!model.cache_read_per_m.has || !model.cache_write_per_m.has) {
return nulled();
}
double hits = fmax(1.0, in.hits);
double in_t = in.prompt_tokens;
double out_t = in.output_tokens;
double ipm = model.input_per_m.value;
double opm = model.output_per_m.value;
double cr = model.cache_read_per_m.value;
double cw = model.cache_write_per_m.value;
/* One cache write, `hits` cache reads; output tokens are billed on every request. */
double uncached = hits * (in_t * ipm + out_t * opm) / 1000000.0;
double cached = (in_t * cw + hits * (in_t * cr + out_t * opm)) / 1000000.0;
double savings = uncached - cached;
double savings_pct = uncached == 0.0 ? 0.0 : savings / uncached * 100.0;
cache_math_result r;
r.uncached = md_some(uncached);
r.cached = md_some(cached);
r.savings = md_some(savings);
r.savings_pct = md_some(savings_pct);
if (cr > 0.0) {
r.has_break_even = 1;
r.break_even_hits = (long long)ceil(cw / cr);
} else {
r.has_break_even = 0;
r.break_even_hits = 0;
}
return r;
}
/* ------------------------------------------------------------------ */
/* Self-test — the reference vectors shared with cacheSavings.test.ts */
/* (the lock-step contract every port mirrors). */
/* ------------------------------------------------------------------ */
/* Fixture model F: inputPerM 10, outputPerM 50, cacheReadPerM 1,
* cacheWritePerM 12.5. Null one rate to test the unpriced path. */
static model_rates base_rates(void)
{
model_rates m;
m.input_per_m = md_some(10.0);
m.output_per_m = md_some(50.0);
m.cache_read_per_m = md_some(1.0);
m.cache_write_per_m = md_some(12.5);
return m;
}
static cache_input mk_input(double p, double o, double h)
{
cache_input i = { p, o, h };
return i;
}
static int close(double a, double b)
{
return fabs(a - b) < 1e-9;
}
/* Spec vector: 10k in / 1k out / 5 hits -> uncached 0.75, cached 0.425,
* savings 0.325, 43.333...% saved, break-even 13 hits. */
static void test_spec_vector(void)
{
cache_math_result r = cache_math(base_rates(), mk_input(10000.0, 1000.0, 5.0));
assert(r.uncached.has && close(r.uncached.value, 0.75));
assert(r.cached.has && close(r.cached.value, 0.425));
assert(r.savings.has && close(r.savings.value, 0.325));
assert(r.savings_pct.has && close(r.savings_pct.value, 43.3333333333));
assert(r.has_break_even && r.break_even_hits == 13); /* ceil(12.5 / 1) */
}
/* At 1 hit caching LOSES 0.035 — an honest negative saving. */
static void test_negative_saving_at_one_hit(void)
{
cache_math_result r = cache_math(base_rates(), mk_input(10000.0, 1000.0, 1.0));
assert(close(r.uncached.value, 0.15));
assert(close(r.cached.value, 0.185));
assert(close(r.savings.value, -0.035));
assert(close(r.savings_pct.value, -23.3333333333));
assert(r.break_even_hits == 13);
}
/* Each missing rate in turn nulls every field. */
static void test_unpriced_rates_null_everything(void)
{
model_rates variants[4];
int i;
for (i = 0; i < 4; i++)
variants[i] = base_rates();
variants[0].input_per_m = md_none();
variants[1].output_per_m = md_none();
variants[2].cache_read_per_m = md_none();
variants[3].cache_write_per_m = md_none();
for (i = 0; i < 4; i++) {
cache_math_result r = cache_math(variants[i], mk_input(10000.0, 1000.0, 5.0));
assert(!r.uncached.has && !r.cached.has && !r.savings.has &&
!r.savings_pct.has && !r.has_break_even);
}
}
/* hits < 1 counts as 1. */
static void test_hits_below_one(void)
{
cache_math_result r0 = cache_math(base_rates(), mk_input(10000.0, 1000.0, 0.0));
cache_math_result r1 = cache_math(base_rates(), mk_input(10000.0, 1000.0, 1.0));
assert(r0.uncached.value == r1.uncached.value &&
r0.cached.value == r1.cached.value &&
r0.savings.value == r1.savings.value &&
r0.savings_pct.value == r1.savings_pct.value &&
r0.break_even_hits == r1.break_even_hits);
}
/* Zero tokens -> zero costs with 0%, no division error. */
static void test_zero_tokens(void)
{
cache_math_result r = cache_math(base_rates(), mk_input(0.0, 0.0, 5.0));
assert(r.uncached.value == 0.0 && r.cached.value == 0.0 &&
r.savings.value == 0.0 && r.savings_pct.value == 0.0);
assert(r.break_even_hits == 13);
}
/* cache_read 0 -> break-even absent but costs kept (10k×$12.5 + 5×(0 + 1k×$50)). */
static void test_zero_read_rate(void)
{
model_rates m = base_rates();
m.cache_read_per_m = md_some(0.0);
cache_math_result r = cache_math(m, mk_input(10000.0, 1000.0, 5.0));
assert(!r.has_break_even); /* write premium never repaid */
assert(close(r.uncached.value, 0.75));
assert(close(r.cached.value, 0.375));
}
/* ceil(4/2) stays 2 — no rounding up at the exact integer boundary. */
static void test_integer_boundary(void)
{
model_rates m = base_rates();
m.cache_write_per_m = md_some(4.0);
m.cache_read_per_m = md_some(2.0);
cache_math_result r = cache_math(m, mk_input(1000.0, 0.0, 3.0));
assert(r.has_break_even && r.break_even_hits == 2);
}
int main(void)
{
test_spec_vector();
test_negative_saving_at_one_hit();
test_unpriced_rates_null_everything();
test_hits_below_one();
test_zero_tokens();
test_zero_read_rate();
test_integer_boundary();
printf("ok\n");
return 0;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →