fixed 1.0.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 +7 -0
  2. data/README.md +108 -0
  3. data/lib/fixed/version.rb +20 -0
  4. data/lib/fixed.rb +226 -0
  5. metadata +49 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e87ed315b7c15cb15aa8663e00e8090b29d352babf0d30191da6d16ccca1640
4
+ data.tar.gz: fb6001610413384c17bdd211bcbccd471c8e5c506e4c07bdabef3814903e11b3
5
+ SHA512:
6
+ metadata.gz: 333bb7867bddec4a1939e205d35e5c05e0443dce411f766d263f3d27c573a54c93a8d021204fbc4edf0d4b0c255a99b6323332464dfbe7afc14a2fbe1a6cf6e9
7
+ data.tar.gz: edcda27654f5edce4d586d463d4f682e0f552a525eef203e68655efa751fbc2235437d34215332530e7cba0179420e366efa5146ef315deff24c6e53b687ee27
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Fixed
2
+
3
+ Fixed is a Ruby gem that provides a fixed-point implementation of numbers with 18-digit precision. It is designed to avoid issues with floating-point rounding errors and ensures that arithmetic operations are performed with full precision. This gem is useful in situations where it is necessary to represent rational numbers with a high degree of precision, such as in scientific or financial applications.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'fixed'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install fixed
20
+
21
+ ## Usage
22
+
23
+ Here's an example of how to use the Fixed gem:
24
+
25
+ ```ruby
26
+ require 'fixed'
27
+
28
+ # Create Fixed instances
29
+ a = Fixed(1)
30
+ b = Fixed(6)
31
+
32
+ # Perform arithmetic operations
33
+ sum = a + b
34
+ difference = a - b
35
+ product = a * b
36
+ ratio = a / b
37
+
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
43
+ ```
44
+
45
+ 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.
46
+
47
+ ## API
48
+
49
+ ### Fixed Class
50
+
51
+ The `Fixed` class represents a fixed-point number with 18-digit precision.
52
+
53
+ #### Constructors
54
+
55
+ - `Fixed.parse(str)`: Creates a new `Fixed` instance by parsing a string representation of a fixed-point number.
56
+ - `Fixed.from_number(number)`: Creates a new `Fixed` instance from a numeric value.
57
+ - `Fixed.from_fractions(fractions)`: Creates a new `Fixed` instance from the raw number of fractions.
58
+ - `Fixed.smallest`: Returns a `Fixed` instance representing the smallest possible value (1e-18).
59
+ - `Fixed.zero`: Returns a `Fixed` instance representing zero (0).
60
+
61
+ #### Arithmetic Operations
62
+
63
+ The `Fixed` class supports the following arithmetic operations:
64
+
65
+ - `+`, `-`, `*`, `/`: Addition, subtraction, multiplication, and division operations.
66
+ - `-@`: Unary negation (returns a new `Fixed` instance with the opposite sign).
67
+ - `abs`: Returns the absolute value of the `Fixed` instance.
68
+
69
+ #### Comparisons
70
+
71
+ The `Fixed` class includes the `Comparable` module, allowing you to compare instances of `Fixed` using comparison operators such as `<`, `>`, `<=`, `>=`, `==`, and `<=>`.
72
+
73
+ #### String Formatting
74
+
75
+ - `to_s(precision = 8)`: Returns a string representation of the `Fixed` instance with the specified precision.
76
+ - `format(precision = 8)`: Returns a formatted string representation of the `Fixed` instance with the specified precision.
77
+ - `pretty_format(precision = 8)`: Returns a formatted string representation of the `Fixed` instance with the specified precision, including thousands separators.
78
+
79
+ ### Kernel Method
80
+
81
+ The `Fixed` gem also adds a `Fixed` method to the `Kernel` module, allowing you to create `Fixed` instances using a convenient syntax. For example:
82
+
83
+ ```ruby
84
+ a = Fixed(1)
85
+ ```
86
+
87
+ This is equivalent to:
88
+
89
+ ```ruby
90
+ a = Fixed.from_number(1)
91
+ ```
92
+
93
+ ### Numeric Extension
94
+
95
+ The `Fixed` gem extends the `Numeric` class with a `to_fixed` method, allowing you to convert numeric values to `Fixed` instances. For example:
96
+
97
+ ```ruby
98
+ number = 1.23
99
+ fixed = number.to_fixed
100
+ ```
101
+
102
+ ## Contributing
103
+
104
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/akuhn/fixed](https://github.com/akuhn/fixed). This project encourages collaboration and appreciates contributions. Feel free to contribute to the project by reporting bugs or submitting pull requests.
105
+
106
+ ## License
107
+
108
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fixed
4
+ VERSION = "1.0.0"
5
+ end
6
+
7
+
8
+ __END__
9
+
10
+ # Major version bump when breaking changes or new features
11
+ # Minor version bump when backward-compatible changes or enhancements
12
+ # Patch version bump when backward-compatible bug fixes, security updates etc
13
+
14
+ 1.0.0
15
+
16
+ - Added Fixed-number with 18-digit precision
17
+ - Introduced convenient constructors for creating fixed numbers
18
+ - Implemented basic arithmetic operations support
19
+ - Added functionality for comparing fixed numbers
20
+ - Included number formatting capabilities
data/lib/fixed.rb ADDED
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fixed/version'
4
+
5
+
6
+ # A number with 18-digit precision.
7
+ #
8
+ # Fixed-point implementation of numbers with 18-digit precision, which
9
+ # avoids issues with floating-point rounding errors and ensures that
10
+ # arithmetic operations are performed with full precision. This class is
11
+ # useful in situations where it's necessary to represent rational
12
+ # numbers with a high degree of precision, such as in scientific or
13
+ # financial applications.
14
+ #
15
+ # Example usage
16
+ #
17
+ # a = Fixed(1)
18
+ # b = Fixed(6)
19
+ # ratio = a / b
20
+ # puts ratio.format(18) # => prints 1.666666666666666667
21
+ #
22
+ #
23
+
24
+
25
+ class Fixed
26
+
27
+ include Comparable
28
+
29
+
30
+ #------- constructors ---------------------------------------------
31
+
32
+ private_class_method :new
33
+
34
+ def initialize(fractions)
35
+ raise unless Integer === fractions
36
+ @fractions = fractions
37
+ freeze
38
+ end
39
+
40
+ def self.parse(str)
41
+ new string_as_fractions(str)
42
+ end
43
+
44
+ def self.from_number(number)
45
+ new number_as_fractions(number)
46
+ end
47
+
48
+ def self.from_fractions(fractions)
49
+ new fractions
50
+ end
51
+
52
+ def self.smallest
53
+ new 1
54
+ end
55
+
56
+ def self.zero
57
+ new 0
58
+ end
59
+
60
+ #------- arithmetics ----------------------------------------------
61
+
62
+ def +(number)
63
+ make(self.fractions + number.fractions)
64
+ end
65
+
66
+ def -(number)
67
+ make(self.fractions - number.fractions)
68
+ end
69
+
70
+ def *(number)
71
+ make(division_with_rounding(
72
+ self.fractions * number.fractions,
73
+ 1000000000000000000,
74
+ ))
75
+ end
76
+
77
+ def /(number)
78
+ make(division_with_rounding(
79
+ 1000000000000000000 * self.fractions,
80
+ number.fractions,
81
+ ))
82
+ end
83
+
84
+ def -@
85
+ make(-self.fractions)
86
+ end
87
+
88
+ def abs
89
+ make(self.fractions.abs)
90
+ end
91
+
92
+ # ------- comparing -----------------------------------------------
93
+
94
+ def <=>(number)
95
+ return nil unless Fixed === number
96
+ self.fractions <=> number.fractions
97
+ end
98
+
99
+ def ==(number)
100
+ Fixed === number && self.fractions == number.fractions
101
+ end
102
+
103
+ def negative?
104
+ self.fractions.negative?
105
+ end
106
+
107
+ def positive?
108
+ self.fractions.positive?
109
+ end
110
+
111
+ def zero?
112
+ self.fractions.zero?
113
+ end
114
+
115
+
116
+ # ------- printing ------------------------------------------------
117
+
118
+ def inspect
119
+ if @fractions >= 0
120
+ @fractions.to_s.rjust(19, ?0).insert(-19, ?.)
121
+ else
122
+ "-#{@fractions.abs.to_s.rjust(19, ?0).insert(-19, ?.)}"
123
+ end
124
+ end
125
+
126
+ def to_s(precision = 8)
127
+ format(precision)
128
+ end
129
+
130
+ def format(precision = 8)
131
+ raise "expected 1..18, got #{precision.inspect}" unless (0..18) === precision
132
+
133
+ # Note: although Ruby's documentation indicates otherwise, Rational values
134
+ # are actually formatted with full precision rather than as floats.
135
+
136
+ str = "%.0#{precision}f" % Rational(@fractions, 1000000000000000000)
137
+ (str =~ /^-?0(\.0+)?$/ && @fractions != 0) ? str << ?* : str
138
+ end
139
+
140
+ # ------- serialization -------------------------------------------
141
+
142
+ def to_json(state)
143
+ inspect.to_json(state)
144
+ end
145
+
146
+ def self.from_snapshot(arg, options)
147
+ String === arg ? self.parse(arg) : arg
148
+ end
149
+
150
+
151
+ # ------- helpers -------------------------------------------------
152
+
153
+ attr_reader :fractions
154
+
155
+ def to_fixed
156
+ self
157
+ end
158
+
159
+ protected
160
+
161
+ def make(new_fractions)
162
+ Fixed.from_fractions new_fractions
163
+ end
164
+
165
+ def self.string_as_fractions(str)
166
+
167
+ # NOTE: this code has been optimized to balance execution time versus
168
+ # creation of unnecessary objects and string copies. We found calling
169
+ # String#match to be slower than calling Regexp#=~ and using special
170
+ # variables, and we found caching the special variables to be faster
171
+ # (apparently new substrings are created upon each access), and also
172
+ # 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...
175
+
176
+ str =~ /\A(-?\d+)(?:\.(\d{1,18}))?\Z/
177
+ 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
180
+ "#{whole}#{decimals}".to_i
181
+ elsif decimals
182
+ "#{whole}#{decimals}".to_i * (10 ** (18 - decimals.length))
183
+ else
184
+ whole.to_i * 1000000000000000000
185
+ end
186
+ end
187
+
188
+ def self.number_as_fractions(number)
189
+ case number
190
+ 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
197
+ else
198
+ Integer number * 1000000000000000000
199
+ end
200
+ end
201
+
202
+ private
203
+
204
+ def division_with_rounding(numerator, denominator)
205
+ (numerator + (denominator / 2)) / denominator
206
+ end
207
+ end
208
+
209
+ module Kernel
210
+ def Fixed(arg)
211
+ case arg
212
+ when String
213
+ Fixed.parse arg
214
+ when Numeric
215
+ Fixed.from_number arg
216
+ else
217
+ raise ArgumentError
218
+ end
219
+ end
220
+ end
221
+
222
+ class Numeric
223
+ def to_fixed
224
+ Fixed.from_number self
225
+ end
226
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fixed
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Adrian Kuhn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-05-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - akuhn@iam.unibe.ch
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - lib/fixed.rb
22
+ - lib/fixed/version.rb
23
+ homepage: https://github.com/akuhn/fixed
24
+ licenses:
25
+ - MIT
26
+ metadata:
27
+ homepage_uri: https://github.com/akuhn/fixed
28
+ source_code_uri: https://github.com/akuhn/fixed
29
+ changelog_uri: https://github.com/akuhn/fixed/blob/master/lib/fixed/version.rb
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: 2.6.0
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubygems_version: 3.3.7
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Fixed-point numbers with 18-digit precision.
49
+ test_files: []