Swap getRandomFloat and getRandomFloatRounded params from (max, min[, scale]) to (min, max[, scale]) for consistency with the sibling bounds helpers randomInt (node:crypto) and isValidRandomIntBounds; migrate all 11 production + 5 test call sites; harmonize JSDoc across the random-float family.
Bundles a correctness fix: getRandomFloat now rejects a non-finite interval width (max - min), closing a latent overflow where finite endpoints such as (-MAX_VALUE, MAX_VALUE) returned Infinity/NaN, silently violating the documented [min, max] contract.
Internal API refactor: package.json exports only ./dist/start.js, so getRandomFloat is app-internal and this is not a semver-major public break.
Closes #1967
),
energyTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : getRandomFloatRounded(connectorMaximumEnergyRounded, connectorMinimumEnergyRounded)
+ : getRandomFloatRounded(connectorMinimumEnergyRounded, connectorMaximumEnergyRounded)
return {
template: energyTemplate,
phase1Value ??
defaultFluctuatedPowerPerPhase ??
getRandomFloatRounded(
- connectorMaximumPowerPerPhase / unitDivider,
- connectorMinimumPowerPerPhase / unitDivider
+ connectorMinimumPowerPerPhase / unitDivider,
+ connectorMaximumPowerPerPhase / unitDivider
)
powerValues.L2 =
phase2Value ??
defaultFluctuatedPowerPerPhase ??
getRandomFloatRounded(
- connectorMaximumPowerPerPhase / unitDivider,
- connectorMinimumPowerPerPhase / unitDivider
+ connectorMinimumPowerPerPhase / unitDivider,
+ connectorMaximumPowerPerPhase / unitDivider
)
powerValues.L3 =
phase3Value ??
defaultFluctuatedPowerPerPhase ??
getRandomFloatRounded(
- connectorMaximumPowerPerPhase / unitDivider,
- connectorMinimumPowerPerPhase / unitDivider
+ connectorMinimumPowerPerPhase / unitDivider,
+ connectorMaximumPowerPerPhase / unitDivider
)
} else {
powerValues.L1 = isNotEmptyString(powerTemplate.value)
powerTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
)
: getRandomFloatRounded(
- connectorMaximumPower / unitDivider,
- connectorMinimumPower / unitDivider
+ connectorMinimumPower / unitDivider,
+ connectorMaximumPower / unitDivider
)
powerValues.L2 = 0
powerValues.L3 = 0
powerTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
)
: getRandomFloatRounded(
- connectorMaximumPower / unitDivider,
- connectorMinimumPower / unitDivider
+ connectorMinimumPower / unitDivider,
+ connectorMaximumPower / unitDivider
)
break
default: {
currentValues.L1 =
phase1Value ??
defaultFluctuatedAmperagePerPhase ??
- getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+ getRandomFloatRounded(connectorMinimumAmperage, connectorMaximumAmperage)
currentValues.L2 =
phase2Value ??
defaultFluctuatedAmperagePerPhase ??
- getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+ getRandomFloatRounded(connectorMinimumAmperage, connectorMaximumAmperage)
currentValues.L3 =
phase3Value ??
defaultFluctuatedAmperagePerPhase ??
- getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+ getRandomFloatRounded(connectorMinimumAmperage, connectorMaximumAmperage)
} else {
currentValues.L1 = isNotEmptyString(currentTemplate.value)
? getRandomFloatFluctuatedRounded(
),
currentTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+ : getRandomFloatRounded(connectorMinimumAmperage, connectorMaximumAmperage)
currentValues.L2 = 0
currentValues.L3 = 0
}
),
currentTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage)
+ : getRandomFloatRounded(connectorMinimumAmperage, connectorMaximumAmperage)
break
default: {
const errorMsg = `MeterValues measurand ${
}
/**
- * Generates a cryptographically secure random float in the [min, max] range
- * @param max - The maximum value (inclusive). Defaults to `Number.MAX_VALUE`
- * @param min - The minimum value (inclusive). Defaults to `0`
- * @returns A float in the [min, max] range
+ * Generates a cryptographically secure random float in the [min, max] range.
+ * @param min - The minimum value (inclusive). Defaults to `0`.
+ * @param max - The maximum value (inclusive). Defaults to `Number.MAX_VALUE`.
+ * @returns A float in the [min, max] range.
+ * @throws {RangeError} If `max < min` or the interval width (`max - min`) is not finite.
*/
-export const getRandomFloat = (max = Number.MAX_VALUE, min = 0): number => {
+export const getRandomFloat = (min = 0, max = Number.MAX_VALUE): number => {
if (max < min) {
throw new RangeError('Invalid interval')
}
- if (!Number.isFinite(max) || !Number.isFinite(min)) {
+ if (!Number.isFinite(max - min)) {
throw new RangeError('Invalid interval')
}
return (randomBytes(4).readUInt32LE() / 0xffffffff) * (max - min) + min
return roundedScaled / factor
}
-export const getRandomFloatRounded = (max = Number.MAX_VALUE, min = 0, scale = 2): number => {
- return roundTo(getRandomFloat(max, min), scale)
+/**
+ * Generates a cryptographically secure random float in the [min, max] range, rounded to the given scale.
+ * @param min - The minimum value (inclusive). Defaults to `0`.
+ * @param max - The maximum value (inclusive). Defaults to `Number.MAX_VALUE`.
+ * @param scale - The number of decimal places to round to. Defaults to `2`.
+ * @returns A float in the [min, max] range, rounded to `scale` decimal places.
+ * @throws {RangeError} If `max < min` or the interval width (`max - min`) is not finite.
+ */
+export const getRandomFloatRounded = (min = 0, max = Number.MAX_VALUE, scale = 2): number => {
+ return roundTo(getRandomFloat(min, max), scale)
}
+/**
+ * Generates a cryptographically secure random float fluctuating around `staticValue` by up to `fluctuationPercent`, rounded to the given scale.
+ * @param staticValue - The center value to fluctuate around.
+ * @param fluctuationPercent - The maximum fluctuation as a percentage in the [0, 100] range.
+ * @param scale - The number of decimal places to round to. Defaults to `2`.
+ * @returns A float within `fluctuationPercent` of `staticValue`, rounded to `scale` decimal places.
+ * @throws {RangeError} If `fluctuationPercent` is outside the [0, 100] range.
+ * @throws {RangeError} If a non-zero `fluctuationPercent` derives a non-finite interval width (e.g. `staticValue` near `Number.MAX_VALUE`).
+ */
export const getRandomFloatFluctuatedRounded = (
staticValue: number,
fluctuationPercent: number,
const lowerValue = staticValue * (1 - fluctuationRatio)
const max = Math.max(upperValue, lowerValue)
const min = Math.min(upperValue, lowerValue)
- return getRandomFloatRounded(max, min, scale)
+ return getRandomFloatRounded(min, max, scale)
}
export const extractTimeSeriesValues = (timeSeries: CircularBuffer<TimestampedData>): number[] => {
assert.notDeepStrictEqual(randomFloat, getRandomFloat())
assert.throws(
() => {
- getRandomFloat(0, 1)
+ getRandomFloat(1, 0)
},
{ message: /Invalid interval/ }
)
assert.throws(
() => {
- getRandomFloat(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY)
+ getRandomFloat(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
},
{ message: /Invalid interval/ }
)
- randomFloat = getRandomFloat(0, -Number.MAX_VALUE)
+ assert.throws(
+ () => {
+ getRandomFloat(-Number.MAX_VALUE, Number.MAX_VALUE)
+ },
+ { message: /Invalid interval/ }
+ )
+ randomFloat = getRandomFloat(-Number.MAX_VALUE, 0)
assert.strictEqual(randomFloat >= -Number.MAX_VALUE, true)
assert.strictEqual(randomFloat <= 0, true)
+ assert.strictEqual(getRandomFloat(5, 5), 5)
})
await it('should extract numeric values from timestamped circular buffer', () => {
})
await it('should generate random float rounded to specified scale', () => {
- const result = getRandomFloatRounded(10, 0, 2)
+ const result = getRandomFloatRounded(0, 10, 2)
assert.strictEqual(result >= 0, true)
assert.strictEqual(result <= 10, true)
// Check rounding to 2 decimal places
assert.strictEqual(decimalStr.split('.')[1].length <= 2, true)
}
// Default scale
- const defaultScale = getRandomFloatRounded(10, 0)
+ const defaultScale = getRandomFloatRounded(0, 10)
assert.strictEqual(defaultScale >= 0, true)
assert.strictEqual(defaultScale <= 10, true)
})
const negResult = getRandomFloatFluctuatedRounded(-100, 10)
assert.strictEqual(negResult >= -110, true)
assert.strictEqual(negResult <= -90, true)
+ // Non-finite derived interval width throws via delegation to getRandomFloat
+ assert.throws(
+ () => {
+ getRandomFloatFluctuatedRounded(Number.MAX_VALUE, 10)
+ },
+ { message: /Invalid interval/ }
+ )
+ // Zero fluctuation returns early without delegating, so it never reaches the width guard
+ assert.doesNotThrow(() => {
+ getRandomFloatFluctuatedRounded(Number.MAX_VALUE, 0)
+ })
})
await it('should detect Cloud Foundry environment from VCAP_APPLICATION', () => {