ACCRINT returns the accrued interest on a security that pays periodic coupons — the
interest that has built up between the security's issue date and its settlement date, and
that a buyer therefore owes the seller on top of the price.
The arithmetic is a rate times a principal times a length of time:
accrued = par × rate × (accrued time, measured in years on the chosen basis)
Written the way coupon markets write it, the year is divided into frequency coupon periods
and the elapsed time is counted in periods rather than in years:
where the sum runs over the quasi-coupon periods touched by the accrual interval, A_i is the
number of accrued days inside period i counted on the basis, and NL_i is the normal
length of period i on the same basis. A period that is entirely inside the interval
contributes exactly 1; the two end periods contribute fractions.
That decomposition is what makes ACCRINT different from its at-maturity sibling
ACCRINTM, which needs no schedule at all: ACCRINTM measures one interval
against one year, while ACCRINT has to reconstruct the coupon schedule backwards from
first_interest, decide which periods the interval crosses, and normalize each piece by that
period's own length. Every hard question about this function is a question about that
reconstruction.
Microsoft's worksheet-function page publishes an equation of the shape above. That page was not
retrieved for this entry — the support host returned HTTP 403 — so the formula printed here is
stated as the standard accrual identity in the Handbook's own voice, not quoted from Microsoft.
The parameter list, the required/optional split and the basis table below are taken from
Microsoft's Learn reference for the same function.
Two lengths, and which one normalizes
The subtle part is NL_i. For a whole interior period there is nothing to decide. For the
period that contains settlement, and for the stub period that contains issue, the
implementation has to choose between:
the actual length of that particular period (which under actual/actual differs from
period to period, because half-years are 181, 182, 183 or 184 days long), and
a canonical length — the length of the last coupon period before first_interest, used
for every fractional piece regardless of where it sits.
The security's first interest (first coupon) date.
Required
settlement
The date the trade settles — where accrual stops.
Required
rate
The security's annual coupon rate.
Required
par
The security's par value.
Required in the documented list
frequency
Coupon payments per year: 1, 2 or 4.
Required
basis
Day-count convention, 0–4.
Optional, defaults to 0
calc_method
Which accrual construction to use.
Optional, defaults to TRUE
rate is annual, frequency divides it. The single most common modelling error in this
family is feeding a per-period rate into an argument that Excel will divide by frequency
again.
basis selects the day-count convention — 0 or omitted = US (NASD) 30/360, 1 =
actual/actual, 2 = actual/360, 3 = actual/365, 4 = European 30/360 — exactly the table on
Microsoft's Learn page for this function, and the same axis described at length on
YEARFRAC. A workbook that omits basis is asking for 30/360, whatever the
instrument's term sheet says.
The date arguments are numeric slots holding Excel date serials, subject to ordinary to-number
coercion; see Coercion and lifting. Microsoft's page
carries the standard warning that dates should be produced by DATE or by other formulas
rather than typed as text.
Try it
Quirks
The calc_method argument
calc_method is documented — on the worksheet-function page, not on the Learn page consulted
here — as a choice of starting line: TRUE accrues from issue, FALSE accrues from
first_interest.
Two consequences follow, and both are visible without any last-bit machinery:
Accrual is not monotone in issue date under FALSE. A bond issued three periods before
first_interest can accrue less than one issued a single period before, because the
intervening whole periods vanish.
Result and edge cases
Returns a Number: an amount of currency in the same units as par, not a rate and not a
percentage.
Settlement exactly on a coupon date is the interesting boundary. Under the reference
engine's TRUE path the final period is always measured as days divided by the canonical
length, so it stays a fraction rather than snapping to a whole period.
Month-end and leap-day dates are where the schedule reconstruction earns or loses its
accuracy: the backward walk from first_interest preserves month-end anchoring, so a coupon
dated the 30th of a 30-day month and one dated the 31st generate different schedules.
Fractional arguments. Date and basis arguments are truncated toward zero before use.
Microsoft's Learn reference for this function documents no error conditions at all — it carries
only the parameter table and the basis table. What follows is therefore the reference
engine's behaviour, recorded as such, plus the conditions Microsoft documents for the sibling
functions in this family:
How to implement
Numerical notes
The difficulty here is not floating point in the usual sense — there is no cancellation
catastrophe and no argument reduction. It is combinatorial: getting the same sequence of
small quotients that Excel forms, and then adding them in the same order.
Schedule generation is the algorithm. Walking back from first_interest by
12/frequency months, with month-end anchoring, is the whole of the hard part. Two
defensible month-arithmetic rules (clamp the day of month, versus remember that the anchor
was a month end) produce schedules that differ by a day several times a year.
Two 30/360 implementations will eventually disagree. The Handbook's own research record
documents a case where two routines implementing "the same" US 30/360 rule differed only in
which adjustment they applied first, and disagreed by one day on month-end pairs. Share one
day-count routine; do not re-derive it per family.
The canonical-versus-actual period length choice (above) is a semantic choice with
numerical consequences far larger than one ULP. It belongs in the specification, not in the
rounding discussion.
There is no known closed form that avoids the schedule for a general issue; attempts to
shortcut it with a single YEARFRAC are correct only within one period.
Rust code
Show the CalcFunc source of this function
Quoted from the public repository DnaCalc/DnaCalc, crates calc-func and calc-fml, at commit 9e1884369a67. Comments are the source's own. A kernel shared with other functions appears once, on its module page.
/// F# `changeMonth` with an explicit return-last-day flag (.NET `AddMonths`/// semantics, or force the target month-end when `last_day` is set).fnchange_month_flag(s: i64, months: i64, last_day: bool) -> Option<i64> {
let (y, mo, d) = ymd_from_excel_serial(WorkbookDateSystem::System1900, s as f64)?;
let idx = y
.checked_mul(12)?
.checked_add(mo - 1)?
.checked_add(months)?;
let ty = idx.div_euclid(12);
let tm = idx.rem_euclid(12) + 1;
let td = if last_day {
dim(ty, tm)
} else {
d.min(dim(ty, tm))
};
excel_serial_from_ymd(WorkbookDateSystem::System1900, ty, tm, td).map(|v| v as i64)
}
/// F# `findPcdNcd`: walk `start` toward `end` by `num_months` steps, returning the/// bounding pair (the position at/past `end`, and the one before it).fnfind_pcd_ncd_accr(
start: i64,
end: i64,
num_months: i64,
last_day: bool,
) -> Result<(i64, i64), BondCoreEvalError> {
letmut front = start;
letmut trailing = end;
loop {
let stop = if num_months > 0 {
front >= end
} else {
front <= end
};
if stop {
returnOk((front, trailing));
}
trailing = front;
front =
change_month_flag(front, num_months, last_day).ok_or(derr(WorksheetErrorCode::Num))?;
}
}
/// F# `actualCoupDays pcd firstInterest`: the actual length of the coupon period/// (on `first`'s schedule) that bounds `pcd`. Works in both regimes — `pcd` a/// period before `first` (odd first coupon) or after it (settlement past the first/// interest date), where the bounding period differs.fnactual_coup_days_accr(
pcd: i64,
first: i64,
num_months: i64,
last_day: bool,
) -> Result<f64, BondCoreEvalError> {
let (prev, next) = find_pcd_ncd_accr(first, pcd, -num_months, last_day)?;
Ok(act(prev, next))
}
pubfnaccrint_kernel(
issue: f64,
first_interest: f64,
settlement: f64,
rate_: f64,
par: Option<f64>,
frequency: f64,
basis_: Option<f64>,
calc_method: Option<bool>,
) -> Result<f64, BondCoreEvalError> {
let issue = dser(issue)?;
let first = dser(first_interest)?;
let settlement = dser(settlement)?;
let rate_ = rate(rate_)?;
let par = pos(par.unwrap_or(1000.0))?;
let f = freq(frequency)?;
let basis_ = basis(basis_.unwrap_or(0.0))?;
// calc_method: TRUE = FromIssueToSettlement (Excel default), FALSE = FromFirstToSettlement.let from_issue = calc_method.unwrap_or(true);
if !(issue < first && issue < settlement) {
returnErr(derr(WorksheetErrorCode::Num));
}
// Identified against 145,620 live-Excel witnesses (W109 G6-02, agent-W; b39/b40/b42),// 99.99% bit-exact — plain SSE2 `f64` throughout (x87 emulation is strictly worse).// Excel normalises the settlement-side fraction by the *canonical* last coupon period// length `CoupDays(first - 1 period, first)` (fixes the ~0.07% act/act error of the prior// kernel — BUG-FUNC-030). Two calc_method paths, both accruing from `issue`:// * calc_method TRUE (from_issue) => period-aware WALK: interior whole periods = 1.0,// the act/act issue stub uses its own actual length, and the settlement-side period is// always days/canonical (stays fractional even when settle is on a coupon date). The// forward-collected terms are summed BACKWARD (settlement -> issue).// * calc_method FALSE => FLAT days(issue->settle)/canonical, EXCEPT when issue// sits in a coupon period earlier than `pcd`: the accrual then counts only the stub in// issue's own grid period plus the span from `pcd` to settle, and SKIPS every whole// coupon period between them (the defining legacy calc_method=FALSE behaviour).let fc = f as f64;
let num_months = 12 / f;
let end_flag = is_month_end(first);
let pcd =
change_month_flag(first, -num_months, end_flag).ok_or(derr(WorksheetErrorCode::Num))?;
let canonical = coup_days_accr(pcd, first, num_months, end_flag, fc, basis_)?;
// W109 G6-02 publication identification (build 20228/CV2 NoCache): the// coupon and accrual fraction are both stored binary64 values, but their// final product is an x87 PC64 multiply followed by the binary64 result// store. This rare double-rounding site is decisive on the b43 rate ladder// and the frozen publication held-out; all earlier day-count arithmetic// remains the identified plain-SSE2 graph.let coupon = par * rate_ / fc;
if !from_issue {
// ---- calc_method FALSE: FLAT fraction (with the whole-period skip). ----let a = if issue < pcd {
// issue in a coupon period earlier than the canonical (last-before-first) period.// Accrue the stub in issue's own grid period [B0,B1]; SKIP the whole grid periods// [B1, pcd] entirely; measure the remainder from pcd. Two divisions, summed.let (b0, b1) = issue_period_grid(issue, first, num_months, end_flag)?;
let l_issue = match basis_ {
DayCountBasis::ActualActual => act(b0, b1),
DayCountBasis::Actual365 => 365.0 / fc,
_ => 360.0 / fc,
};
let stub = days_between_num(issue, b1, basis_)? / l_issue;
let rest = days_between_num(pcd, settlement, basis_)? / canonical;
stub + rest
} else {
days_between_num(issue, settlement, basis_)? / canonical
};
returnOk(crate::ported::excel_numeric::excel_x87_mul(coupon, a));
}
// ---- calc_method TRUE: period walk accruing from issue. ----if settlement <= first {
let first_date = issue.max(pcd);
let days = days_between_num(first_date, settlement, basis_)?;
letmut a = days / canonical;
// datesAggregate1: walk pcd back toward issue, one quasi-coupon period per step.letmut front = pcd;
while front > issue {
let ncd_i = front;
let pcd_i = change_month_flag(front, -num_months, end_flag)
.ok_or(derr(WorksheetErrorCode::Num))?;
front = pcd_i;
if issue <= pcd_i {
a += 1.0;
} else {
// The period that contains `issue`: a partial forward fraction.let fd = issue.max(pcd_i);
let days_i = match basis_ {
DayCountBasis::Us30_360 => diff360_us(fd, ncd_i, false)?,
_ => days_between_num(fd, ncd_i, basis_)?,
};
let coup_days_i = match basis_ {
DayCountBasis::Us30_360 => diff360_us(pcd_i, ncd_i, true)?,
DayCountBasis::ActualActual => act(pcd_i, ncd_i),
DayCountBasis::Actual365 => 365.0 / fc,
_ => days_between_denum(pcd_i, ncd_i, basis_)?,
};
a += days_i / coup_days_i;
}
}
returnOk(crate::ported::excel_numeric::excel_x87_mul(coupon, a));
}
// Settlement past the first interest date: forward-collect the period terms, then sum// them BACKWARD (settlement side first — the direction Excel accumulates). Interior// COMPLETE periods contribute exactly 1.0; the act/act issue stub uses its own actual// length; the FINAL period touching settlement is always days/canonical (it stays// fractional even when settlement lands exactly on the period-end coupon date).letmut p_start = first;
while p_start > issue {
p_start = change_month_flag(p_start, -num_months, end_flag)
.ok_or(derr(WorksheetErrorCode::Num))?;
}
letmut terms: Vec<f64> = Vec::new();
while p_start < settlement {
let p_end = change_month_flag(p_start, num_months, end_flag)
.ok_or(derr(WorksheetErrorCode::Num))?;
let ov_start = issue.max(p_start);
let ov_end = settlement.min(p_end);
if ov_end > ov_start {
let is_last = ov_end == settlement;
if ov_start == p_start && ov_end == p_end && !is_last {
terms.push(1.0);
} else {
let denom = ifmatches!(basis_, DayCountBasis::ActualActual)
&& ov_start == issue
&& issue > p_start
{
act(p_start, p_end)
} else {
canonical
};
terms.push(days_between_num(ov_start, ov_end, basis_)? / denom);
}
}
p_start = p_end;
}
letmut a = 0.0;
for t in terms.iter().rev() {
a += *t;
}
Ok(crate::ported::excel_numeric::excel_x87_mul(coupon, a))
}
No implementation has been published with a Handbook test suite for this function.
Version differences
The recorded environments below distinguish a reading from a same-input comparison. Counts describe captured inputs in each environment; overlapping captures and comparisons are not totals of unique worksheet calls.
Excel 16.0 build
Platform and CPU
Captured inputs
20131
Windows, bitness not recorded, AMD Ryzen 7 4800H with Radeon Graphics
145,458
20228
Windows, 64-bit, CPU not recorded
1,230
XC-ACCRINT-001: build 20131 versus build 20228; 780 same-input pairs, 0 differing answers. Compared axes: excel-build. Numeric answers were compared by their recorded binary64 bits; other answers by typed value.
Agreement on these compared inputs does not establish agreement on unmeasured inputs.
Windows, bitness not recorded, AMD Ryzen 7 4800H with Radeon Graphics
145,458 captured inputs are indexed for this environment. Capture dates: 2026-07-19 to 2026-07-20. This is a reading count, not a comparison with another build.
Excel 16.0 build 20228
Windows, 64-bit, CPU not recorded
1,230 captured inputs are indexed for this environment. Capture dates: 2026-08-09 to 2026-08-09. This is a reading count, not a comparison with another build.
Alternatives
Relationships
ACCRINTM — the same question for a security that pays all its interest
at maturity. No coupon schedule, no frequency, no calc_method; one interval over one year
fraction. If you are unsure which you need, the test is whether the instrument pays coupons
before maturity.
The coupon-date functions — COUPPCD, COUPNCD,
COUPDAYBS, COUPDAYS — expose the schedule that
ACCRINT reconstructs internally. They are the right instruments for auditing an ACCRINT
result: if COUPDAYBS/COUPDAYS disagrees with the fraction implied by ACCRINT, the
disagreement is localized to one of the two.
YEARFRAC — the standalone day-count engine. ACCRINT on a single
whole period is a YEARFRAC computation wearing a coupon schedule.
Confused with: ACCRINTM (above), and with simple rate × par × YEARFRAC(...) hand
arithmetic, which reproduces ACCRINT only when the interval lies inside one coupon period.