Skip to content

Commit e6f290d

Browse files
jparisesarahdayan
andauthored
fix(toDecimal): preserve negative sign for leading zeros (#693)
Change #690 fixed the general case of formatting negative unit values, but was one remaining case where formatting was still incorrect: when the first unit is -0, the resulting string didn't include the leading negative sign. This change identifies that exact case (negative value, leading zero) and pads the resulting string with a leading negative sign. Fixes #692 Co-authored-by: Sarah Dayan <[email protected]>
1 parent 06e7ff2 commit e6f290d

File tree

2 files changed

+46
-17
lines changed

2 files changed

+46
-17
lines changed

packages/core/src/api/toDecimal.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NON_DECIMAL_CURRENCY_MESSAGE } from '../checks';
22
import { assert } from '../helpers';
33
import type { Calculator, Dinero, Formatter, Transformer } from '../types';
4-
import { absolute, computeBase, equal, isArray } from '../utils';
4+
import { absolute, computeBase, equal, isArray, lessThan } from '../utils';
55

66
import { toUnits } from './toUnits';
77

@@ -49,23 +49,22 @@ function getDecimal<TAmount>(
4949
formatter: Formatter<TAmount>
5050
) {
5151
const absoluteFn = absolute(calculator);
52+
const equalFn = equal(calculator);
53+
const lessThanFn = lessThan(calculator);
54+
const zero = calculator.zero();
5255

5356
return (units: readonly TAmount[], scale: TAmount) => {
54-
return units
55-
.map((unit, index) => {
56-
const isFirst = index === 0;
57-
const isLast = units.length - 1 === index;
58-
59-
const unitAsString = formatter.toString(
60-
isFirst ? unit : absoluteFn(unit)
61-
);
62-
63-
if (isLast) {
64-
return unitAsString.padStart(formatter.toNumber(scale), '0');
65-
}
66-
67-
return unitAsString;
68-
})
69-
.join('.');
57+
const whole = formatter.toString(units[0]);
58+
const fractional = formatter.toString(absoluteFn(units[1]));
59+
60+
const scaleNumber = formatter.toNumber(scale);
61+
const decimal = `${whole}.${fractional.padStart(scaleNumber, '0')}`;
62+
63+
const leadsWithZero = equalFn(units[0], zero);
64+
const isNegative = lessThanFn(units[1], zero);
65+
66+
// A leading negative zero is a special case because the `toString`
67+
// formatter won't preserve its negative sign (since 0 === -0).
68+
return leadsWithZero && isNegative ? `-${decimal}` : decimal;
7069
};
7170
}

packages/dinero.js/src/api/__tests__/toDecimal.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ describe('toDecimal', () => {
4545

4646
expect(toDecimal(d)).toEqual('-10.50');
4747
});
48+
it('returns the negative amount with a leading zero in decimal format', () => {
49+
const d = dinero({ amount: -1, currency: USD });
50+
51+
expect(toDecimal(d)).toEqual('-0.01');
52+
});
53+
it('returns negative zero amount as a positive value in decimal format', () => {
54+
const d = dinero({ amount: -0, currency: USD });
55+
56+
expect(toDecimal(d)).toEqual('0.00');
57+
});
4858
it('uses a custom transformer', () => {
4959
const d = dinero({ amount: 1050, currency: USD });
5060

@@ -118,6 +128,16 @@ describe('toDecimal', () => {
118128

119129
expect(toDecimal(d)).toEqual('-10.50');
120130
});
131+
it('returns the negative amount with a leading zero in decimal format', () => {
132+
const d = dinero({ amount: -1n, currency: bigintUSD });
133+
134+
expect(toDecimal(d)).toEqual('-0.01');
135+
});
136+
it('returns negative zero amount as a positive value in decimal format', () => {
137+
const d = dinero({ amount: -0n, currency: bigintUSD });
138+
139+
expect(toDecimal(d)).toEqual('0.00');
140+
});
121141
it('uses a custom transformer', () => {
122142
const d = dinero({ amount: 1050n, currency: bigintUSD });
123143

@@ -198,6 +218,16 @@ describe('toDecimal', () => {
198218

199219
expect(toDecimal(d)).toEqual('-10.05');
200220
});
221+
it('returns the negative amount with a leading zero in decimal format', () => {
222+
const d = dinero({ amount: new Big(-1), currency: bigjsUSD });
223+
224+
expect(toDecimal(d)).toEqual('-0.01');
225+
});
226+
it('returns negative zero amount as a positive value in decimal format', () => {
227+
const d = dinero({ amount: new Big(-0), currency: bigjsUSD });
228+
229+
expect(toDecimal(d)).toEqual('0.00');
230+
});
201231
it('uses a custom transformer', () => {
202232
const d = dinero({ amount: new Big(1050), currency: bigjsUSD });
203233

0 commit comments

Comments
 (0)