A stock split can make a perfectly flat earnings history look like a collapse. A reverse split can make the same history look like an explosion. Neither impression is economic. Both come from comparing earnings per share measured in different share units.
The remedy is retrospective EPS restatement.
For a split factor :
| Symbol | Meaning | Unit |
|---|---|---|
| Exact post-split shares divided by pre-split shares | dimensionless | |
| Earnings available to ordinary shareholders | currency | |
| Completed weighted-average shares on the source basis | shares | |
| Weighted-average shares on the target basis, | shares |
Shares follow the factor. EPS moves inversely. The earnings numerator does not change.
A share is a unit
Suppose a company earned USD 8 million with 2 million weighted-average ordinary shares. Basic EPS is USD 4:
Now it performs a 2-for-1 split. Each old share becomes two new shares. There are 4 million shares on the new basis, but the same shareholders collectively own the same company and the same USD 8 million of historical earnings.
The comparable historical EPS is:
Nothing was lost. The ownership unit became smaller, so twice as many units share the same earnings.
Why the adjustment is retrospective
IAS 33 paragraph 64 and FASB Statement No. 128 paragraph 54 require Basic and diluted EPS to be adjusted retrospectively for share splits and reverse splits for all periods presented. The rule also reaches qualifying events after the reporting period but before the statements are authorised or issued.
That timing surprises developers who treat the period-end date as a hard data cutoff.
In the canonical example:
- FY2025 ends on 2025-12-31;
- a 2-for-1 split becomes effective on 2026-01-15;
- the statements are authorized on 2026-02-20.
The FY2025 per-share figures are presented on the new basis even though the split occurred in 2026. The fact that the per-share calculations reflect the change is disclosed.
This is not look-ahead trading data. It is a financial-statement presentation rule applied at the authorization date.
The timeline uses synthetic dates and amounts. It explains the rule; it is not presented as an issuer event.
Forward and reverse directions
A forward split has . A 3-for-2 split has:
The denominator becomes one and a half times larger; EPS becomes two thirds of its old amount.
A consolidation or reverse split has . A 1-for-5 consolidation has:
The denominator becomes one fifth as large; EPS becomes five times larger in absolute magnitude.
The sign does not change. A loss of USD 0.50 per share on the old basis becomes a loss of USD 2.50 per share on a 1-for-5 basis:
Calling that an “EPS improvement” would be a category error. Total loss is unchanged.
Notice that labels and arrows carry the direction, so the comparison does not depend on color alone.
Basis dates prevent double application
Restatement systems often fail because they know the event but not the basis of the source data.
Suppose a source row already reflects a 2-for-1 split. Multiplying it by two again corrupts every denominator and halves EPS a second time.
The package assigns each row a basis_date. Its meaning is precise:
Every qualifying split or consolidation effective on or before the basis date is already reflected in the row.
An event is selected only when:
Equality means “already reflected.”
The contract also requires basis_is_uniform=true. This matters for a period that spans an event. A weighted-average denominator must first be expressed entirely on one share basis. A mixed denominator containing unadjusted pre-split segments and already adjusted post-split segments cannot safely be corrected by multiplying the whole number once.
Calculation flow
Notice that the source row's basis is read before any factor is applied:
Accessible description: the engine validates a uniform source row, selects only events after its basis date, multiplies exact factors, restates shares, preserves earnings, recalculates EPS, and rounds only the returned display fields.
The reusable source is in visuals/mermaid/restatement-flow.md.
Multiple events compose
Corporate-action history may contain more than one event. Factors multiply.
Consider:
- 3-for-2 split: ;
- later 1-for-4 consolidation: .
The cumulative factor is:
If the original weighted-average denominator was 3 million shares:
With EUR 6 million earnings:
The exact factor is three eighths, not 0.38, 0.3750 with an arbitrary precision, or a chain of rounded intermediate denominators.
A verified filing comparison: Apple’s 2020 split
A historical case is useful only when its evidence layers stay separate. The following comparison uses issuer filings; it does not claim that a market-data vendor revised its database on a particular date.
What the primary sources report
Apple’s 2019 Form 10-K reported, for 2019:
- net income of USD 55,256 million;
- weighted-average basic shares of 4,617,834 thousand; and
- Basic EPS of USD 11.97.
Apple announced a four-for-one split on July 30, 2020, with an August 24 record date and split-adjusted trading beginning August 31. Apple’s 2020 Form 10-K says the split was effected on August 28 and that share and per-share information was retrospectively adjusted. Its comparative 2019 column shows:
- the same USD 55,256 million net income;
- 18,471,336 thousand weighted-average basic shares; and
- Basic EPS of USD 2.99.
| 2019 amount | 2019 filing | 2020 comparative | Interpretation |
|---|---|---|---|
| Net income | USD 55,256m | USD 55,256m | The split did not restate earnings |
| Weighted-average basic shares | 4,617,834k | 18,471,336k | Exactly four times the old share units |
| Basic EPS | USD 11.97 | USD 2.99 | Same earnings expressed per smaller share unit |
What we derive
The share bridge is exact:
Recomputing EPS from the unchanged numerator gives:
This is evidence of a retrospective unit restatement, not evidence that Apple’s 2019 operating earnings fell and not an investment-performance conclusion. A provider-specific statement—such as “vendor X changed its 2019 EPS on date Y”—would need a licensed, timestamped provider snapshot or change log that this package does not have.
Why rational arithmetic is useful
Both reference implementations use arbitrary-precision integers to represent rational numbers:
The 3-for-2 and 1-for-4 factors reduce exactly to 3-for-8. Earnings, unit scales, denominators, and EPS calculations remain rational until the requested display fields are rounded.
This eliminates several avoidable defects:
- binary floating-point ties;
- rounded factors compounded across events;
- denominator rounding before EPS;
- Python and TypeScript disagreement;
- failure to reconcile earnings after restatement.
The core invariant remains:
Display-rounded EPS may not multiply back exactly. That is expected and is why the output includes rounding diagnostics.
A production-minded event contract
Each event declares:
- a stable event ID;
- its effective date;
stock_splitorshare_consolidation;- post-split and pre-split share terms;
- an assertion that it is effective;
- whether the combined transaction changes resources; this engine requires
changes_resources=false.
Events must be unique and strictly date ordered. They cannot be later than the declared financial-statement authorization date.
The algorithm validates direction:
stock_splitmust reduce to a factor greater than one;share_consolidationmust reduce to a factor less than one;- 1-for-1 is rejected.
The legal conclusion that an event is effective—and the accounting conclusion that it is a plain no-resource-change unit event—does not belong inside a math function. The event-source policy records where those upstream conclusions came from; the function rejects changes_resources=true rather than silently treating a special-dividend combination as a plain consolidation.
The calculation path
- Validate the framework, currency, authorisation date, unit scales, rounding policy, and source policies.
- Validate that events are unique, effective, no-resource-change, chronological, and no later than the authorisation cutoff.
- Reduce every event ratio to an exact fraction and confirm that its direction matches its type.
- Require each period row to have one uniform share basis.
- Select only events whose effective date is later than that row's
basis_date. - Multiply the selected factors, preserve earnings, restate shares, and recalculate Basic EPS.
- Round only the output fields and return event lineage, finality, and rounding diagnostics.
What the engine does not restate
This topic preserves the earnings numerator. It does not claim that every historical numerator is eternally fixed. A different accounting change or prior-period correction could require its own numerator restatement.
It also does not complete diluted EPS. Options, warrants, convertibles, share awards, exercise prices, and antidilution conclusions may need corresponding review on the new basis.
Bonus issues and rights issues are nearby but separate topics because their source facts and factors differ.
| Event | Factor source | Treatment here |
|---|---|---|
| Plain stock split | Legal post/pre share ratio | Included |
| Plain share consolidation | Legal post/pre share ratio | Included |
| Bonus issue | Bonus/capitalisation terms | D46-F01-A06 |
| Rights issue with a bonus element | Theoretical ex-rights factor | D46-F01-A07 |
| Cash share issue or repurchase | Consideration and transaction date | Weighted-average-shares workflow |
| Consolidation combined with a special dividend or fair-value repurchase effect | Transaction substance and resource change | IAS 33.29 analysis; excluded from this engine |
Finally, the engine does not decide fractional-share settlement, par value, authorized capital, or journal entries. Those are legal and accounting consequences outside this EPS denominator transformation.
The exclusion in the last row is substantive. IAS 33.29 explains that when a consolidation plus special dividend has the overall effect of a fair-value share repurchase, resources decrease with the share count. In that case, the denominator reduction is recognized from the dividend-recognition date, not applied as a plain no-resource-change consolidation across every prior period.
Testing the restatement
A useful suite should include:
- a forward split inside the presentation window;
- a split after period-end but before authorization;
- a reverse split and negative EPS;
- multiple events whose fraction reduces;
- two rows with different basis dates;
- an event exactly on the basis date;
- no events;
- zero earnings;
- invalid ratios and event directions;
- events after authorization;
- mixed-basis rejection;
- cross-language fixture parity.
The included fixtures cover these structural cases and validate output strings exactly.
They also include the Apple 2019 comparison as a separately labeled documentary regression sourced from the two SEC filings. All other shared cases are synthetic. The documentary case is not evidence of any provider’s ingestion time or adjusted-data convention.
Failure modes to make visible
- Double application: a source row is already split-adjusted, but its
basis_datesays otherwise. - Mixed units: pre-event and post-event segments were averaged before being normalized to one basis.
- Rounded factors: a rational ratio such as is stored as a short decimal and compounded.
- Rounded EPS scaling: a displayed EPS value is divided by instead of recomputing from exact earnings and shares.
- Wrong cutoff: an event after authorisation is pulled into the current invocation.
- Label-only classification: a resource-changing combined transaction is treated as a plain consolidation.
- Diluted-EPS shortcut: instrument terms and antidilution tests are not reconsidered where required.
Reading a restated series
When historical EPS changes after a split, the business has not rewritten history. The unit used to express each share has changed.
For analytical databases, it is helpful to retain:
- original published value and basis;
- restated value and target basis;
- event IDs and factors applied;
- authorization cutoff;
- calculation version;
- disclosure/source links.
That lineage distinguishes a controlled restatement from an unexplained vendor revision.
Closing principle
Stock-split EPS restatement has one economic idea:
Preserve the earnings; re-express the ownership unit.
The implementation becomes trustworthy when it also preserves the dates, basis, exact factor, event lineage, and rounding policy. Then a split no longer creates a fake earnings trend—and a reverse split no longer creates a fake recovery.
See the package README, references, implementations, tests, and visual lab for the complete reproducible system.
Rendered from the canonical Mermaid sources linked by this article.
Post-period authorization window
An effective split after period-end but before financial-statement authorization changes the share basis used for the presented period.
Takeaway: the reporting-period boundary is not the only date that matters.
Restatement calculation flow
The source row’s basis date determines which later effective events are applied.
Takeaway: only the display is rounded; the factor and calculation remain exact.
ReferencesPrimary sources and evidence notesExpand the source trail, evidence role, and limitations behind the engineering choices.
Expand the source trail, evidence role, and limitations behind the engineering choices.
Last reviewed: 2026-07-22.
Primary accounting sources
- IFRS Foundation, IAS 33 Earnings per Share, paragraphs 26–29 and 64. Official issued standard. Paragraphs 26–28 support retrospective denominator adjustment when share count changes without a corresponding change in resources; paragraph 29 identifies the fair-value-repurchase exception for a consolidation combined with a special dividend; paragraph 64 covers qualifying changes after the reporting period but before authorization and the related disclosure. Accessed 2026-07-22. The linked 2022 issued-standard PDF is used because the freely accessible current-standard page summarizes the topic but does not expose the complete licensed text.
- Financial Accounting Standards Board, Statement of Financial Accounting Standards No. 128, Earnings per Share, paragraph 54. Official FASB standard source underlying ASC Topic 260. Supports retroactive adjustment for stock splits and reverse stock splits, post-period/pre-issuance treatment, and disclosure. Accessed 2026-07-19.
- Financial Accounting Standards Board, EITF Issue No. 09-E. Official FASB material explicitly identifying ASC 260-10-55-12 as the stock-dividend/split restatement guidance. Used to connect the original standard language to the Codification location. Accessed 2026-07-19.
- Ategrity Specialty Insurance Company Holdings, Form 10-Q for the quarter ended September 30, 2025, Notes 2, 15, and 18. Primary SEC-filed example of a 1-for-10.66 reverse split and corporate conversion whose share and per-share presentation was retrospectively adjusted. Used as implementation evidence, not as GAAP itself. Accessed 2026-07-22.
Historical case sources
- Apple Inc., 2019 Form 10-K, Note 17. Primary SEC filing. Supports the originally filed 2019 net income of USD 55,256 million, weighted-average basic shares of 4,617,834 thousand, and Basic EPS of USD 11.97. Filed 2019-10-31; accessed 2026-07-22.
- Apple Inc., July 30, 2020 results release. Primary issuer announcement. Supports the four-for-one split approval, August 24, 2020 record date, and August 31, 2020 split-adjusted trading date. Accessed 2026-07-22.
- Apple Inc., 2020 Form 10-K, common-stock-split disclosure and Note 17. Primary SEC filing. Supports the August 28, 2020 effective split, retrospective adjustment of share and per-share information, and the 2019 comparative amounts: unchanged net income of USD 55,256 million, weighted-average basic shares of 18,471,336 thousand, and Basic EPS of USD 2.99. Filed 2020-10-30; accessed 2026-07-22.
Technical sources
- Ecma International, ECMAScript Language Specification — BigInt Objects. Supports the TypeScript implementation’s integer primitive. Accessed 2026-07-22.
- Python Software Foundation, Built-in Types — Numeric Types. Supports Python arbitrary-precision integer behavior. Accessed 2026-07-22.
Applicability and limitations
- The engine covers stock/share splits and share consolidations (reverse splits). Bonus issues are A06; rights-issue bonus factors are A07.
- The source denominator must be uniform on one declared share basis. Constructing a weighted-average denominator from transaction segments belongs to A03.
- The algorithm accepts only legally effective, no-resource-change events through the declared authorization date. Legal effectiveness, transaction classification, and reporting authorization remain upstream judgments.
- Production reporting must be checked against the entity’s complete licensed standards, adopted amendments, jurisdictional rules, facts, and disclosure controls.
- Shared JSON fixtures are synthetic and carry no market-data license. The Apple documentary regression case is separately identified, uses only facts transcribed from public issuer filings, and is not a provider-data snapshot.
- No claim is made about when any market-data vendor ingested, revised, or displayed the Apple figures. Proving a provider revision requires a licensed, timestamped provider snapshot or change log.
- No FMP or other vendor payload is included in this distributable topic. Any future provider QA must remain private and clearance-required unless display and redistribution rights are confirmed.
Full dependency-light reference implementations in both supported languages.
/** Exact stock-split and share-consolidation EPS restatement. */
export type AccountingFramework = "IFRS" | "US_GAAP" | "OTHER_DECLARED";
export type RoundingMode = "half_even" | "half_up";
export type CapitalEventType = "stock_split" | "share_consolidation";
export interface CapitalEvent {
event_id: string;
effective_date: string;
event_type: CapitalEventType;
post_split_shares: string;
pre_split_shares: string;
event_is_effective: boolean;
changes_resources: boolean;
}
export interface EpsPeriod {
period_id: string;
period_start: string;
period_end: string;
basis_date: string;
basis_is_uniform: boolean;
earnings_available_to_ordinary_shareholders: string;
weighted_average_ordinary_shares: string;
is_final: boolean;
}
export interface EpsRestatementInput {
accounting_framework: AccountingFramework;
currency: string;
statements_authorized_for_issue_date: string;
earnings_scale: string;
share_scale: string;
share_decimal_places: number;
eps_decimal_places: number;
rounding_mode: RoundingMode;
restatement_policy: string;
event_source_policy: string;
events: CapitalEvent[];
periods: EpsPeriod[];
}
export interface RestatedPeriod {
period_id: string;
period_start: string;
period_end: string;
source_basis_date: string;
applied_event_ids: string[];
factor_numerator: string;
factor_denominator: string;
earnings_available_to_ordinary_shareholders: string;
pre_restatement_shares: string;
restated_shares: string;
pre_restatement_basic_eps: string;
restated_basic_eps: string;
share_rounding_adjusted: boolean;
eps_rounding_adjusted: boolean;
is_final: boolean;
}
export interface EpsRestatementResult {
metric: "stock_split_consolidation_eps_restatement";
accounting_framework: AccountingFramework;
currency: string;
statements_authorized_for_issue_date: string;
status: "final" | "provisional";
rows: RestatedPeriod[];
}
interface Rational {
numerator: bigint;
denominator: bigint;
}
const DECIMAL_RE = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
const POSITIVE_INTEGER_RE = /^[1-9]\d*$/;
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
export class EpsRestatementValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "EpsRestatementValidationError";
}
}
function abs(value: bigint): bigint {
return value < 0n ? -value : value;
}
function gcd(left: bigint, right: bigint): bigint {
let a = abs(left);
let b = abs(right);
while (b !== 0n) {
[a, b] = [b, a % b];
}
return a;
}
function rational(numerator: bigint, denominator: bigint): Rational {
if (denominator === 0n) throw new Error("rational denominator cannot be zero");
if (denominator < 0n) {
numerator = -numerator;
denominator = -denominator;
}
const divisor = gcd(numerator, denominator);
return {numerator: numerator / divisor, denominator: denominator / divisor};
}
function multiply(left: Rational, right: Rational): Rational {
return rational(
left.numerator * right.numerator,
left.denominator * right.denominator,
);
}
function divide(left: Rational, right: Rational): Rational {
if (right.numerator === 0n) throw new Error("cannot divide by zero");
return rational(
left.numerator * right.denominator,
left.denominator * right.numerator,
);
}
function parseDecimal(value: unknown, field: string): Rational {
if (typeof value !== "string" || !DECIMAL_RE.test(value)) {
throw new EpsRestatementValidationError(
`${field} must be a canonical decimal string`,
);
}
const unsigned = value.startsWith("-") ? value.slice(1) : value;
const [whole, fraction = ""] = unsigned.split(".");
if (whole.length + fraction.length > 30 || fraction.length > 12) {
throw new EpsRestatementValidationError(
`${field} exceeds the decimal size limit`,
);
}
return rational(BigInt(value.replace(".", "")), 10n ** BigInt(fraction.length));
}
function parsePositiveInteger(value: unknown, field: string): bigint {
if (
typeof value !== "string" ||
!POSITIVE_INTEGER_RE.test(value) ||
value.length > 30
) {
throw new EpsRestatementValidationError(
`${field} must be a positive canonical integer string`,
);
}
return BigInt(value);
}
function roundFixed(
value: Rational,
places: number,
mode: RoundingMode,
): [string, boolean] {
const negative = value.numerator < 0n;
const scaled = abs(value.numerator) * 10n ** BigInt(places);
let quotient = scaled / value.denominator;
const remainder = scaled % value.denominator;
const comparison = remainder * 2n - value.denominator;
const increment = comparison > 0n ||
(comparison === 0n &&
(mode === "half_up" ||
(mode === "half_even" && quotient % 2n === 1n)));
if (increment) quotient += 1n;
let digits = quotient.toString();
if (places > 0) {
digits = digits.padStart(places + 1, "0");
const split = digits.length - places;
digits = `${digits.slice(0, split)}.${digits.slice(split)}`;
}
if (negative && quotient !== 0n) digits = `-${digits}`;
return [digits, remainder !== 0n];
}
function terminatingString(value: Rational): string {
let denominator = value.denominator;
let twos = 0;
let fives = 0;
while (denominator % 2n === 0n) {
denominator /= 2n;
twos++;
}
while (denominator % 5n === 0n) {
denominator /= 5n;
fives++;
}
if (denominator !== 1n) throw new Error("expected a terminating decimal");
const scale = Math.max(twos, fives);
let scaled = value.numerator *
2n ** BigInt(scale - twos) *
5n ** BigInt(scale - fives);
const negative = scaled < 0n;
if (negative) scaled = -scaled;
let digits = scaled.toString();
if (scale > 0) {
digits = digits.padStart(scale + 1, "0");
const split = digits.length - scale;
digits = `${digits.slice(0, split)}.${digits.slice(split)}`
.replace(/0+$/, "")
.replace(/\.$/, "");
}
return negative && digits !== "0" ? `-${digits}` : digits;
}
function requireText(value: unknown, field: string): string {
if (typeof value !== "string" || value.trim() === "") {
throw new EpsRestatementValidationError(`${field} must be a non-empty string`);
}
return value;
}
function parseDate(value: unknown, field: string): string {
if (typeof value !== "string" || !DATE_RE.test(value)) {
throw new EpsRestatementValidationError(`${field} must use YYYY-MM-DD`);
}
const parsed = new Date(`${value}T00:00:00Z`);
if (Number.isNaN(parsed.valueOf()) || parsed.toISOString().slice(0, 10) !== value) {
throw new EpsRestatementValidationError(`${field} must be a real date`);
}
return value;
}
export function restateEpsForCapitalEvents(
input: EpsRestatementInput,
): EpsRestatementResult {
if (input === null || typeof input !== "object" || Array.isArray(input)) {
throw new EpsRestatementValidationError("input must be an object");
}
const required: (keyof EpsRestatementInput)[] = [
"accounting_framework", "currency", "statements_authorized_for_issue_date",
"earnings_scale", "share_scale", "share_decimal_places",
"eps_decimal_places", "rounding_mode", "restatement_policy",
"event_source_policy", "events", "periods",
];
for (const field of required) {
if (!(field in input)) {
throw new EpsRestatementValidationError(`missing required field: ${field}`);
}
}
if (!["IFRS", "US_GAAP", "OTHER_DECLARED"].includes(input.accounting_framework)) {
throw new EpsRestatementValidationError("unsupported accounting_framework");
}
if (!/^[A-Z]{3}$/.test(input.currency)) {
throw new EpsRestatementValidationError(
"currency must be a three-letter uppercase code",
);
}
const authorization = parseDate(
input.statements_authorized_for_issue_date,
"statements_authorized_for_issue_date",
);
requireText(input.restatement_policy, "restatement_policy");
requireText(input.event_source_policy, "event_source_policy");
const earningsScale = parseDecimal(input.earnings_scale, "earnings_scale");
const shareScale = parseDecimal(input.share_scale, "share_scale");
if (earningsScale.numerator <= 0n || shareScale.numerator <= 0n) {
throw new EpsRestatementValidationError("unit scales must be positive");
}
for (const [value, field] of [
[input.share_decimal_places, "share_decimal_places"],
[input.eps_decimal_places, "eps_decimal_places"],
] as const) {
if (!Number.isInteger(value) || value < 0 || value > 12) {
throw new EpsRestatementValidationError(
`${field} must be an integer from 0 to 12`,
);
}
}
if (!["half_even", "half_up"].includes(input.rounding_mode)) {
throw new EpsRestatementValidationError(
"rounding_mode must be half_even or half_up",
);
}
if (!Array.isArray(input.events)) {
throw new EpsRestatementValidationError("events must be an array");
}
if (!Array.isArray(input.periods) || input.periods.length === 0) {
throw new EpsRestatementValidationError("periods must be a non-empty array");
}
const events: {id: string; effective: string; factor: Rational}[] = [];
const eventIds = new Set<string>();
let previousDate: string | null = null;
input.events.forEach((raw, index) => {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
throw new EpsRestatementValidationError(`events[${index}] must be an object`);
}
const id = requireText(raw.event_id, `events[${index}].event_id`);
if (eventIds.has(id)) {
throw new EpsRestatementValidationError("event_id values must be unique");
}
eventIds.add(id);
const effective = parseDate(
raw.effective_date,
`events[${index}].effective_date`,
);
if (effective > authorization) {
throw new EpsRestatementValidationError(
"events after authorization are outside this invocation",
);
}
if (previousDate !== null && effective <= previousDate) {
throw new EpsRestatementValidationError(
"events must be strictly ordered by effective_date",
);
}
previousDate = effective;
if (typeof raw.event_is_effective !== "boolean" || !raw.event_is_effective) {
throw new EpsRestatementValidationError("every event must be effective");
}
if (typeof raw.changes_resources !== "boolean") {
throw new EpsRestatementValidationError(
"every event must declare changes_resources",
);
}
if (raw.changes_resources) {
throw new EpsRestatementValidationError(
"resource-changing combined transactions are outside this plain " +
"split/consolidation engine",
);
}
const post = parsePositiveInteger(
raw.post_split_shares,
`events[${index}].post_split_shares`,
);
const pre = parsePositiveInteger(
raw.pre_split_shares,
`events[${index}].pre_split_shares`,
);
const factor = rational(post, pre);
if (raw.event_type === "stock_split" &&
factor.numerator <= factor.denominator) {
throw new EpsRestatementValidationError(
"stock_split factor must be greater than one",
);
}
if (raw.event_type === "share_consolidation" &&
factor.numerator >= factor.denominator) {
throw new EpsRestatementValidationError(
"share_consolidation factor must be less than one",
);
}
if (!["stock_split", "share_consolidation"].includes(raw.event_type)) {
throw new EpsRestatementValidationError("unsupported event_type");
}
events.push({id, effective, factor});
});
const periodIds = new Set<string>();
const rows = input.periods.map((raw, index): RestatedPeriod => {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
throw new EpsRestatementValidationError(`periods[${index}] must be an object`);
}
const periodId = requireText(raw.period_id, `periods[${index}].period_id`);
if (periodIds.has(periodId)) {
throw new EpsRestatementValidationError("period_id values must be unique");
}
periodIds.add(periodId);
const start = parseDate(raw.period_start, `periods[${index}].period_start`);
const end = parseDate(raw.period_end, `periods[${index}].period_end`);
if (start > end) {
throw new EpsRestatementValidationError(
"period_start must not exceed period_end",
);
}
const basis = parseDate(raw.basis_date, `periods[${index}].basis_date`);
if (basis > authorization) {
throw new EpsRestatementValidationError(
"basis_date cannot exceed authorization",
);
}
if (typeof raw.basis_is_uniform !== "boolean" || !raw.basis_is_uniform) {
throw new EpsRestatementValidationError("every period basis must be uniform");
}
if (typeof raw.is_final !== "boolean") {
throw new EpsRestatementValidationError("period is_final must be boolean");
}
const earningsReported = parseDecimal(
raw.earnings_available_to_ordinary_shareholders,
`periods[${index}].earnings_available_to_ordinary_shareholders`,
);
const sharesReported = parseDecimal(
raw.weighted_average_ordinary_shares,
`periods[${index}].weighted_average_ordinary_shares`,
);
if (sharesReported.numerator <= 0n) {
throw new EpsRestatementValidationError(
"weighted_average_ordinary_shares must be positive",
);
}
const earnings = multiply(earningsReported, earningsScale);
const originalShares = multiply(sharesReported, shareScale);
let factor = rational(1n, 1n);
const applied: string[] = [];
for (const event of events) {
if (event.effective > basis) {
factor = multiply(factor, event.factor);
applied.push(event.id);
}
}
const restatedShares = multiply(originalShares, factor);
const preEps = divide(earnings, originalShares);
const restatedEps = divide(earnings, restatedShares);
const [shareDisplay, shareAdjusted] = roundFixed(
restatedShares,
input.share_decimal_places,
input.rounding_mode,
);
const [preEpsDisplay] = roundFixed(
preEps,
input.eps_decimal_places,
input.rounding_mode,
);
const [restatedEpsDisplay, epsAdjusted] = roundFixed(
restatedEps,
input.eps_decimal_places,
input.rounding_mode,
);
return {
period_id: periodId,
period_start: start,
period_end: end,
source_basis_date: basis,
applied_event_ids: applied,
factor_numerator: factor.numerator.toString(),
factor_denominator: factor.denominator.toString(),
earnings_available_to_ordinary_shareholders: terminatingString(earnings),
pre_restatement_shares: terminatingString(originalShares),
restated_shares: shareDisplay,
pre_restatement_basic_eps: preEpsDisplay,
restated_basic_eps: restatedEpsDisplay,
share_rounding_adjusted: shareAdjusted,
eps_rounding_adjusted: epsAdjusted,
is_final: raw.is_final,
};
});
return {
metric: "stock_split_consolidation_eps_restatement",
accounting_framework: input.accounting_framework,
currency: input.currency,
statements_authorized_for_issue_date: authorization,
status: rows.every((row) => row.is_final) ? "final" : "provisional",
rows,
};
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.