ae_fast_decimal_formatter 1.0.0 → 2.1.1

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: 45ca777593c0bfaf6e4cac01b3681d91935a91a0b005defec7436f9fd9b44c39
4
- data.tar.gz: be583138932ff4fe697f296bb57f691898f299fe6a1af7f2ed7be85f5d7cfffc
3
+ metadata.gz: fac5f36669896fd67ffb43a022635899c7143a6cd6204064260051a6fde5a180
4
+ data.tar.gz: f5d41ebc1983a010473c7763915b3ca04c5c845581bfaafdcc55d9ac1e918061
5
5
  SHA512:
6
- metadata.gz: 36437067c9f6a566b9894cb2f377a337a2d8aec466b46f856804027debc2ab583974cf325b45b71ef9e33f88235a9f2b3a2042dd2546119414f4931a3a0b0525
7
- data.tar.gz: 00cf30e71c1fe476701dd362e97383ed9d89b7f5eeb24c83459d45dcae5ad3d2ba9a49737bc1263463191bd63640ff3f3b273b91384bcd2826228cecbb4c96db
6
+ metadata.gz: b884e0704195a3d19cf5b346f09eeb1e1ac8ad5186582060c932ac127d6b1a30cccb1cf223fd65608f38930e6551f57826653428fdd4528ebf53f20ae55686f1
7
+ data.tar.gz: 0f9d967ff0a792e6c29d5d3123280fbf2e52d5502b5de6e3b10df93aaedf476ba7a917827a2b498d10991cb310a57ffc858511b9db97840843aed7df669151dc
@@ -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,5 @@
1
1
  require "mkmf"
2
2
 
3
- abort "missing snprintf()" unless have_func "snprintf"
4
- abort "missing strncmp()" unless have_func "strncmp"
3
+ append_cflags("-std=c99")
5
4
 
6
5
  create_makefile "ae_fast_decimal_formatter/ae_fast_decimal_formatter"
@@ -1,3 +1,3 @@
1
1
  class AeFastDecimalFormatter
2
- VERSION = '1.0.0'
2
+ VERSION = '2.1.1'
3
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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ae_fast_decimal_formatter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - AppFolio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-23 00:00:00.000000000 Z
11
+ date: 2022-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable