extra points if you are interested in how it works...
let's start with virtual array {{},{}}
. SEQUENCE(34, 1, 3, 3)
will give us 34
numbers in 1
column starting from number 3
with the step of 3
numbers:
these will be used as exponents while rising 10
on the power ^
so our virtual array will be:
next, we insert it as the 2nd argument of VLOOKUP
where we check ABS
absolute values (converting negative values into positive) of A column multiplied by *1
just in case values of A column are not numeric. via VLOOKUP
we return the second 2
column and as the 4th argument, we use approximate mode 1
numbers from -999 to 999 will intentionally error out at this point so we could later use IFNA
to "fix" our errors with IF(A:A=IF(,,),, TEXT(A:A, "#.0 "))
translated as: if range A:A is truly empty =IF(,,)
output nothing, else format A column with provided pattern #.0
eg. if cell A5 = empty, the output will be blank cell... if -999 < A5=50 < 999 the output will be 50.0
and the last part:
TEXT(A:A/10^(VLOOKUP(LEN(TEXT(INT(ABS(A:A)), "0"))-1,
SEQUENCE(35, 1,, 3), 1, 1)), "#.0")
ABS(A:A)
to convert negative numbers into positive. INT
to remove decimal numbers if any. TEXT(, "0")
to convert scientific notations 3E 8
into regular numbers 300000000
. LEN
to count digits. -1
to correct for base10 notation. VLOOKUP
above-constructed number in SEQUENCE
of 35
numbers in 1
column, this time starting from number 0 ,,
with the step of 3
numbers. return via VLOOKUP
the first 1
column (eg. the sequence) in approximate mode 1
of vlookup. insert this number as exponent when rising the 10
on power ^
. and take values in A column and divide it by the above-constructed number 10
raised on the power ^
of a specific exponent. and lastly, format it with TEXT
as #.0
to convert ugly 0.0
into beautiful 0
we just use REGEXREPLACE
. and INDEX
is used instead of the longer ARRAYFORMULA
.
sidenote: to remove trailing spaces (which are there to add nice alignment lol) either remove them from the formula or use TRIM
right after INDEX
.
script solution:
hopefully, someone will cover this too...
extra:
CodePudding user response:
- For almost all practical purposes we can use Intl
compact
format to achieve this functionality.
/**
* Utility function needed to map 2D arrays
*/
function customFunctionRecurse_(array, mainFunc, subFunc, extraArgToMainFunc) {
if (Array.isArray(array))
return array.map((e) => mainFunc(e, extraArgToMainFunc));
else return subFunc(array);
}
/**
* Simple custom formating function using Intl
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
* @customfunction
* @param {A1:D2} numArr A 2D array
* @returns {String[][]}Compact Intl formatted 2D array
*/
function format(numArr) {
const cIntl = new Intl.NumberFormat('en-GB', {
notation: 'compact',
compactDisplay: 'short',
});
return customFunctionRecurse_(numArr, format, (num) => cIntl.format(num));
}
- But for extreme ends or custom formatting, We need to use a custom script:
/**
* Formats various numbers according to the provided format
* @customfunction
* @param {A1:D2} numArr A 2D array
* @param {X1:Y2=} formatArr [optional] A format 2D real/virtual array
* with base 10 power -> suffix mapping
* eg: X1:3 Y1:K represents numbers > 10^3 should have a K suffix
* @returns {String[][]} Formatted 2D array
*/
function customFormat(
numArr,
formatArr = [
//sample byte => kb formatting
[3, 'kb'],
[6, 'mb'],
[9, 'gb'],
[12, 'tb'],
]
) {
if (
formatArr[formatArr.length - 1] &&
formatArr[formatArr.length - 1][0] !== 0
) {
formatArr = formatArr.reverse();
formatArr.push([0, '']);
}
const addSuffix = (num) => {
const anum = Math.abs(num);
if (num === 0) return '0.00';
if (anum > 0 && anum < 1) return String(num.toFixed(2));
for (const [exp, suffix] of formatArr) {
if (anum >= Math.pow(10, exp))
return `${(num / Math.pow(10, exp)).toFixed(2)}${suffix}`;
}
};
return customFunctionRecurse_(numArr, customFormat, addSuffix, formatArr);
}
Usage:
=CUSTOMFORMAT(A1:A5,{{3,"k"};{10,"G"}})
Tells custom function to use k
for numbers>10^3
and G
for 10^10
Illustration:
Show code snippet
/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/
/**
* Utility function needed to map 2D arrays
*/
function customFunctionRecurse_(array, mainFunc, subFunc, extraArgToMainFunc) {
if (Array.isArray(array))
return array.map((e) => mainFunc(e, extraArgToMainFunc));
else return subFunc(array);
}
/**
* Simple custom formating function using Intl
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat
* @customfunction
* @param {A1:D2} A 2D array
* @returns {String[][]}Compact Intl formatted 2D array
*/
function format(numArr) {
const cIntl = new Intl.NumberFormat('en-GB', {
notation: 'compact',
compactDisplay: 'short',
});
return customFunctionRecurse_(numArr, format, (num) => cIntl.format(num));
}
/**
* Formats various numbers according to the provided format
* @customfunction
* @param {A1:D2} A 2D array
* @param {X1:Y2=} [optional] A format 2D real/virtual array
* with base 10 power -> suffix mapping
* eg: X1:3 Y1:K represents numbers > 10^3 should have a K suffix
* @returns {String[][]} Formatted 2D array
*/
function customFormat(
numArr,
formatArr = [
//sample byte => kb formatting
[3, 'kb'],
[6, 'mb'],
[9, 'gb'],
[12, 'tb'],
]
) {
//console.log({ numArr, formatArr });
if (
formatArr[formatArr.length - 1] &&
formatArr[formatArr.length - 1][0] !== 0
) {
formatArr = formatArr.reverse();
formatArr.push([0, '']);
}
const addSuffix = (num) => {
const anum = Math.abs(num);
if (num === 0) return '0.00';
if (anum > 0 && anum < 1) return String(num.toFixed(2));
for (const [exp, suffix] of formatArr) {
if (anum >= Math.pow(10, exp))
return `${(num / Math.pow(10, exp)).toFixed(2)}${suffix}`;
}
};
return customFunctionRecurse_(numArr, customFormat, addSuffix, formatArr);
}
console.log(
customFormat([
[
0,
1000,
153,
12883255,
235688235123,
88555552233355888,
-86555,
0.8523588055,
Math.pow(10, 15),
],
])
);
<!-- https://meta.stackoverflow.com/a/375985/ --> <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>