fixed 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|