feat(shared): add signed percent and percentage-point formatters (WealthLedger/main#54)

This commit is contained in:
2026-11-08 14:20:49 +03:00
parent d910d624d6
commit 010e3ab8af
3 changed files with 39 additions and 0 deletions
+10
View File
@@ -3,12 +3,22 @@ import { describe, expect, it } from "vitest";
import { import {
formatDate, formatDate,
formatMoney, formatMoney,
formatPercentagePoints,
formatSignedPercent,
formatSnapshotDay, formatSnapshotDay,
formatSnapshotTime, formatSnapshotTime,
instrumentLogoUrl, instrumentLogoUrl,
} from "./display"; } from "./display";
describe("shared display helpers", () => { 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", () => { it("uses padded Russian dates and preserves the UTC date of timestamps", () => {
expect(formatDate("2027-09-10")).toBe("10.09.2027"); expect(formatDate("2027-09-10")).toBe("10.09.2027");
expect(formatDate("2024-02-29")).toBe("29.02.2024"); expect(formatDate("2024-02-29")).toBe("29.02.2024");
+27
View File
@@ -1,3 +1,5 @@
import { decimalFromString } from "./decimal";
const DECIMAL_PART_COUNT = 2; const DECIMAL_PART_COUNT = 2;
const DEFAULT_MINOR_UNITS = 2; const DEFAULT_MINOR_UNITS = 2;
@@ -99,6 +101,29 @@ function formatMoney(
return value.startsWith("-") ? `−${formatted.slice(1)}` : `+${formatted}`; 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) { function isZeroDecimal(value: string) {
const unsigned = value.startsWith("-") ? value.slice(1) : value; const unsigned = value.startsWith("-") ? value.slice(1) : value;
@@ -171,6 +196,8 @@ export {
formatDate, formatDate,
formatDecimal, formatDecimal,
formatMoney, formatMoney,
formatPercentagePoints,
formatSignedPercent,
formatSnapshotDay, formatSnapshotDay,
formatSnapshotTime, formatSnapshotTime,
instrumentLogoUrl, instrumentLogoUrl,
+2
View File
@@ -12,6 +12,8 @@ export {
formatDate, formatDate,
formatDecimal, formatDecimal,
formatMoney, formatMoney,
formatPercentagePoints,
formatSignedPercent,
formatSnapshotDay, formatSnapshotDay,
formatSnapshotTime, formatSnapshotTime,
instrumentLogoUrl, instrumentLogoUrl,