ae_fast_decimal_formatter 0.1.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59156225262394ee5540585652a72a196b60845ef06bb64d8409c590f62e535d
4
- data.tar.gz: 1dc099b29def36a2f513c6a70d3772ce5df6c48c7a0465ca64e39a300a9dbaa7
3
+ metadata.gz: 25d8e986474fd332d72658a25693cb1147d0ff71f187b5dd39e3b1be69078bb9
4
+ data.tar.gz: c0faafa4e3be83109a3de7174e7055c226129e31cdfb83c443483c6c2973a86f
5
5
  SHA512:
6
- metadata.gz: 9d10f701251c18ca9bba7261635a8723370825a4abc5feee17520a7d9fb64d347bd5bf17fcbdd1e0ccd797bc6536895d9e3e7c7df780e41ddca6646d626b7fa4
7
- data.tar.gz: 70abf3b82f2e17cf81010fd8538095c041f717e9d3fc4035dffada029c043ca5e106ea716c696a5469df1fbd87cc42a1e27ab771cbaba23c7a7e1a5b958281ff
6
+ metadata.gz: '0009ff8c75044a9caafee78662242e0de482dedf0dc9aa207b6d808b7d1ee4145fde1c071cd0b2d29b76fc2f4126f8b212c4b55621f2d5ae317a8f96dd1141f3'
7
+ data.tar.gz: 2cd3523e207af1a6f0cc2946bdfbc82d8d915e2e58eb488d39fb730dd92597935958f3ba628370a3ff6d426efba1d765d36116eda16b7b387f3c54b9f2a6b32e
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2019-2022 AppFolio, Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/ae_fast_decimal_formatter/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ae_fast_decimal_formatter'
7
+ spec.version = AeFastDecimalFormatter::VERSION
8
+ spec.platform = Gem::Platform::RUBY
9
+ spec.author = 'AppFolio'
10
+ spec.email = 'opensource@appfolio.com'
11
+ spec.description = 'Efficiently format decimal number.'
12
+ spec.summary = spec.description
13
+ spec.homepage = 'https://github.com/appfolio/ae_fast_decimal_formatter'
14
+ spec.license = 'MIT'
15
+ spec.files = Dir['**/*'].select { |f| f[%r{^(lib/|ext/|LICENSE.txt|.*gemspec)}] }
16
+ spec.require_paths = ['lib']
17
+ spec.extensions = ['ext/ae_fast_decimal_formatter/extconf.rb']
18
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.6.3')
19
+
20
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
+
22
+ spec.add_dependency('addressable', ['>= 2.3.6', '< 3'])
23
+ spec.add_dependency('rack', ['>= 2.2.3', '< 3'])
24
+ end
@@ -1,111 +1,59 @@
1
1
  #include <ruby.h>
2
2
  #include <float.h>
3
3
 
4
- VALUE c_format_decimal(double num, int precision)
5
- {
6
- char numstr[100];
7
- char str[DBL_MAX_10_EXP + 2];
8
- int numi, numlen, stri, strl;
9
- int maxp = 5, i;
10
- char formatstr[10];
11
- int digits_to_comma;
12
- double rounding_factor, rounded_num;
4
+ inline static char * long_to_formatted_string(long value, char *str, unsigned int strlen, unsigned int precision) {
5
+ char *p;
6
+ unsigned long v;
13
7
 
14
- if (precision > maxp) { precision = maxp; }
15
- if (precision < 0) { precision = 0; }
8
+ v = (value < 0) ? -value : value;
9
+ p = str + strlen - 1;
16
10
 
17
- snprintf(formatstr, 10, "%%.%df", precision);
18
- formatstr[9] = 0;
19
-
20
- if (precision > 0) {
21
- rounding_factor = (double) 10.0;
22
- for (i = 1; i < precision; i++) { rounding_factor *= (double) 10.0; }
23
-
24
- double multiplied_num = num * (double) 10.0;
25
- for (i = 1; i < precision; i++) { multiplied_num *= (double) 10.0; }
26
-
27
- rounded_num = round(multiplied_num) / rounding_factor;
28
- } else {
29
- rounded_num = round(num);
11
+ // Copy all the precision digits
12
+ for (unsigned int i = 0; i < precision; i++) {
13
+ *p-- = '0' + (v % 10);
14
+ v /= 10;
30
15
  }
31
16
 
32
- if (precision > 0) { precision++; } // adjust for .
33
-
34
- numlen = snprintf(numstr, 100, formatstr, rounded_num);
35
- digits_to_comma = numlen - precision;
36
- if (numstr[0] == '-') { digits_to_comma--; }
37
-
38
- strl = numlen + (digits_to_comma / 3);
39
- if ((digits_to_comma % 3) == 0) {
40
- strl--;
41
- }
42
-
43
- if (strncmp(numstr, "-0.00000", strl) == 0) {
44
- return rb_str_new("0.00000", 1 + precision);
45
- }
46
-
47
- stri = strl;
48
- str[stri] = 0;
49
- for (i = 1; i <= (precision + 1); i++) {
50
- str[stri - i] = numstr[numlen - i];
17
+ // Add the dot
18
+ if (precision > 0) {
19
+ *p-- = '.';
51
20
  }
52
21
 
53
- stri -= (precision + 2);
54
- numlen -= (precision + 1);
55
- numi = 1;
22
+ // Copy all the digits, adding a comma every 3rd digit
23
+ int digits = 0;
24
+ do {
25
+ *p-- = '0' + (v % 10);
26
+ v /= 10;
56
27
 
57
- while ((numlen - numi) >= 1) {
58
- if ((numi % 3) == 0) {
59
- str[stri] = ',';
60
- stri--;
28
+ if ((++digits % 3) == 0 && v) {
29
+ *p-- = ',';
61
30
  }
62
- str[stri] = numstr[numlen - numi];
63
- stri--;
64
- numi++;
65
- }
66
-
67
- if (numstr[0] == '-') {
68
- str[0] = '-';
69
- } else {
70
- if ((numi % 3) == 0) {
71
- str[1] = ',';
72
- }
73
- str[0] = numstr[0];
74
- }
75
- return rb_str_new2(str);
76
- }
31
+ } while(v);
77
32
 
78
- struct ae_fast_decimal_formatter {
79
- double num;
80
- int precision;
81
- };
33
+ // Finally, add the - if we started with a negative number
34
+ if (value < 0) *p-- = '-';
82
35
 
83
- static void ae_fast_decimal_formatter_free(void *p) {}
84
-
85
- static VALUE ae_fast_decimal_formatter_alloc(VALUE klass) {
86
- VALUE obj;
87
- struct ae_fast_decimal_formatter *ptr;
88
-
89
- obj = Data_Make_Struct(klass, struct ae_fast_decimal_formatter, NULL, ae_fast_decimal_formatter_free, ptr);
90
-
91
- ptr->num = 0.0;
92
- ptr->precision = 0;
93
-
94
- return obj;
95
- }
36
+ // And adjust the p to undo the last p--
37
+ p++;
96
38
 
97
- static VALUE ae_fast_decimal_formatter_init(VALUE self, VALUE number, VALUE precision) {
98
- struct ae_fast_decimal_formatter *ptr;
99
- Data_Get_Struct(self, struct ae_fast_decimal_formatter, ptr);
100
- ptr->num = NUM2DBL(number);
101
- ptr->precision = NUM2UINT(precision);
102
- return self;
39
+ return p;
103
40
  }
104
41
 
105
- static VALUE ae_fast_decimal_formatter_format(VALUE self) {
106
- struct ae_fast_decimal_formatter *ptr;
107
- Data_Get_Struct(self, struct ae_fast_decimal_formatter, ptr);
108
- return c_format_decimal(ptr->num, ptr->precision);
42
+ // The function takes a value num (assumed to be fixnum) and value precision (assumed to be fixnum
43
+ // between 0 and 5) and returns num as a formatted string.
44
+ //
45
+ // For example, given 100, 2 the function will output "1.00".
46
+ // For example, given 123456, 2 the function will output "1,234.56".
47
+ // For example, given -1234, 0 the function will output "-1,234".
48
+ static VALUE ae_fast_decimal_formatter_format_long(VALUE self, VALUE num, VALUE precision) {
49
+ // The maximum length is 28 characters: 20 digits (assuming 64bits), 6 commas,
50
+ // negative sign, and a period. So we will always fit in 32 chars.
51
+ char buf[32];
52
+ char *numstr = long_to_formatted_string(NUM2LONG(num), buf, 32, NUM2UINT(precision));
53
+
54
+ // The rb_str_new takes a pointer to a string and a length. It does not rely on
55
+ // null terminated strings.
56
+ return rb_str_new(numstr, 32 - (int)(numstr - buf));
109
57
  }
110
58
 
111
59
  void Init_ae_fast_decimal_formatter(void) {
@@ -113,7 +61,5 @@ void Init_ae_fast_decimal_formatter(void) {
113
61
 
114
62
  cFastDecimalFormatter = rb_const_get(rb_cObject, rb_intern("AeFastDecimalFormatter"));
115
63
 
116
- rb_define_alloc_func(cFastDecimalFormatter, ae_fast_decimal_formatter_alloc);
117
- rb_define_method(cFastDecimalFormatter, "initialize", ae_fast_decimal_formatter_init, 2);
118
- rb_define_method(cFastDecimalFormatter, "format", ae_fast_decimal_formatter_format, 0);
64
+ rb_define_singleton_method(cFastDecimalFormatter, "format_long", ae_fast_decimal_formatter_format_long, 2);
119
65
  }
@@ -1,6 +1,3 @@
1
1
  require "mkmf"
2
2
 
3
- abort "missing snprintf()" unless have_func "snprintf"
4
- abort "missing strncmp()" unless have_func "strncmp"
5
-
6
3
  create_makefile "ae_fast_decimal_formatter/ae_fast_decimal_formatter"
@@ -0,0 +1,3 @@
1
+ class AeFastDecimalFormatter
2
+ VERSION = '2.1.0'
3
+ end
@@ -1,4 +1,23 @@
1
+ require 'bigdecimal'
2
+
1
3
  class AeFastDecimalFormatter
4
+ def self.format(number, precision)
5
+ precision = 0 if precision < 0
6
+ precision = 5 if precision > 5
7
+
8
+ # We can call to_f on the input to convert variety of types to floats. This is
9
+ # faster than inspecting the type of the input and performing different logic.
10
+ # This is safe as we have a test that verifies we produce the same result for
11
+ # float as we do for BigDecimals (i.e. format_long((num * 10 ** precision).round, precision)).
12
+ #
13
+ # In order to round correct, it looks like we need the two sets of rounds. For example,
14
+ # (148.855 * 100).round.to_i -> 14885 seems incorrect, so we try,
15
+ # (148.855.round(2) * 100).to_i -> 14886 but then,
16
+ # (1234567890.12.round(2) * 100).to_i -> 123456789011 which is definitely not correct.
17
+ #
18
+ # Thus the two rounds.
19
+ format_long((number.to_f.round(precision) * 10 ** precision).round, precision)
20
+ end
2
21
  end
3
22
 
4
23
  require "ae_fast_decimal_formatter/ae_fast_decimal_formatter"
metadata CHANGED
@@ -1,27 +1,70 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ae_fast_decimal_formatter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - Appfolio Inc.
7
+ - AppFolio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-10 00:00:00.000000000 Z
12
- dependencies: []
13
- description: Efficiently format decimal number so that reports of millions of decimal
14
- cells are fast
15
- email: dev@appfolio.com
11
+ date: 2022-03-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: addressable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.3.6
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 2.3.6
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rack
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 2.2.3
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '3'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 2.2.3
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '3'
53
+ description: Efficiently format decimal number.
54
+ email: opensource@appfolio.com
16
55
  executables: []
17
56
  extensions:
18
57
  - ext/ae_fast_decimal_formatter/extconf.rb
19
58
  extra_rdoc_files: []
20
59
  files:
60
+ - LICENSE.txt
61
+ - ae_fast_decimal_formatter.gemspec
21
62
  - ext/ae_fast_decimal_formatter/ae_fast_decimal_formatter.c
22
63
  - ext/ae_fast_decimal_formatter/extconf.rb
23
64
  - lib/ae_fast_decimal_formatter.rb
24
- homepage: https://www.github.com/appfolio/ae_fast_decimal_formatter
65
+ - lib/ae_fast_decimal_formatter/ae_fast_decimal_formatter.bundle
66
+ - lib/ae_fast_decimal_formatter/version.rb
67
+ homepage: https://github.com/appfolio/ae_fast_decimal_formatter
25
68
  licenses:
26
69
  - MIT
27
70
  metadata:
@@ -35,17 +78,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
35
78
  - - ">="
36
79
  - !ruby/object:Gem::Version
37
80
  version: 2.6.3
38
- - - "<"
39
- - !ruby/object:Gem::Version
40
- version: '3'
41
81
  required_rubygems_version: !ruby/object:Gem::Requirement
42
82
  requirements:
43
83
  - - ">="
44
84
  - !ruby/object:Gem::Version
45
85
  version: '0'
46
86
  requirements: []
47
- rubygems_version: 3.0.8
87
+ rubygems_version: 3.0.9
48
88
  signing_key:
49
89
  specification_version: 4
50
- summary: Efficiently format decimal number
90
+ summary: Efficiently format decimal number.
51
91
  test_files: []