Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit b735edf

Browse files
authored
Use the official IEEE 754 rounding mode names (#109)
1 parent 7c34396 commit b735edf

File tree

6 files changed

+229
-215
lines changed

6 files changed

+229
-215
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [15.0.0] - 2024-05-07
11+
12+
- Use the official IEEE 754 rounding names rather than "trunc", "ceil", etc. This is a breaking change if you're using those rounding modes. If not, you shouldn't see any change.
13+
1014
## [14.1.0] - 2024-05-06
1115

1216
### Added

examples/floor.mts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Decimal128 } from "../src/decimal128.mjs";
22

33
function floor(d: Decimal128): Decimal128 {
4-
return d.round(0, "floor");
4+
return d.round(0, "roundTowardNegative");
55
}
66

77
export { floor };

examples/round.js

+25-53
Original file line numberDiff line numberDiff line change
@@ -11,64 +11,36 @@ let zeroPointSix = new Decimal128("0.6");
1111
let onePointFive = new Decimal128("1.5");
1212

1313
// ceiling
14-
"-1" === minusOnePointFive.round(0, "ceil").toString();
15-
"1" === zeroPointFour.round(0, "ceil").toString();
16-
"1" === zeroPointFive.round(0, "ceil").toString();
17-
"1" === zeroPointSix.round(0, "ceil").toString();
18-
"2" === onePointFive.round(0, "ceil").toString();
14+
"-1" === minusOnePointFive.round(0, "roundTowardPositive").toString();
15+
"1" === zeroPointFour.round(0, "roundTowardPositive").toString();
16+
"1" === zeroPointFive.round(0, "roundTowardPositive").toString();
17+
"1" === zeroPointSix.round(0, "roundTowardPositive").toString();
18+
"2" === onePointFive.round(0, "roundTowardPositive").toString();
1919

2020
// floor
21-
"-2" === minusOnePointFive.round(0, "floor").toString();
22-
"0" === zeroPointFour.round(0, "floor").toString();
23-
"0" === zeroPointFive.round(0, "floor").toString();
24-
"0" === zeroPointSix.round(0, "floor").toString();
25-
"1" === onePointFive.round(0, "floor").toString();
26-
27-
// expand
28-
"-2" === minusOnePointFive.round(0, "expand").toString();
29-
"1" === zeroPointFour.round(0, "expand").toString();
30-
"1" === zeroPointFive.round(0, "expand").toString();
31-
"1" === zeroPointSix.round(0, "expand").toString();
32-
"2" === onePointFive.round(0, "expand").toString();
21+
"-2" === minusOnePointFive.round(0, "roundTowardNegative").toString();
22+
"0" === zeroPointFour.round(0, "roundTowardNegative").toString();
23+
"0" === zeroPointFive.round(0, "roundTowardNegative").toString();
24+
"0" === zeroPointSix.round(0, "roundTowardNegative").toString();
25+
"1" === onePointFive.round(0, "roundTowardNegative").toString();
3326

3427
// truncate
35-
"-1" === minusOnePointFive.round(0, "trunc").toString();
36-
"0" === zeroPointFour.round(0, "trunc").toString();
37-
"0" === zeroPointFive.round(0, "trunc").toString();
38-
"0" === zeroPointSix.round(0, "trunc").toString();
39-
"1" === onePointFive.round(0, "trunc").toString();
40-
41-
// round ties to ceiling
42-
"-1" === minusOnePointFive.round(0, "halfCeil").toString();
43-
"0" === zeroPointFour.round(0, "halfCeil").toString();
44-
"1" === zeroPointFive.round(0, "halfCeil").toString();
45-
"1" === zeroPointSix.round(0, "halfCeil").toString();
46-
"2" === onePointFive.round(0, "halfCeil").toString();
47-
48-
// round ties to floor
49-
"-2" === minusOnePointFive.round(0, "halfFloor").toString();
50-
"0" === zeroPointFour.round(0, "halfFloor").toString();
51-
"0" === zeroPointFive.round(0, "halfFloor").toString();
52-
"1" === zeroPointSix.round(0, "halfFloor").toString();
53-
"1" === onePointFive.round(0, "halfFloor").toString();
28+
"-1" === minusOnePointFive.round(0, "roundTowardZero").toString();
29+
"0" === zeroPointFour.round(0, "roundTowardZero").toString();
30+
"0" === zeroPointFive.round(0, "roundTowardZero").toString();
31+
"0" === zeroPointSix.round(0, "roundTowardZero").toString();
32+
"1" === onePointFive.round(0, "roundTowardZero").toString();
5433

5534
// round ties away from zero
56-
"-2" === minusOnePointFive.round(0, "halfExpand").toString();
57-
"0" === zeroPointFour.round(0, "halfExpand").toString();
58-
"1" === zeroPointFive.round(0, "halfExpand").toString();
59-
"1" === zeroPointSix.round(0, "halfExpand").toString();
60-
"2" === onePointFive.round(0, "halfExpand").toString();
61-
62-
// round ties to toward zero
63-
"-1" === minusOnePointFive.round(0, "halfTrunc").toString();
64-
"0" === zeroPointFour.round(0, "halfTrunc").toString();
65-
"0" === zeroPointFive.round(0, "halfTrunc").toString();
66-
"1" === zeroPointSix.round(0, "halfTrunc").toString();
67-
"1" === onePointFive.round(0, "halfTrunc").toString();
35+
"-2" === minusOnePointFive.round(0, "roundTiesToAway").toString();
36+
"0" === zeroPointFour.round(0, "roundTiesToAway").toString();
37+
"1" === zeroPointFive.round(0, "roundTiesToAway").toString();
38+
"1" === zeroPointSix.round(0, "roundTiesToAway").toString();
39+
"2" === onePointFive.round(0, "roundTiesToAway").toString();
6840

6941
// round ties to even
70-
"-2" === minusOnePointFive.round(0, "halfEven").toString();
71-
"0" === zeroPointFour.round(0, "halfEven").toString();
72-
"0" === zeroPointFive.round(0, "halfEven").toString();
73-
"1" === zeroPointSix.round(0, "halfEven").toString();
74-
"2" === onePointFive.round(0, "halfEven").toString();
42+
"-2" === minusOnePointFive.round(0, "roundTiesToEven").toString();
43+
"0" === zeroPointFour.round(0, "roundTiesToEven").toString();
44+
"0" === zeroPointFive.round(0, "roundTiesToEven").toString();
45+
"1" === zeroPointSix.round(0, "roundTiesToEven").toString();
46+
"2" === onePointFive.round(0, "roundTiesToEven").toString();

src/decimal128.mts

+60-51
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,9 @@ function roundHalfEven(
333333
};
334334
}
335335

336-
function roundCeiling(x: SignedSignificandExponent): SignedSignificandExponent {
336+
function roundHalfExpand(
337+
x: SignedSignificandExponent
338+
): SignedSignificandExponent {
337339
let sig = x.significand.toString();
338340
let lastDigit = parseInt(sig.charAt(MAX_SIGNIFICANT_DIGITS)) as Digit;
339341
let cutoff = cutoffAfterSignificantDigits(sig, MAX_SIGNIFICANT_DIGITS - 1);
@@ -348,17 +350,27 @@ function roundCeiling(x: SignedSignificandExponent): SignedSignificandExponent {
348350
x.isNegative,
349351
penultimateDigit,
350352
lastDigit,
351-
ROUNDING_MODE_CEILING
353+
ROUNDING_MODE_HALF_EXPAND
352354
);
353355

356+
if (finalDigit < 10) {
357+
return {
358+
isNegative: x.isNegative,
359+
significand: BigInt(`${cutoff}${finalDigit}`),
360+
exponent: exp,
361+
};
362+
}
363+
364+
let rounded = propagateCarryFromRight(cutoff);
365+
354366
return {
355367
isNegative: x.isNegative,
356-
significand: BigInt(`${cutoff}${finalDigit}`),
368+
significand: BigInt(`${rounded}0`),
357369
exponent: exp,
358370
};
359371
}
360372

361-
function roundFloor(x: SignedSignificandExponent): SignedSignificandExponent {
373+
function roundCeiling(x: SignedSignificandExponent): SignedSignificandExponent {
362374
let sig = x.significand.toString();
363375
let lastDigit = parseInt(sig.charAt(MAX_SIGNIFICANT_DIGITS)) as Digit;
364376
let cutoff = cutoffAfterSignificantDigits(sig, MAX_SIGNIFICANT_DIGITS - 1);
@@ -373,27 +385,17 @@ function roundFloor(x: SignedSignificandExponent): SignedSignificandExponent {
373385
x.isNegative,
374386
penultimateDigit,
375387
lastDigit,
376-
ROUNDING_MODE_FLOOR
388+
ROUNDING_MODE_CEILING
377389
);
378390

379-
if (finalDigit < 10) {
380-
return {
381-
isNegative: x.isNegative,
382-
significand: BigInt(`${cutoff}${finalDigit}`),
383-
exponent: exp,
384-
};
385-
}
386-
387-
let rounded = propagateCarryFromRight(cutoff);
388-
389391
return {
390392
isNegative: x.isNegative,
391-
significand: BigInt(`${rounded}0`),
393+
significand: BigInt(`${cutoff}${finalDigit}`),
392394
exponent: exp,
393395
};
394396
}
395397

396-
function roundTrunc(x: SignedSignificandExponent): SignedSignificandExponent {
398+
function roundFloor(x: SignedSignificandExponent): SignedSignificandExponent {
397399
let sig = x.significand.toString();
398400
let lastDigit = parseInt(sig.charAt(MAX_SIGNIFICANT_DIGITS)) as Digit;
399401
let cutoff = cutoffAfterSignificantDigits(sig, MAX_SIGNIFICANT_DIGITS - 1);
@@ -408,19 +410,27 @@ function roundTrunc(x: SignedSignificandExponent): SignedSignificandExponent {
408410
x.isNegative,
409411
penultimateDigit,
410412
lastDigit,
411-
ROUNDING_MODE_TRUNCATE
413+
ROUNDING_MODE_FLOOR
412414
);
413415

416+
if (finalDigit < 10) {
417+
return {
418+
isNegative: x.isNegative,
419+
significand: BigInt(`${cutoff}${finalDigit}`),
420+
exponent: exp,
421+
};
422+
}
423+
424+
let rounded = propagateCarryFromRight(cutoff);
425+
414426
return {
415427
isNegative: x.isNegative,
416-
significand: BigInt(`${cutoff}${finalDigit}`),
428+
significand: BigInt(`${rounded}0`),
417429
exponent: exp,
418430
};
419431
}
420432

421-
function roundHalfCeil(
422-
x: SignedSignificandExponent
423-
): SignedSignificandExponent {
433+
function roundTrunc(x: SignedSignificandExponent): SignedSignificandExponent {
424434
let sig = x.significand.toString();
425435
let lastDigit = parseInt(sig.charAt(MAX_SIGNIFICANT_DIGITS)) as Digit;
426436
let cutoff = cutoffAfterSignificantDigits(sig, MAX_SIGNIFICANT_DIGITS - 1);
@@ -435,7 +445,7 @@ function roundHalfCeil(
435445
x.isNegative,
436446
penultimateDigit,
437447
lastDigit,
438-
ROUNDING_MODE_HALF_CEILING
448+
ROUNDING_MODE_TRUNCATE
439449
);
440450

441451
return {
@@ -450,16 +460,14 @@ function adjustNonInteger(
450460
options: FullySpecifiedConstructorOptions
451461
): SignedSignificandExponent {
452462
switch (options.roundingMode) {
453-
case ROUNDING_MODE_HALF_EVEN:
454-
return roundHalfEven(x);
455463
case ROUNDING_MODE_CEILING:
456464
return roundCeiling(x);
457465
case ROUNDING_MODE_FLOOR:
458466
return roundFloor(x);
459467
case ROUNDING_MODE_TRUNCATE:
460468
return roundTrunc(x);
461-
case ROUNDING_MODE_HALF_CEILING:
462-
return roundHalfCeil(x);
469+
case ROUNDING_MODE_HALF_EXPAND:
470+
return roundHalfExpand(x);
463471
default:
464472
return roundHalfEven(x);
465473
}
@@ -572,14 +580,13 @@ function handleInfinity(s: string): Decimal128Constructor {
572580
};
573581
}
574582

575-
export const ROUNDING_MODE_CEILING: RoundingMode = "ceil";
576-
export const ROUNDING_MODE_FLOOR: RoundingMode = "floor";
577-
export const ROUNDING_MODE_TRUNCATE: RoundingMode = "trunc";
578-
export const ROUNDING_MODE_HALF_EVEN: RoundingMode = "halfEven";
579-
export const ROUNDING_MODE_HALF_CEILING: RoundingMode = "halfCeil";
583+
export const ROUNDING_MODE_CEILING: RoundingMode = "roundTowardPositive";
584+
export const ROUNDING_MODE_FLOOR: RoundingMode = "roundTowardNegative";
585+
export const ROUNDING_MODE_TRUNCATE: RoundingMode = "roundTowardZero";
586+
export const ROUNDING_MODE_HALF_EVEN: RoundingMode = "roundTiesToEven";
587+
export const ROUNDING_MODE_HALF_EXPAND: RoundingMode = "roundTiesToAway";
580588

581-
const ROUNDING_MODE_DEFAULT = ROUNDING_MODE_HALF_EVEN;
582-
const CONSTRUCTOR_SHOULD_NORMALIZE = false;
589+
const ROUNDING_MODE_DEFAULT: RoundingMode = ROUNDING_MODE_HALF_EVEN;
583590

584591
function roundIt(
585592
isNegative: boolean,
@@ -610,12 +617,8 @@ function roundIt(
610617
return digitToRound;
611618
case ROUNDING_MODE_TRUNCATE:
612619
return digitToRound;
613-
case ROUNDING_MODE_HALF_CEILING:
620+
case ROUNDING_MODE_HALF_EXPAND:
614621
if (decidingDigit >= 5) {
615-
if (isNegative) {
616-
return digitToRound;
617-
}
618-
619622
return (digitToRound + 1) as DigitOrTen;
620623
}
621624

@@ -637,14 +640,19 @@ function roundIt(
637640
}
638641
}
639642

640-
type RoundingMode = "ceil" | "floor" | "trunc" | "halfEven" | "halfCeil";
643+
type RoundingMode =
644+
| "roundTowardPositive"
645+
| "roundTowardNegative"
646+
| "roundTowardZero"
647+
| "roundTiesToEven"
648+
| "roundTiesToAway";
641649

642650
const ROUNDING_MODES: RoundingMode[] = [
643-
"ceil",
644-
"floor",
645-
"trunc",
646-
"halfEven",
647-
"halfCeil",
651+
"roundTowardPositive",
652+
"roundTowardNegative",
653+
"roundTowardZero",
654+
"roundTiesToEven",
655+
"roundTiesToAway",
648656
];
649657

650658
const digitStrRegExp =
@@ -659,14 +667,11 @@ interface ConstructorOptions {
659667

660668
interface FullySpecifiedConstructorOptions {
661669
roundingMode: RoundingMode;
662-
normalize: boolean;
663670
}
664671

665-
const DEFAULT_CONSTRUCTOR_OPTIONS: FullySpecifiedConstructorOptions =
666-
Object.freeze({
667-
roundingMode: ROUNDING_MODE_DEFAULT,
668-
normalize: CONSTRUCTOR_SHOULD_NORMALIZE,
669-
});
672+
const DEFAULT_CONSTRUCTOR_OPTIONS: FullySpecifiedConstructorOptions = {
673+
roundingMode: ROUNDING_MODE_DEFAULT,
674+
};
670675

671676
type ToStringFormat = "decimal" | "exponential";
672677
const TOSTRING_FORMATS: string[] = ["decimal", "exponential"];
@@ -1349,6 +1354,10 @@ export class Decimal128 {
13491354
return this.clone();
13501355
}
13511356

1357+
if (!ROUNDING_MODES.includes(mode)) {
1358+
throw new RangeError(`Invalid rounding mode "${mode}"`);
1359+
}
1360+
13521361
if (numDecimalDigits < 0) {
13531362
numDecimalDigits = 0;
13541363
}

tests/constructor.test.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -462,11 +462,11 @@ describe("rounding options", () => {
462462
describe("negative value, final decimal digit is five, penultimate digit is less than nine", () => {
463463
let val = "-1234567890123456789012345678901234.5";
464464
let answers = {
465-
ceil: "-1234567890123456789012345678901234",
466-
floor: "-1234567890123456789012345678901235",
467-
trunc: "-1234567890123456789012345678901234",
468-
halfEven: "-1234567890123456789012345678901234",
469-
halfCeil: "-1234567890123456789012345678901234",
465+
roundTowardPositive: "-1234567890123456789012345678901234",
466+
roundTowardNegative: "-1234567890123456789012345678901235",
467+
roundTowardZero: "-1234567890123456789012345678901234",
468+
roundTiesToEven: "-1234567890123456789012345678901234",
469+
roundTiesToAway: "-1234567890123456789012345678901235",
470470
};
471471
for (const [mode, expected] of Object.entries(answers)) {
472472
test(`constructor with rounding mode "${mode}"`, () => {
@@ -479,11 +479,11 @@ describe("rounding options", () => {
479479
describe("negative value, final decimal digit is five, penultimate digit is nine", () => {
480480
let roundNineVal = "-1234567890123456789012345678901239.5";
481481
let roundUpAnswers = {
482-
ceil: "-1234567890123456789012345678901239",
483-
floor: "-1234567890123456789012345678901240",
484-
trunc: "-1234567890123456789012345678901239",
485-
halfEven: "-1234567890123456789012345678901240",
486-
halfCeil: "-1234567890123456789012345678901239",
482+
roundTowardPositive: "-1234567890123456789012345678901239",
483+
roundTowardNegative: "-1234567890123456789012345678901240",
484+
roundTowardZero: "-1234567890123456789012345678901239",
485+
roundTiesToEven: "-1234567890123456789012345678901240",
486+
roundTiesToAway: "-1234567890123456789012345678901240",
487487
};
488488
for (const [mode, expected] of Object.entries(roundUpAnswers)) {
489489
test(`constructor with rounding mode "${mode}"`, () => {

0 commit comments

Comments
 (0)