fixed 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -12
  3. data/lib/fixed/version.rb +7 -1
  4. data/lib/fixed.rb +61 -20
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e87ed315b7c15cb15aa8663e00e8090b29d352babf0d30191da6d16ccca1640
4
- data.tar.gz: fb6001610413384c17bdd211bcbccd471c8e5c506e4c07bdabef3814903e11b3
3
+ metadata.gz: 61a3482844d4fbd66a1edb0dee9b686ba6a4662a289d9d165b7d35c63e5405f0
4
+ data.tar.gz: 123197024b596d6a549e7b1b72f9b900b1edf1d26579299648d468c858f6fb1b
5
5
  SHA512:
6
- metadata.gz: 333bb7867bddec4a1939e205d35e5c05e0443dce411f766d263f3d27c573a54c93a8d021204fbc4edf0d4b0c255a99b6323332464dfbe7afc14a2fbe1a6cf6e9
7
- data.tar.gz: edcda27654f5edce4d586d463d4f682e0f552a525eef203e68655efa751fbc2235437d34215332530e7cba0179420e366efa5146ef315deff24c6e53b687ee27
6
+ metadata.gz: e34d2f9c2fcf1a487f7f9c4e4630997e015c3253c470e00b1dfddc14687f657eba7293c2a3c065002a96f7377828173aa567feede0d45ce59cba2da4a1cbcf2b
7
+ data.tar.gz: f28a0c77579fb25a9501a2a7cd8a97225440f93bcb1020d3291828348f8f9865b8f6de8e0d93341da6589d19f2a9a8061cef751428d28db4df6234f791464195
data/README.md CHANGED
@@ -25,21 +25,12 @@ Here's an example of how to use the Fixed gem:
25
25
  ```ruby
26
26
  require 'fixed'
27
27
 
28
- # Create Fixed instances
29
- a = Fixed(1)
30
- b = Fixed(6)
28
+ a = Fixed(5)
29
+ b = Fixed(3)
31
30
 
32
- # Perform arithmetic operations
33
- sum = a + b
34
- difference = a - b
35
- product = a * b
36
31
  ratio = a / b
37
32
 
38
- # Output the results
39
- puts "Sum: #{sum}" # => Sum: 7.00000000
40
- puts "Difference: #{difference}" # => Difference: -5.00000000
41
- puts "Product: #{product}" # => Product: 6.00000000
42
- puts "Ratio: #{ratio.format(18)}" # => Ratio: 0.166666666666666667
33
+ puts ratio.format(18) # => prints 1.666666666666666667
43
34
  ```
44
35
 
45
36
  In the above example, the Fixed gem allows you to perform arithmetic operations with high precision, avoiding rounding errors that can occur with floating-point numbers.
data/lib/fixed/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Fixed
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
6
6
 
7
7
 
@@ -11,6 +11,12 @@ __END__
11
11
  # Minor version bump when backward-compatible changes or enhancements
12
12
  # Patch version bump when backward-compatible bug fixes, security updates etc
13
13
 
14
+ 1.1.0
15
+
16
+ - Support Ruby versions as old as 1.9.3
17
+ - Add #split for dividing a number proportionally based on specified ratios
18
+ - Add #pretty_format for printing with thousands separators
19
+
14
20
  1.0.0
15
21
 
16
22
  - Added Fixed-number with 18-digit precision
data/lib/fixed.rb CHANGED
@@ -89,6 +89,31 @@ class Fixed
89
89
  make(self.fractions.abs)
90
90
  end
91
91
 
92
+ def split(*numbers)
93
+ ratios = numbers.map(&:to_fixed)
94
+ raise ArgumentError if ratios.any?(&:negative?)
95
+ raise ArgumentError if ratios.all?(&:zero?)
96
+
97
+ # This method ensures that the calculated portions add up to the original
98
+ # value and that no eps are lost due to rounding errors or other issues.
99
+
100
+ remainder = ratios.reduce(:+)
101
+ number = self
102
+
103
+ ratios.map do |ratio|
104
+ next Fixed.zero if remainder.zero?
105
+
106
+ part = make(division_with_rounding(
107
+ number.fractions * ratio.fractions,
108
+ remainder.fractions,
109
+ ))
110
+ remainder -= ratio
111
+ number -= part
112
+ part
113
+ end
114
+ end
115
+
116
+
92
117
  # ------- comparing -----------------------------------------------
93
118
 
94
119
  def <=>(number)
@@ -101,15 +126,15 @@ class Fixed
101
126
  end
102
127
 
103
128
  def negative?
104
- self.fractions.negative?
129
+ @fractions < 0
105
130
  end
106
131
 
107
132
  def positive?
108
- self.fractions.positive?
133
+ @fractions > 0
109
134
  end
110
135
 
111
136
  def zero?
112
- self.fractions.zero?
137
+ @fractions == 0
113
138
  end
114
139
 
115
140
 
@@ -130,13 +155,23 @@ class Fixed
130
155
  def format(precision = 8)
131
156
  raise "expected 1..18, got #{precision.inspect}" unless (0..18) === precision
132
157
 
133
- # Note: although Ruby's documentation indicates otherwise, Rational values
134
- # are actually formatted with full precision rather than as floats.
158
+ rounded_fractions = division_with_rounding(@fractions, 10 ** (18 - precision))
159
+ str = rounded_fractions.abs.to_s.rjust(precision + 1, ?0)
160
+ str.insert(-1 - precision, ?.) if precision > 0
161
+ "#{?- if @fractions < 0}#{str}#{?* if @fractions != 0 && str =~ /^[-0\.]*$/}"
162
+ end
163
+
164
+ def pretty_format(precision = 8)
165
+ str = format(precision).dup
166
+ stop = negative? ? 4 : 3
135
167
 
136
- str = "%.0#{precision}f" % Rational(@fractions, 1000000000000000000)
137
- (str =~ /^-?0(\.0+)?$/ && @fractions != 0) ? str << ?* : str
168
+ n = str.index('.') || str.length
169
+ str.insert(n -= 3, ',') while n > stop
170
+
171
+ return str
138
172
  end
139
173
 
174
+
140
175
  # ------- serialization -------------------------------------------
141
176
 
142
177
  def to_json(state)
@@ -170,30 +205,36 @@ class Fixed
170
205
  # variables, and we found caching the special variables to be faster
171
206
  # (apparently new substrings are created upon each access), and also
172
207
  # we found string concatenation and converting integer only once to
173
- # be faster than two conversions and arithmetic concatenation that
174
- # correctly handles the sign for all negative numbers...
208
+ # be faster than two conversions and arithmetic operations.
175
209
 
176
210
  str =~ /\A(-?\d+)(?:\.(\d{1,18}))?\Z/
177
211
  whole, decimals = $1, $2
178
- raise "expected number with up to 18 decimal places, got #{str.inspect}" unless whole
179
- if decimals and decimals.length == 18
212
+ raise "expected valid string representation, got #{str.inspect}" unless whole
213
+
214
+ if decimals == nil
215
+ whole.to_i * 1000000000000000000
216
+ elsif decimals.length == 18
180
217
  "#{whole}#{decimals}".to_i
181
- elsif decimals
182
- "#{whole}#{decimals}".to_i * (10 ** (18 - decimals.length))
183
218
  else
184
- whole.to_i * 1000000000000000000
219
+ "#{whole}#{decimals}".ljust(whole.length + 18, ?0).to_i
185
220
  end
186
221
  end
187
222
 
188
223
  def self.number_as_fractions(number)
189
224
  case number
190
225
  when Float
191
- # This approach ensures consistency with the visible representation
192
- # of floats by avoiding rounding errors that may occur if we simply
193
- # multiply by 18 digits, considering that floats have only about
194
- # 15 digits of precision (see unit tests for examples).
195
-
196
- Integer Rational(number.to_s) * 1000000000000000000
226
+ # NOTE: Ensures consistency with the visible representation of floats
227
+ # to avoid rounding errors such as 16.50479841 => 1.6504798409999998976
228
+
229
+ number.to_s =~ /\A(-?\d+)(?:\.(\d+))(?:e([+-]\d+))?\Z/
230
+ whole, decimals, padding = $1, $2, $3.to_i + 18
231
+ raise "unsupported floating-point value: #{number}" unless whole
232
+
233
+ if padding < decimals.length
234
+ "#{whole}#{decimals}"[0..(padding - decimals.length - 1)].to_i
235
+ else
236
+ "#{whole}#{decimals}".ljust(whole.length + padding, ?0).to_i
237
+ end
197
238
  else
198
239
  Integer number * 1000000000000000000
199
240
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fixed
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Kuhn
@@ -35,7 +35,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: 2.6.0
38
+ version: 1.9.3
39
39
  required_rubygems_version: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="