import { FieldType } from '@terascope/types' ; import { isArrayLike } from './arrays' ; import { getTypeOf } from './deps' ; let supportsBigInt = true ; try { if ( typeof globalThis . BigInt === 'undefined' ) { supportsBigInt = false ; } } catch ( err ) { supportsBigInt = false ; } /** A native implementation of lodash random */ export function random ( min , max ) { return Math . floor ( Math . random ( ) * ( max - min + 1 ) ) + min ; } /** Check if an input is a number */ export function isNumber ( input ) { return typeof input === 'number' && ! Number . isNaN ( input ) ; } /** Convert any input to a number, return Number.NaN if unable to convert input  */ export function toNumber ( input ) { if ( typeof input === 'number' ) return input ; return Number ( input ) ; } /** Will throw if converted number is NaN */ export function toNumberOrThrow ( input ) { const num = toNumber ( input ) ; if ( ! isNumber ( num ) ) { throw new Error ( `Could not convert ${ input } to a number` ) ; } return num ; } /** Check if value is a bigint */ export function isBigInt ( input ) { return typeof input === 'bigint' ; } /** Convert any input to a bigint */ export function toBigInt ( input ) { if ( ! supportsBigInt ) { throw new Error ( 'BigInt isn\'t supported in this environment' ) ; } try { return toBigIntOrThrow ( input ) ; } catch { return false ; } } const _maxBigInt = supportsBigInt ? BigInt ( Number . MAX_SAFE_INTEGER ) : ( Number . MAX_SAFE_INTEGER as any ) ; /** Convert any input to a bigint */ export function toBigIntOrThrow ( input ) { if ( typeof input === 'object' ) { throw new TypeError ( `Expected ${ input } (${ getTypeOf ( input ) }) to be parsable to a float` ) ; } if ( isBigInt ( input ) ) return input ; if ( ! supportsBigInt ) { throw new Error ( 'BigInt isn\'t supported in this environment' ) ; } if ( typeof input === 'number' && input < Number . MAX_SAFE_INTEGER ) { return BigInt ( Math . trunc ( input ) ) ; } if ( ! isNumberLike ( input ) ) { throw new TypeError ( `Expected ${ input } (${ getTypeOf ( input ) }) to be parsable to a BigInt` ) ; } let big ; if ( typeof input === 'string' && input . includes ( '.' ) ) { big = BigInt ( Number . parseInt ( input , 10 ) ) ; } else { big = BigInt ( input as string | number | bigint ) ; } if ( big > _maxBigInt ) { return big + BigInt ( 1 ) ; } return big ; } /**
 * Convert a BigInt to either a number or a string
*/ export function bigIntToJSON ( int ) { if ( typeof int === 'number' ) return int ; if ( int <= _maxBigInt ) { return Number . parseInt ( int . toString ( 10 ) , 10 ) ; } return ( int - BigInt ( 1 ) ) . toString ( 10 ) ; } todo export function isNumberLike ( input ) { if ( typeof input === 'number' ) return true ; if ( typeof input === 'object' ) return false ; if ( typeof input === 'boolean' ) return false ; return /^\s*[+-]{0,1}[\d,]+(\.[\d]+){0,1}\s*$/ . test ( String ( input ) ) ; } /** A simplified implementation of lodash isInteger */ export function isInteger ( val ) { if ( typeof val !== 'number' ) return false ; return Number . isSafeInteger ( val ) ; } /** Convert an input to a integer, return false if unable to convert input  */ export function toInteger ( input ) { try { return toIntegerOrThrow ( input ) ; } catch ( err ) { return false ; } } /** Convert an input to a integer or throw */ export function toIntegerOrThrow ( input ) { if ( typeof input === 'object' ) { throw new TypeError ( `Expected ${ input } (${ getTypeOf ( input ) }) to be parsable to a integer` ) ; } if ( isBigInt ( input ) ) { const val = bigIntToJSON ( input ) ; if ( typeof val === 'string' ) { throw new TypeError ( `Expected ${ val } (${ getTypeOf ( input ) }) to be parsable to a integer` ) ; } return val ; } if ( isInteger ( input ) || isFloat ( input ) ) return Math . trunc ( input ) ; if ( ! isNumberLike ( input ) ) { throw new TypeError ( `Expected ${ input } (${ getTypeOf ( input ) }) to be parsable to a integer` ) ; } const val = Number . parseInt ( input as any , 10 ) ; if ( isInteger ( val ) ) return val ; throw new TypeError ( `Expected ${ val } (${ getTypeOf ( input ) }) to be parsable to a integer` ) ; } /** Verify the input is a finite number (and float like) */ export function isFloat ( val ) { if ( ! isNumber ( val ) ) return false ; if ( val === Number . POSITIVE_INFINITY ) return false ; if ( val === Number . NEGATIVE_INFINITY ) return false ; return true ; } /** Convert an input to a float, return false if unable to convert input  */ export function toFloat ( input ) { try { return toFloatOrThrow ( input ) ; } catch ( err ) { return false ; } } /** Convert an input to a float or throw */ export function toFloatOrThrow ( input ) { if ( typeof input === 'object' ) { throw new TypeError ( `Expected ${ input } (${ getTypeOf ( input ) }) to be parsable to a float` ) ; } if ( isFloat ( input ) ) return input ; if ( isBigInt ( input ) ) { const val = bigIntToJSON ( input ) ; if ( typeof val === 'string' ) { throw new TypeError ( `Expected ${ val } (${ getTypeOf ( input ) }) to be parsable to a float` ) ; } return val ; } if ( ! isNumberLike ( input ) ) { throw new TypeError ( `Expected ${ input } (${ getTypeOf ( input ) }) to be parsable to a float` ) ; } const val = Number . parseFloat ( input as any ) ; if ( isFloat ( val ) ) return val ; throw new TypeError ( `Expected ${ val } (${ getTypeOf ( input ) }) to be parsable to a float` ) ; } /**
 * Like parseList, except it returns numbers
 */ export function parseNumberList ( input ) { let items ; if ( typeof input === 'string' ) { items = input . split ( ',' ) ; } else if ( Array . isArray ( input ) ) { items = input ; } else if ( isArrayLike ( input ) ) { items = Array . from ( input ) ; } else if ( isNumber ( input ) ) { return [ input ] ; } else { return [ ] ; } return items . filter ( isConvertibleToNumber ) . map ( toNumber ) . filter ( isNumber ) ; } function isConvertibleToNumber ( item ) { if ( item == null ) return false ; if ( typeof item === 'string' && ! item . trim ( ) . length ) return false ; return true ; } export interface InNumberRangeArg { min ? : number ; max ? : number ; inclusive ? : boolean ; } example export function inNumberRange ( input , args ) { if ( ! isNumber ( input ) && ! isBigInt ( input ) ) return false ; const min = args . min == null ? Number . NEGATIVE_INFINITY : args . min ; const max = args . max == null ? Number . POSITIVE_INFINITY : args . max ; if ( args . inclusive ) { return ( input >= min && input <= max ) ; } return ( input > min && input < max ) ; } export function inNumberRangeFP ( args ) { return function _inNumberRangeFP ( input ) : input is number { return inNumberRange ( input , args ) ; } ; } param fractionDigits param truncate export function setPrecision ( input , fractionDigits , truncate = false ) { if ( Number . isNaN ( input ) || input === Number . POSITIVE_INFINITY || input === Number . NEGATIVE_INFINITY ) { return input as number ; } const num = toFloatOrThrow ( input ) ; if ( ! truncate ) { return parseFloat ( num . toFixed ( fractionDigits ) ) ; } return parseFloat ( setPrecisionFromString ( num . toString ( ) , fractionDigits ) ) ; } param fractionDigits param truncate export function setPrecisionFP ( fractionDigits , truncate = false ) { return function _setPrecision ( input ) { return setPrecision ( input , fractionDigits , truncate ) ; } ; } /**
 * this will always truncate (not round)
*/ function setPrecisionFromString ( input , fractionDigits ) { const [ int , points ] = input . toString ( ) . split ( '.' ) ; if ( ! points ) return int || '0' ; const remainingPoints = points . slice ( 0 , fractionDigits ) ; if ( ! remainingPoints ) return int || '0' ; return `${ int || '0' }.${ remainingPoints }` ; } /**
 * Convert a fahrenheit value to celsius, this will return a precision of
 * 2 decimal points
*/ export function toCelsius ( input ) { const num = toFloatOrThrow ( input ) ; const cNum = ( num - 32 ) * ( 5 / 9 ) ; return parseFloat ( cNum . toFixed ( 2 ) ) ; } /**
 * Convert a celsius value to fahrenheit, this will return a precision of
 * 2 decimal points
*/ export function toFahrenheit ( input ) { const num = toFloatOrThrow ( input ) ; const fNum = ( ( 9 / 5 ) * num ) + 32 ; return parseFloat ( fNum . toFixed ( 2 ) ) ; } const INT_SIZES = { [ FieldType . Byte ] : { min : - 128 , max : 127 } , [ FieldType . Short ] : { min : - 32768 , max : 32767 } , [ FieldType . Integer ] : { min : - ( 2 ** 31 ) , max : ( 2 ** 31 ) - 1 } , } as const ; function _validateNumberFieldType ( input , type ) { const int = toIntegerOrThrow ( input ) ; if ( INT_SIZES [ type ] ) { const { max , min } = INT_SIZES [ type ] ; if ( int >= max ) { throw new TypeError ( `Invalid byte, value of ${ int } is greater than maximum size of ${ max }` ) ; } if ( int <= min ) { throw new TypeError ( `Invalid byte, value of ${ int } is less than minimum size of ${ min }` ) ; } } return int ; } export function validateByteNumber ( input ) { return _validateNumberFieldType ( input , FieldType . Byte ) ; } export function validateShortNumber ( input ) { return _validateNumberFieldType ( input , FieldType . Short ) ; } export function validateIntegerNumber ( input ) { return _validateNumberFieldType ( input , FieldType . Integer ) ; } export function validateNumberType ( type ) { return function _validateNumberType ( input ) : number { return _validateNumberFieldType ( input , type ) ; } ; } export function isValidateNumberType ( type ) { const fn = validateNumberType ( type ) ; return function _isValidateNumberType ( input ) : boolean { try { return fn ( input ) != null ; } catch ( _err ) { return false ; } } ; }