diff --git a/src/shared/lib/display.test.ts b/src/shared/lib/display.test.ts index fae1dce..9658726 100644 --- a/src/shared/lib/display.test.ts +++ b/src/shared/lib/display.test.ts @@ -3,12 +3,22 @@ import { describe, expect, it } from "vitest"; import { formatDate, formatMoney, + formatPercentagePoints, + formatSignedPercent, formatSnapshotDay, formatSnapshotTime, instrumentLogoUrl, } from "./display"; describe("shared display helpers", () => { + it("formats signed rates as percentages and percentage points without negative zero", () => { + expect(formatSignedPercent("0.151")).toBe("+15,1 %"); + expect(formatSignedPercent("-0.15149")).toBe("−15,1 %"); + expect(formatSignedPercent("0")).toBe("0,0 %"); + expect(formatSignedPercent("-0.00049")).toBe("0,0 %"); + expect(formatPercentagePoints("0.043")).toBe("+4,3 п.п."); + expect(formatPercentagePoints("-0.0435")).toBe("−4,4 п.п."); + }); it("uses padded Russian dates and preserves the UTC date of timestamps", () => { expect(formatDate("2027-09-10")).toBe("10.09.2027"); expect(formatDate("2024-02-29")).toBe("29.02.2024"); diff --git a/src/shared/lib/display.ts b/src/shared/lib/display.ts index 48b6910..2fb8a0f 100644 --- a/src/shared/lib/display.ts +++ b/src/shared/lib/display.ts @@ -1,3 +1,5 @@ +import { decimalFromString } from "./decimal"; + const DECIMAL_PART_COUNT = 2; const DEFAULT_MINOR_UNITS = 2; @@ -99,6 +101,29 @@ function formatMoney( return value.startsWith("-") ? `−${formatted.slice(1)}` : `+${formatted}`; } +function formatPercentagePoints(rate: string): string { + return formatRate(rate, "п.п."); +} + +function formatRate(rate: string, suffix: string): string { + const { scale, value } = decimalFromString(rate); + const abs = value < 0n ? -value : value; + const radix = 10n; + const half = 2n; + const tenthsPerUnit = 1000n; + const denominator = radix ** BigInt(scale); + const tenths = (abs * tenthsPerUnit + denominator / half) / denominator; + let sign = ""; + if (tenths !== 0n) sign = value < 0n ? "−" : "+"; + const formatted = `${formatDecimal(String(tenths / radix))},${tenths % radix}`; + + return `${sign}${formatted} ${suffix}`; +} + +function formatSignedPercent(rate: string): string { + return formatRate(rate, "%"); +} + function isZeroDecimal(value: string) { const unsigned = value.startsWith("-") ? value.slice(1) : value; @@ -171,6 +196,8 @@ export { formatDate, formatDecimal, formatMoney, + formatPercentagePoints, + formatSignedPercent, formatSnapshotDay, formatSnapshotTime, instrumentLogoUrl, diff --git a/src/shared/lib/index.ts b/src/shared/lib/index.ts index f64e6b3..fb2f8b8 100644 --- a/src/shared/lib/index.ts +++ b/src/shared/lib/index.ts @@ -12,6 +12,8 @@ export { formatDate, formatDecimal, formatMoney, + formatPercentagePoints, + formatSignedPercent, formatSnapshotDay, formatSnapshotTime, instrumentLogoUrl,