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

Commit

Permalink
Use the official IEEE 754 rounding mode names
Browse files Browse the repository at this point in the history
  • Loading branch information
jessealama committed May 7, 2024
1 parent 7c34396 commit 5ead135
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 171 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [15.0.0] - 2024-05-07

- 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.

## [14.1.0] - 2024-05-06

### Added
Expand Down
2 changes: 1 addition & 1 deletion examples/floor.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Decimal128 } from "../src/decimal128.mjs";

function floor(d: Decimal128): Decimal128 {
return d.round(0, "floor");
return d.round(0, "roundTowardNegative");
}

export { floor };
109 changes: 60 additions & 49 deletions src/decimal128.mts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,9 @@ function roundHalfEven(
};
}

function roundCeiling(x: SignedSignificandExponent): SignedSignificandExponent {
function roundHalfExpand(
x: SignedSignificandExponent
): SignedSignificandExponent {
let sig = x.significand.toString();
let lastDigit = parseInt(sig.charAt(MAX_SIGNIFICANT_DIGITS)) as Digit;
let cutoff = cutoffAfterSignificantDigits(sig, MAX_SIGNIFICANT_DIGITS - 1);
Expand All @@ -348,17 +350,27 @@ function roundCeiling(x: SignedSignificandExponent): SignedSignificandExponent {
x.isNegative,
penultimateDigit,
lastDigit,
ROUNDING_MODE_CEILING
ROUNDING_MODE_HALF_EXPAND
);

if (finalDigit < 10) {
return {
isNegative: x.isNegative,
significand: BigInt(`${cutoff}${finalDigit}`),
exponent: exp,
};
}

let rounded = propagateCarryFromRight(cutoff);

return {
isNegative: x.isNegative,
significand: BigInt(`${cutoff}${finalDigit}`),
significand: BigInt(`${rounded}0`),
exponent: exp,
};
}

function roundFloor(x: SignedSignificandExponent): SignedSignificandExponent {
function roundCeiling(x: SignedSignificandExponent): SignedSignificandExponent {
let sig = x.significand.toString();
let lastDigit = parseInt(sig.charAt(MAX_SIGNIFICANT_DIGITS)) as Digit;
let cutoff = cutoffAfterSignificantDigits(sig, MAX_SIGNIFICANT_DIGITS - 1);
Expand All @@ -373,27 +385,17 @@ function roundFloor(x: SignedSignificandExponent): SignedSignificandExponent {
x.isNegative,
penultimateDigit,
lastDigit,
ROUNDING_MODE_FLOOR
ROUNDING_MODE_CEILING
);

if (finalDigit < 10) {
return {
isNegative: x.isNegative,
significand: BigInt(`${cutoff}${finalDigit}`),
exponent: exp,
};
}

let rounded = propagateCarryFromRight(cutoff);

return {
isNegative: x.isNegative,
significand: BigInt(`${rounded}0`),
significand: BigInt(`${cutoff}${finalDigit}`),
exponent: exp,
};
}

function roundTrunc(x: SignedSignificandExponent): SignedSignificandExponent {
function roundFloor(x: SignedSignificandExponent): SignedSignificandExponent {
let sig = x.significand.toString();
let lastDigit = parseInt(sig.charAt(MAX_SIGNIFICANT_DIGITS)) as Digit;
let cutoff = cutoffAfterSignificantDigits(sig, MAX_SIGNIFICANT_DIGITS - 1);
Expand All @@ -408,19 +410,27 @@ function roundTrunc(x: SignedSignificandExponent): SignedSignificandExponent {
x.isNegative,
penultimateDigit,
lastDigit,
ROUNDING_MODE_TRUNCATE
ROUNDING_MODE_FLOOR
);

if (finalDigit < 10) {
return {
isNegative: x.isNegative,
significand: BigInt(`${cutoff}${finalDigit}`),
exponent: exp,
};
}

let rounded = propagateCarryFromRight(cutoff);

return {
isNegative: x.isNegative,
significand: BigInt(`${cutoff}${finalDigit}`),
significand: BigInt(`${rounded}0`),
exponent: exp,
};
}

function roundHalfCeil(
x: SignedSignificandExponent
): SignedSignificandExponent {
function roundTrunc(x: SignedSignificandExponent): SignedSignificandExponent {
let sig = x.significand.toString();
let lastDigit = parseInt(sig.charAt(MAX_SIGNIFICANT_DIGITS)) as Digit;
let cutoff = cutoffAfterSignificantDigits(sig, MAX_SIGNIFICANT_DIGITS - 1);
Expand All @@ -435,7 +445,7 @@ function roundHalfCeil(
x.isNegative,
penultimateDigit,
lastDigit,
ROUNDING_MODE_HALF_CEILING
ROUNDING_MODE_TRUNCATE
);

return {
Expand All @@ -458,8 +468,8 @@ function adjustNonInteger(
return roundFloor(x);
case ROUNDING_MODE_TRUNCATE:
return roundTrunc(x);
case ROUNDING_MODE_HALF_CEILING:
return roundHalfCeil(x);
case ROUNDING_MODE_HALF_EXPAND:
return roundHalfExpand(x);
default:
return roundHalfEven(x);
}
Expand Down Expand Up @@ -572,14 +582,13 @@ function handleInfinity(s: string): Decimal128Constructor {
};
}

export const ROUNDING_MODE_CEILING: RoundingMode = "ceil";
export const ROUNDING_MODE_FLOOR: RoundingMode = "floor";
export const ROUNDING_MODE_TRUNCATE: RoundingMode = "trunc";
export const ROUNDING_MODE_HALF_EVEN: RoundingMode = "halfEven";
export const ROUNDING_MODE_HALF_CEILING: RoundingMode = "halfCeil";
export const ROUNDING_MODE_CEILING: RoundingMode = "roundTowardPositive";
export const ROUNDING_MODE_FLOOR: RoundingMode = "roundTowardNegative";
export const ROUNDING_MODE_TRUNCATE: RoundingMode = "roundTowardZero";
export const ROUNDING_MODE_HALF_EVEN: RoundingMode = "roundTiesToEven";
export const ROUNDING_MODE_HALF_EXPAND: RoundingMode = "roundTiesToAway";

const ROUNDING_MODE_DEFAULT = ROUNDING_MODE_HALF_EVEN;
const CONSTRUCTOR_SHOULD_NORMALIZE = false;
const ROUNDING_MODE_DEFAULT: RoundingMode = ROUNDING_MODE_HALF_EVEN;

function roundIt(
isNegative: boolean,
Expand Down Expand Up @@ -610,12 +619,8 @@ function roundIt(
return digitToRound;
case ROUNDING_MODE_TRUNCATE:
return digitToRound;
case ROUNDING_MODE_HALF_CEILING:
case ROUNDING_MODE_HALF_EXPAND:
if (decidingDigit >= 5) {
if (isNegative) {
return digitToRound;
}

return (digitToRound + 1) as DigitOrTen;
}

Expand All @@ -637,14 +642,19 @@ function roundIt(
}
}

type RoundingMode = "ceil" | "floor" | "trunc" | "halfEven" | "halfCeil";
type RoundingMode =
| "roundTowardPositive"
| "roundTowardNegative"
| "roundTowardZero"
| "roundTiesToEven"
| "roundTiesToAway";

const ROUNDING_MODES: RoundingMode[] = [
"ceil",
"floor",
"trunc",
"halfEven",
"halfCeil",
"roundTowardPositive",
"roundTowardNegative",
"roundTowardZero",
"roundTiesToEven",
"roundTiesToAway",
];

const digitStrRegExp =
Expand All @@ -659,14 +669,11 @@ interface ConstructorOptions {

interface FullySpecifiedConstructorOptions {
roundingMode: RoundingMode;
normalize: boolean;
}

const DEFAULT_CONSTRUCTOR_OPTIONS: FullySpecifiedConstructorOptions =
Object.freeze({
roundingMode: ROUNDING_MODE_DEFAULT,
normalize: CONSTRUCTOR_SHOULD_NORMALIZE,
});
const DEFAULT_CONSTRUCTOR_OPTIONS: FullySpecifiedConstructorOptions = {
roundingMode: ROUNDING_MODE_DEFAULT,
};

type ToStringFormat = "decimal" | "exponential";
const TOSTRING_FORMATS: string[] = ["decimal", "exponential"];
Expand Down Expand Up @@ -1349,6 +1356,10 @@ export class Decimal128 {
return this.clone();
}

if (!ROUNDING_MODES.includes(mode)) {
throw new RangeError(`Invalid rounding mode "${mode}"`);
}

if (numDecimalDigits < 0) {
numDecimalDigits = 0;
}
Expand Down
20 changes: 10 additions & 10 deletions tests/constructor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,11 +462,11 @@ describe("rounding options", () => {
describe("negative value, final decimal digit is five, penultimate digit is less than nine", () => {
let val = "-1234567890123456789012345678901234.5";
let answers = {
ceil: "-1234567890123456789012345678901234",
floor: "-1234567890123456789012345678901235",
trunc: "-1234567890123456789012345678901234",
halfEven: "-1234567890123456789012345678901234",
halfCeil: "-1234567890123456789012345678901234",
roundTowardPositive: "-1234567890123456789012345678901234",
roundTowardNegative: "-1234567890123456789012345678901235",
roundTowardZero: "-1234567890123456789012345678901234",
roundTiesToEven: "-1234567890123456789012345678901234",
roundTiesAway: "-1234567890123456789012345678901234",
};
for (const [mode, expected] of Object.entries(answers)) {
test(`constructor with rounding mode "${mode}"`, () => {
Expand All @@ -479,11 +479,11 @@ describe("rounding options", () => {
describe("negative value, final decimal digit is five, penultimate digit is nine", () => {
let roundNineVal = "-1234567890123456789012345678901239.5";
let roundUpAnswers = {
ceil: "-1234567890123456789012345678901239",
floor: "-1234567890123456789012345678901240",
trunc: "-1234567890123456789012345678901239",
halfEven: "-1234567890123456789012345678901240",
halfCeil: "-1234567890123456789012345678901239",
roundTowardPositive: "-1234567890123456789012345678901239",
roundTowardNegative: "-1234567890123456789012345678901240",
roundTowardZero: "-1234567890123456789012345678901239",
roundTiesToEven: "-1234567890123456789012345678901240",
roundTiesAway: "-1234567890123456789012345678901240",
};
for (const [mode, expected] of Object.entries(roundUpAnswers)) {
test(`constructor with rounding mode "${mode}"`, () => {
Expand Down
Loading

0 comments on commit 5ead135

Please sign in to comment.