fixed 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []