From ae3770ae304f4c9d5ee1220e8844e5bc701928ab Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Fri, 12 Apr 2024 12:20:24 -0500 Subject: [PATCH] Refine fraction formatting behavior --- __tests__/exif.test.ts | 12 ++++++++++++ src/utility/number.ts | 44 ++++++++++++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/__tests__/exif.test.ts b/__tests__/exif.test.ts index b3a20c72..ea770f9b 100644 --- a/__tests__/exif.test.ts +++ b/__tests__/exif.test.ts @@ -12,23 +12,35 @@ describe('EXIF', () => { expect(formatExposureTime(1.5)).toBe('1.5s'); }); it('exposure compensation', () => { + expect(formatExposureCompensation(1)).toBe('+1ev'); expect(formatExposureCompensation(-1)).toBe('-1ev'); expect(formatExposureCompensation(0)).toBe(undefined); expect(formatExposureCompensation(0.25)).toBe('+1/4ev'); expect(formatExposureCompensation(0.33)).toBe('+1/3ev'); expect(formatExposureCompensation(0.333)).toBe('+1/3ev'); + expect(formatExposureCompensation(-0.25)).toBe('-1/4ev'); + expect(formatExposureCompensation(-0.33)).toBe('-1/3ev'); + expect(formatExposureCompensation(-0.333)).toBe('-1/3ev'); expect(formatExposureCompensation(0.5)).toBe('+1/2ev'); + expect(formatExposureCompensation(0.4998458896569944)).toBe('+1/2ev'); expect(formatExposureCompensation(0.66)).toBe('+2/3ev'); expect(formatExposureCompensation(0.67)).toBe('+2/3ev'); expect(formatExposureCompensation(0.015625)).toBe('+1/64ev'); expect(formatExposureCompensation(-0.015625)).toBe('-1/64ev'); expect(formatExposureCompensation(1)).toBe('+1ev'); + expect(formatExposureCompensation(1.1)).toBe('+1 1/10ev'); + expect(formatExposureCompensation(-1.1)).toBe('-1 1/10ev'); + expect(formatExposureCompensation(1.7)).toBe('+1 7/10ev'); + expect(formatExposureCompensation(-1.7)).toBe('-1 7/10ev'); expect(formatExposureCompensation(-1.33)).toBe('-1 1/3ev'); expect(formatExposureCompensation(1.33)).toBe('+1 1/3ev'); expect(formatExposureCompensation(1.333)).toBe('+1 1/3ev'); expect(formatExposureCompensation(1.3333)).toBe('+1 1/3ev'); expect(formatExposureCompensation(1.5)).toBe('+1 1/2ev'); + expect(formatExposureCompensation(2.5)).toBe('+2 1/2ev'); + expect(formatExposureCompensation(-2.5)).toBe('-2 1/2ev'); expect(formatExposureCompensation(1.9960938)).toBe('+2ev'); + expect(formatExposureCompensation(-1.9960938)).toBe('-2ev'); // Ignore long fractions expect(formatExposureCompensation(-0.119)).toBe('-0.12ev'); expect(formatExposureCompensation(-0.112340989)).toBe('-0.11ev'); diff --git a/src/utility/number.ts b/src/utility/number.ts index 701e9f15..47fff79b 100644 --- a/src/utility/number.ts +++ b/src/utility/number.ts @@ -14,10 +14,13 @@ const gcd = (a: number, b: number): number => { } }; -const formatDecimalToFraction = (decimal: number) => { - if (Math.abs(decimal - 0.33) < 0.011) { +const formatDecimalToFraction = (_decimal: number) => { + // Prevent imprecision which causes numbers such as, + // 0.1 to equal 0.10000000000000009 + const decimal = parseFloat(_decimal.toPrecision(8)); + if (Math.abs(Math.abs(decimal) - 0.33) < 0.011) { return '1/3'; - } else if (Math.abs(decimal - 0.66) <= 0.011) { + } else if (Math.abs(Math.abs(decimal) - 0.66) <= 0.011) { return '2/3'; } else { const length = decimal.toString().length - 2; @@ -34,22 +37,43 @@ const formatDecimalToFraction = (decimal: number) => { } }; +const STICKY_THRESHOLD = 0.011; +const STICKY_DECIMALS = [0.25, 0.33, 0.5, 0.66, 0.75]; +const MAX_FRACTION_LENGTH = 4; // 1/64 not 1/100 + export const formatNumberToFraction = (number: number) => { - const decimal = (1 - number % 1) > 0.01 + const sign = number >= 0 ? '+' : '-'; + + let decimal = (1 - Math.abs(number % 1)) > STICKY_THRESHOLD ? number % 1 : 0; - const integer = Math.round(Math.abs(number - decimal)); + if (decimal !== 0) { + for (const stickyDecimal of STICKY_DECIMALS) { + if (Math.abs(Math.abs(decimal) - stickyDecimal) < STICKY_THRESHOLD) { + decimal = decimal < 0 ? -stickyDecimal : stickyDecimal; + break; + } + } + } + + let integer = Math.round(Math.abs(number - decimal)); + if (Math.abs(decimal) === 1) { + decimal = 0; + integer += 1; + } + const fraction = decimal !== 0 ? formatDecimalToFraction(Math.abs(decimal)) : ''; - const sign = number >= 0 ? '+' : '-'; - // Ensure fractions are not too long - if (!fraction || fraction.length <= 4) { - const whole = integer > 0 + + // Ensure fractions aren't too long + if (!fraction || fraction.length <= MAX_FRACTION_LENGTH) { + const integerString = integer > 0 ? fraction ? `${integer} ` : integer : fraction ? '' : '0'; - return `${sign}${whole}${fraction}`; + return `${sign}${integerString}${fraction}`; } else { + // console.log({ fraction }); const decimalFormatted = decimal.toPrecision(2).replace(/^-*0+/, ''); return `${sign}${integer}${decimalFormatted}`; }