mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Add tests and documentation to number utils
Also improved safeNumberParse robustness
This commit is contained in:
parent
5ba85f8db2
commit
5a2fcf7a9f
|
|
@ -1,9 +1,30 @@
|
|||
import * as R from "remeda";
|
||||
|
||||
/**
|
||||
* Rounds a number to a specified number of decimal places.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* roundToNDecimalPlaces(3.14159); // returns 3.14
|
||||
* roundToNDecimalPlaces(3.14159, 3); // returns 3.142
|
||||
* roundToNDecimalPlaces(2.5, 0); // returns 3
|
||||
* ```
|
||||
*/
|
||||
export function roundToNDecimalPlaces(num: number, n = 2) {
|
||||
return Number((Math.round(num * 10 ** n) / 10 ** n).toFixed(n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates a number to a specified number of decimal places without rounding.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* cutToNDecimalPlaces(3.9999, 2); // returns 3.99
|
||||
* cutToNDecimalPlaces(3.12, 1); // returns 3.1
|
||||
* cutToNDecimalPlaces(100, 2); // returns 100
|
||||
* cutToNDecimalPlaces(3.0001, 2); // returns 3
|
||||
* ```
|
||||
*/
|
||||
export function cutToNDecimalPlaces(num: number, n = 2) {
|
||||
const multiplier = 10 ** n;
|
||||
const truncatedNum = Math.trunc(num * multiplier) / multiplier;
|
||||
|
|
@ -11,13 +32,47 @@ export function cutToNDecimalPlaces(num: number, n = 2) {
|
|||
return Number(n > 0 ? result.replace(/\.?0+$/, "") : result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average (arithmetic mean) of an array of numbers.
|
||||
* Returns 0 if the array is empty.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* averageArray([2, 4, 6, 8]); // returns 5
|
||||
* averageArray([-2, -4, -6, -8]); // returns -5
|
||||
* averageArray([10, -10, 20, -20]); // returns 0
|
||||
* averageArray([42]); // returns 42
|
||||
* averageArray([]); // returns 0
|
||||
* ```
|
||||
*/
|
||||
export function averageArray(arr: number[]) {
|
||||
if (arr.length === 0) return 0;
|
||||
|
||||
return R.sum(arr) / arr.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely parses a string into a number, returning `null` if the input is `null`,
|
||||
* empty, or not a valid number.
|
||||
*
|
||||
* Trims whitespace from the input before parsing. If the trimmed string is empty
|
||||
* or cannot be converted to a valid number, returns `null`.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* safeNumberParse("42"); // returns 42
|
||||
* safeNumberParse(" 3.14 "); // returns 3.14
|
||||
* safeNumberParse(""); // returns null
|
||||
* safeNumberParse("abc"); // returns null
|
||||
* safeNumberParse(null); // returns null
|
||||
* ```
|
||||
*/
|
||||
export function safeNumberParse(value: string | null) {
|
||||
if (value === null) return null;
|
||||
|
||||
const result = Number(value);
|
||||
const trimmed = value.trim();
|
||||
if (trimmed === "") return null;
|
||||
|
||||
const result = Number(trimmed);
|
||||
return Number.isNaN(result) ? null : result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,45 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { cutToNDecimalPlaces } from "./number";
|
||||
import { describe, expect, it, test } from "vitest";
|
||||
import {
|
||||
averageArray,
|
||||
cutToNDecimalPlaces,
|
||||
roundToNDecimalPlaces,
|
||||
safeNumberParse,
|
||||
} from "./number";
|
||||
|
||||
describe("roundToNDecimalPlaces()", () => {
|
||||
it("rounds to 2 decimal places by default", () => {
|
||||
expect(roundToNDecimalPlaces(1.234)).toBe(1.23);
|
||||
expect(roundToNDecimalPlaces(1.235)).toBe(1.24);
|
||||
expect(roundToNDecimalPlaces(1.2)).toBe(1.2);
|
||||
expect(roundToNDecimalPlaces(1)).toBe(1);
|
||||
});
|
||||
|
||||
it("rounds to 0 decimal places", () => {
|
||||
expect(roundToNDecimalPlaces(1.6, 0)).toBe(2);
|
||||
expect(roundToNDecimalPlaces(1.4, 0)).toBe(1);
|
||||
expect(roundToNDecimalPlaces(2.5, 0)).toBe(3);
|
||||
});
|
||||
|
||||
it("rounds to 3 decimal places", () => {
|
||||
expect(roundToNDecimalPlaces(1.23456, 3)).toBe(1.235);
|
||||
expect(roundToNDecimalPlaces(1.23444, 3)).toBe(1.234);
|
||||
});
|
||||
|
||||
it("handles negative numbers", () => {
|
||||
expect(roundToNDecimalPlaces(-1.2345, 2)).toBe(-1.23);
|
||||
expect(roundToNDecimalPlaces(-1.2355, 2)).toBe(-1.24);
|
||||
});
|
||||
|
||||
it("handles zero", () => {
|
||||
expect(roundToNDecimalPlaces(0, 2)).toBe(0);
|
||||
expect(roundToNDecimalPlaces(0, 0)).toBe(0);
|
||||
});
|
||||
|
||||
it("handles large numbers", () => {
|
||||
expect(roundToNDecimalPlaces(123456.789, 1)).toBe(123456.8);
|
||||
expect(roundToNDecimalPlaces(123456.789, 0)).toBe(123457);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cutToNDecimalPlaces()", () => {
|
||||
test("cutOff truncates decimal places correctly", () => {
|
||||
|
|
@ -22,3 +62,68 @@ describe("cutToNDecimalPlaces()", () => {
|
|||
expect(result).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("averageArray()", () => {
|
||||
it("returns the average of positive numbers", () => {
|
||||
const result = averageArray([2, 4, 6, 8]);
|
||||
expect(result).toBe(5);
|
||||
});
|
||||
|
||||
it("returns the average of negative numbers", () => {
|
||||
const result = averageArray([-2, -4, -6, -8]);
|
||||
expect(result).toBe(-5);
|
||||
});
|
||||
|
||||
it("returns the average of mixed positive and negative numbers", () => {
|
||||
const result = averageArray([10, -10, 20, -20]);
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
it("returns the value itself for a single-element array", () => {
|
||||
const result = averageArray([42]);
|
||||
expect(result).toBe(42);
|
||||
});
|
||||
|
||||
it("returns 0 for an empty array", () => {
|
||||
const result = averageArray([]);
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("safeNumberParse()", () => {
|
||||
it("returns null for null input", () => {
|
||||
expect(safeNumberParse(null)).toBeNull();
|
||||
});
|
||||
|
||||
it("parses valid integer string", () => {
|
||||
expect(safeNumberParse("42")).toBe(42);
|
||||
});
|
||||
|
||||
it("parses valid float string", () => {
|
||||
expect(safeNumberParse("3.14")).toBe(3.14);
|
||||
});
|
||||
|
||||
it("returns null for non-numeric string", () => {
|
||||
expect(safeNumberParse("abc")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for empty string", () => {
|
||||
expect(safeNumberParse("")).toBeNull();
|
||||
});
|
||||
|
||||
it("parses string with leading/trailing spaces", () => {
|
||||
expect(safeNumberParse(" 7 ")).toBe(7);
|
||||
});
|
||||
|
||||
it("parses negative numbers", () => {
|
||||
expect(safeNumberParse("-123")).toBe(-123);
|
||||
});
|
||||
|
||||
it("parses zero", () => {
|
||||
expect(safeNumberParse("0")).toBe(0);
|
||||
});
|
||||
|
||||
it("returns null for string with only spaces", () => {
|
||||
expect(safeNumberParse(" ")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user