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.
- checksums.yaml +7 -0
- data/README.md +108 -0
- data/lib/fixed/version.rb +20 -0
- data/lib/fixed.rb +226 -0
- 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: []
|