From c7f9fb4a0e32d670ab9bd299076c6453bb977380 Mon Sep 17 00:00:00 2001 From: Gordon Wall Date: Thu, 17 Jun 2021 13:36:22 -0400 Subject: [PATCH 1/6] revised logic to determining millidx fixes 999 bug --- millify/__init__.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/millify/__init__.py b/millify/__init__.py index ed58dfa..f9467b6 100644 --- a/millify/__init__.py +++ b/millify/__init__.py @@ -1,6 +1,4 @@ -import math import re -from decimal import Decimal __author__ = "Alexander Zaitsev (azaitsev@gmail.com)" __copyright__ = "Copyright 2018, azaitsev@gmail.com" @@ -8,11 +6,6 @@ __version__ = "0.1.1" -def remove_exponent(d): - """Remove exponent.""" - return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize() - - def millify(n, precision=0, drop_nulls=True, prefixes=[]): """Humanize number.""" millnames = ['', 'k', 'M', 'B', 'T', 'P', 'E', 'Z', 'Y'] @@ -20,12 +13,14 @@ def millify(n, precision=0, drop_nulls=True, prefixes=[]): millnames = [''] millnames.extend(prefixes) n = float(n) - millidx = max(0, min(len(millnames) - 1, - int(math.floor(0 if n == 0 else math.log10(abs(n)) / 3)))) - result = '{:.{precision}f}'.format(n / 10**(3 * millidx), precision=precision) + millidx = 0 + while abs(n) >= 1000: + millidx += 1 + n = round(n / 1000.0, precision) + result = '{}'.format(n) if drop_nulls: - result = remove_exponent(Decimal(result)) - return '{0}{dx}'.format(result, dx=millnames[millidx]) + result = result.rstrip('0').rstrip('.') + return '{}{dx}'.format(result, dx=millnames[millidx]) def prettify(amount, separator=','): From fef20ac6119c52152163801b2fd1367529b45f43 Mon Sep 17 00:00:00 2001 From: Gordon Wall Date: Thu, 8 Jul 2021 10:26:40 -0400 Subject: [PATCH 2/6] rounding fix for input numbers less than 1000 n < 1000 was skipping While block and showing many decimals, if input had many decimals. --- millify/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/millify/__init__.py b/millify/__init__.py index f9467b6..5c08227 100644 --- a/millify/__init__.py +++ b/millify/__init__.py @@ -17,6 +17,8 @@ def millify(n, precision=0, drop_nulls=True, prefixes=[]): while abs(n) >= 1000: millidx += 1 n = round(n / 1000.0, precision) + if n < 1000: + round(n, precision) result = '{}'.format(n) if drop_nulls: result = result.rstrip('0').rstrip('.') From 7090e35a14977c928adcd0b39e3ffad06d23a01a Mon Sep 17 00:00:00 2001 From: Gordon Wall Date: Fri, 9 Jul 2021 18:23:07 -0400 Subject: [PATCH 3/6] set value of n --- millify/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/millify/__init__.py b/millify/__init__.py index 5c08227..8af905b 100644 --- a/millify/__init__.py +++ b/millify/__init__.py @@ -18,7 +18,7 @@ def millify(n, precision=0, drop_nulls=True, prefixes=[]): millidx += 1 n = round(n / 1000.0, precision) if n < 1000: - round(n, precision) + n = round(n, precision) result = '{}'.format(n) if drop_nulls: result = result.rstrip('0').rstrip('.') From f3ffb1ecc59df62432bb13f3f7afe469bf4d8f53 Mon Sep 17 00:00:00 2001 From: Mitch Angenent Date: Mon, 4 Oct 2021 20:56:47 +0200 Subject: [PATCH 4/6] fix: handling of custom separator for >5-digit numbers --- millify/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/millify/__init__.py b/millify/__init__.py index 8af905b..071f5dd 100644 --- a/millify/__init__.py +++ b/millify/__init__.py @@ -32,4 +32,4 @@ def prettify(amount, separator=','): if orig == new: return new else: - return prettify(new) + return prettify(new, separator=separator) From 8ed1873d45fe8c5c154c9c8283581a6d6eaa18b1 Mon Sep 17 00:00:00 2001 From: Mitch Angenent Date: Mon, 4 Oct 2021 20:59:48 +0200 Subject: [PATCH 5/6] feat: check type of separator and prefixes --- millify/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/millify/__init__.py b/millify/__init__.py index 071f5dd..dd26cfb 100644 --- a/millify/__init__.py +++ b/millify/__init__.py @@ -9,7 +9,7 @@ def millify(n, precision=0, drop_nulls=True, prefixes=[]): """Humanize number.""" millnames = ['', 'k', 'M', 'B', 'T', 'P', 'E', 'Z', 'Y'] - if prefixes: + if prefixes and isinstance(prefixes, list): millnames = [''] millnames.extend(prefixes) n = float(n) @@ -18,7 +18,7 @@ def millify(n, precision=0, drop_nulls=True, prefixes=[]): millidx += 1 n = round(n / 1000.0, precision) if n < 1000: - n = round(n, precision) + n = round(n, precision) result = '{}'.format(n) if drop_nulls: result = result.rstrip('0').rstrip('.') @@ -27,6 +27,8 @@ def millify(n, precision=0, drop_nulls=True, prefixes=[]): def prettify(amount, separator=','): """Separate with predefined separator.""" + if not isinstance(separator, str): + separator = ',' orig = str(amount) new = re.sub("^(-?\d+)(\d{3})", "\g<1>{0}\g<2>".format(separator), str(amount)) if orig == new: From 1575b13134520f4afc58b51b88225e5bf691dcf1 Mon Sep 17 00:00:00 2001 From: Mitch Angenent Date: Mon, 4 Oct 2021 21:00:17 +0200 Subject: [PATCH 6/6] test: adds unit tests for millify and prettify --- tests/test_millify.py | 82 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/test_millify.py diff --git a/tests/test_millify.py b/tests/test_millify.py new file mode 100644 index 0000000..bed1242 --- /dev/null +++ b/tests/test_millify.py @@ -0,0 +1,82 @@ +import unittest +from millify import millify +from millify import prettify + + +class TestMillify(unittest.TestCase): + """ Tests the millify() function. """ + + def test_basics(self): + self.assertEqual(millify(123), '123') + self.assertEqual(millify(1234), '1k') + self.assertEqual(millify(12345), '12k') + self.assertEqual(millify(12345678), '12M') + self.assertEqual(millify(1000000000), '1B') + + def test_precision(self): + self.assertEqual(millify(1234, precision=2), '1.23k') + self.assertEqual(millify(12345, precision=3), '12.345k') + self.assertEqual(millify(12342123, precision=2), '12.34M') + self.assertEqual(millify(12345123, precision=2), '12.35M') + self.assertEqual(millify(12345678, precision=3), '12.346M') + self.assertEqual(millify(12345678, precision=4), '12.3457M') + + def test_strings(self): + self.assertEqual(millify('1234'), '1k') + self.assertEqual(millify('12345'), '12k') + self.assertEqual(millify('12345678'), '12M') + + def test_drop_nulls(self): + self.assertEqual(millify(10000, precision=1, drop_nulls=False), '10.0k') + self.assertEqual(millify(10000, precision=1, drop_nulls=True), '10k') + self.assertEqual(millify(10000, precision=1), '10k') + + def test_prefixes(self): + prefixes = ['kB', 'MB', 'GB'] + self.assertEqual(millify(10000, prefixes=None), '10k') + self.assertEqual(millify(10000, prefixes=True), '10k') + self.assertEqual(millify(10000, prefixes=prefixes), '10kB') + self.assertEqual(millify(1000000, prefixes=prefixes), '1MB') + self.assertEqual(millify(1000000000, prefixes=prefixes), '1GB') + + def test_nines(self): + self.assertEqual(millify(999), '999') + self.assertEqual(millify('999'), '999') + self.assertEqual(millify(9999), '10k') + self.assertEqual(millify(9999, precision=2), '10k') + self.assertEqual(millify(99999), '100k') + self.assertEqual(millify(999999), '1M') + self.assertEqual(millify(999999999), '1B') + + +class TestPrettify(unittest.TestCase): + """ Tests the prettify() function. """ + + def test_basics(self): + self.assertEqual(prettify(1234), '1,234') + self.assertEqual(prettify(12345), '12,345') + self.assertEqual(prettify(123456), '123,456') + self.assertEqual(prettify(1234567), '1,234,567') + self.assertEqual(prettify(1234567890), '1,234,567,890') + + def test_strings(self): + self.assertEqual(prettify('1234'), '1,234') + self.assertEqual(prettify('12345'), '12,345') + self.assertEqual(prettify('123456'), '123,456') + self.assertEqual(prettify('1234567'), '1,234,567') + self.assertEqual(prettify('1234567890'), '1,234,567,890') + + def test_custom_separator(self): + self.assertEqual(prettify('1234', separator=','), '1,234') + self.assertEqual(prettify('12345', separator='.'), '12.345') + self.assertEqual(prettify('123456', separator='/'), '123/456') + self.assertEqual(prettify('1234567', separator='-'), '1-234-567') + self.assertEqual(prettify('1234567890', separator=' '), '1 234 567 890') + + def test_separator_type(self): + self.assertEqual(prettify('1234567890', separator=None), '1,234,567,890') + self.assertEqual(prettify('1234567890', separator=True), '1,234,567,890') + + +if __name__ == '__main__': + unittest.main()