diff --git a/prisma/migrations/20260210212119_revert_currency_rate_precision_column/migration.sql b/prisma/migrations/20260210212119_revert_currency_rate_precision_column/migration.sql new file mode 100644 index 00000000..a79e37a9 --- /dev/null +++ b/prisma/migrations/20260210212119_revert_currency_rate_precision_column/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `precision` on the `CachedCurrencyRate` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "CachedCurrencyRate" DROP COLUMN "precision"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 27b54fd3..7e98901f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -210,7 +210,6 @@ model CachedCurrencyRate { to String date DateTime rate Float - precision Int lastFetched DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/src/components/AddExpense/AddExpensePage.tsx b/src/components/AddExpense/AddExpensePage.tsx index 03145441..f93d6bd6 100644 --- a/src/components/AddExpense/AddExpensePage.tsx +++ b/src/components/AddExpense/AddExpensePage.tsx @@ -25,7 +25,7 @@ import { UploadFile } from './UploadFile'; import { UserInput } from './UserInput'; import { CurrencyInput } from '../ui/currency-input'; import { CurrencyConversion } from '../Friend/CurrencyConversion'; -import { currencyConversion } from '~/utils/numbers'; +import { currencyConversion, getRatePrecision } from '~/utils/numbers'; import { CURRENCY_CONVERSION_ICON } from '../ui/categoryIcons'; export const AddOrEditExpensePage: React.FC<{ diff --git a/src/components/Friend/CurrencyConversion.tsx b/src/components/Friend/CurrencyConversion.tsx index ba052c87..ae62e63a 100644 --- a/src/components/Friend/CurrencyConversion.tsx +++ b/src/components/Friend/CurrencyConversion.tsx @@ -1,7 +1,7 @@ import React, { type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslationWithUtils } from '~/hooks/useTranslationWithUtils'; import { api } from '~/utils/api'; -import { currencyConversion } from '~/utils/numbers'; +import { MAX_RATE_PRECISION, currencyConversion, getRatePrecision } from '~/utils/numbers'; import { toast } from 'sonner'; import { env } from '~/env'; @@ -55,7 +55,12 @@ export const CurrencyConversion: React.FC<{ useEffect(() => { setAmountStr(toUIString(amount, false, true)); - setRate(editingRate ? editingRate.toFixed(4) : ''); + if (editingRate) { + const precision = getRatePrecision(editingRate); + setRate(editingRate.toFixed(precision)); + } else { + setRate(''); + } if (editingTargetCurrency && isCurrencyCode(editingTargetCurrency)) { setTargetCurrency(editingTargetCurrency); } @@ -63,7 +68,8 @@ export const CurrencyConversion: React.FC<{ useEffect(() => { if (getCurrencyRate.data?.rate) { - setRate(getCurrencyRate.data.rate.toFixed(4)); + const precision = getRatePrecision(getCurrencyRate.data.rate); + setRate(getCurrencyRate.data.rate.toFixed(precision)); } }, [getCurrencyRate.data]); @@ -104,7 +110,7 @@ export const CurrencyConversion: React.FC<{ return; } const [int = '', dec = ''] = raw.split('.'); - const trimmedDec = dec.slice(0, 4); + const trimmedDec = dec.slice(0, 10); const normalized = raw.includes('.') ? `${int}.${trimmedDec}` : int; setRate(normalized); }, []); @@ -153,6 +159,13 @@ export const CurrencyConversion: React.FC<{ } }, [onSubmit, targetCurrency, amountStr, rate, currency, getCurrencyHelpersCached, t]); + const ratePrecision = useMemo(() => { + if (!rate) { + return 0; + } + return getRatePrecision(Number(rate)); + }, [rate]); + return (
{/* From amount */} -
+
-
+
{editingTargetCurrency ? ( @@ -219,14 +232,14 @@ export const CurrencyConversion: React.FC<{
{/* Rate */} -
+
- 1 {currency} = {Number(rate).toFixed(4)} {targetCurrency} + 1 {currency} = {Number(rate).toFixed(ratePrecision)} {targetCurrency} - 1 {targetCurrency} = {(1 / Number(rate)).toFixed(4)} {currency} + 1 {targetCurrency} = {(1 / Number(rate)).toFixed(ratePrecision)} {currency} )} diff --git a/src/server/api/services/currencyRateService.ts b/src/server/api/services/currencyRateService.ts index 0b119742..0126db0d 100644 --- a/src/server/api/services/currencyRateService.ts +++ b/src/server/api/services/currencyRateService.ts @@ -27,6 +27,7 @@ abstract class CurrencyRateProvider { const cachedRate = await this.checkCache(from, to, date); if (cachedRate) { + console.log(cachedRate); return cachedRate; } diff --git a/src/store/addStore.ts b/src/store/addStore.ts index 9a5d29e5..0a4dcbcd 100644 --- a/src/store/addStore.ts +++ b/src/store/addStore.ts @@ -6,7 +6,7 @@ import { DEFAULT_CATEGORY } from '~/lib/category'; import { type CurrencyCode } from '~/lib/currency'; import type { TransactionAddInputModel } from '~/types'; import { shuffleArray } from '~/utils/array'; -import { BigMath, gcd } from '~/utils/numbers'; +import { BigMath } from '~/utils/numbers'; import { cyrb128, splitmix32 } from '~/utils/random'; export type Participant = User & { amount?: bigint }; diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts index d28f0d8e..4d2f52dc 100644 --- a/src/utils/numbers.ts +++ b/src/utils/numbers.ts @@ -259,12 +259,28 @@ export function currencyConversion({ const toDecimalDigits = CURRENCIES[to].decimalDigits; const preMultiplier = BigInt(10 ** Math.max(toDecimalDigits - fromDecimalDigits, 0)); const postMultiplier = BigInt(10 ** Math.max(fromDecimalDigits - toDecimalDigits, 0)); + const precision = getRatePrecision(rate); + const ratePrecisionFactor = 10 ** precision; return BigMath.roundDiv( - amount * preMultiplier * BigInt(Math.round(rate * 10000)), - postMultiplier * 10000n, + amount * preMultiplier * BigInt(Math.round(rate * ratePrecisionFactor)), + postMultiplier * BigInt(ratePrecisionFactor), ); } +export const MAX_RATE_PRECISION = 10; + +export const getRatePrecision = (value: number, maxPrecision = MAX_RATE_PRECISION) => { + const normalized = value.toString().trim(); + if ('' === normalized) { + return 0; + } + const decimalIndex = normalized.indexOf('.'); + if (-1 === decimalIndex) { + return 0; + } + return Math.min(Math.max(normalized.length - decimalIndex - 1, 0), maxPrecision); +}; + export const BigMath = { abs(x: bigint) { return 0n > x ? -x : x;