diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..d132390 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# bigint-quantile + +Easily calculate quantiles for large datasets. + +## Installation + +```bash +$ npm install bigint-quantile +``` + +## Usage + +```ts +import { calculateQuantile } from "bigint-quantile"; + +const data: bigint[] = [1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n]; +const quantile = 0.5; + +const result = calculateQuantile(data, quantile); +``` diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..d9938df --- /dev/null +++ b/biome.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.1/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": ["dist/**/*"] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/build.config.ts b/build.config.ts new file mode 100644 index 0000000..bd3382b --- /dev/null +++ b/build.config.ts @@ -0,0 +1,11 @@ +import { defineBuildConfig } from "unbuild"; + +export default defineBuildConfig({ + entries: ["./src/index"], + outDir: "dist", + declaration: true, + failOnWarn: false, + rollup: { + emitCJS: true, + }, +}); diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..47d072b Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..820dac7 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "bigint-quantile", + "type": "module", + "version": "0.0.1", + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.ts", + "files": ["dist"], + "scripts": { + "build": "bunx unbuild", + "test": "bun test", + "test:coverage": "bun test --coverage", + "lint": "bunx biome check .", + "format": "bunx biome check . --write --unsafe" + }, + "devDependencies": { + "@biomejs/biome": "1.9.1", + "@types/bun": "latest", + "unbuild": "^2.0.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/src/index.spec.ts b/src/index.spec.ts new file mode 100644 index 0000000..96cd616 --- /dev/null +++ b/src/index.spec.ts @@ -0,0 +1,32 @@ +import { expect, it } from "bun:test"; +import { calculateQuantile } from "./"; + +const data: bigint[] = [1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n]; + +it("should calculate 25th percentile / first quartile", () => { + const firstQuartile = calculateQuantile(data, 0.25); + expect(firstQuartile).toBe(3n); +}); + +it("should calculate 50th percentile / median", () => { + const median = calculateQuantile(data, 0.5); + expect(median).toBe(5n); +}); + +it("should calculate 75th percentile / third quartile", () => { + const thirdQuartile = calculateQuantile(data, 0.75); + expect(thirdQuartile).toBe(7n); +}); + +it("should throw error if quantile is not between 0 and 1", () => { + expect(() => calculateQuantile(data, 1.1)).toThrowError( + "Quantile must be between 0 and 1", + ); + expect(() => calculateQuantile(data, -0.1)).toThrowError( + "Quantile must be between 0 and 1", + ); +}); + +it("should return 0 if data is empty", () => { + expect(calculateQuantile([], 0.5)).toBe(0n); +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..8faeb21 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,14 @@ +export const calculateQuantile = (data: bigint[], q: number): bigint => { + if (q < 0 || q > 1) throw new Error("Quantile must be between 0 and 1"); + const sorted = data.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); + const pos = (sorted.length - 1) * q; + const base = BigInt(Math.floor(pos)); + const rest = pos - Math.floor(pos); + if (sorted[Number(base)] === undefined) return 0n; + const result = sorted[Number(base)]; + if (rest === 0) { + return result; + } + const next = sorted[Number(base) + 1] || sorted[Number(base)]; + return result + ((next - result) * BigInt(Math.round(rest * 100))) / 100n; +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ffc08ab --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}