numerals 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +149 -5
  3. data/lib/numerals/conversions/bigdecimal.rb +209 -9
  4. data/lib/numerals/conversions/context_conversion.rb +40 -0
  5. data/lib/numerals/conversions/float.rb +106 -71
  6. data/lib/numerals/conversions/flt.rb +115 -44
  7. data/lib/numerals/conversions/integer.rb +32 -3
  8. data/lib/numerals/conversions/rational.rb +27 -3
  9. data/lib/numerals/conversions.rb +74 -33
  10. data/lib/numerals/digits.rb +8 -5
  11. data/lib/numerals/format/base_scaler.rb +160 -0
  12. data/lib/numerals/format/exp_setter.rb +218 -0
  13. data/lib/numerals/format/format.rb +257 -0
  14. data/lib/numerals/format/input.rb +140 -0
  15. data/lib/numerals/format/mode.rb +157 -0
  16. data/lib/numerals/format/notation.rb +51 -0
  17. data/lib/numerals/format/notations/html.rb +53 -0
  18. data/lib/numerals/format/notations/latex.rb +48 -0
  19. data/lib/numerals/format/notations/text.rb +141 -0
  20. data/lib/numerals/format/output.rb +167 -0
  21. data/lib/numerals/format/symbols.rb +565 -0
  22. data/lib/numerals/format/text_parts.rb +35 -0
  23. data/lib/numerals/format.rb +25 -0
  24. data/lib/numerals/formatting_aspect.rb +36 -0
  25. data/lib/numerals/numeral.rb +34 -21
  26. data/lib/numerals/repeat_detector.rb +99 -0
  27. data/lib/numerals/rounding.rb +340 -181
  28. data/lib/numerals/version.rb +1 -1
  29. data/lib/numerals.rb +4 -2
  30. data/numerals.gemspec +1 -1
  31. data/test/test_base_scaler.rb +189 -0
  32. data/test/test_big_conversions.rb +105 -0
  33. data/test/test_digits_definition.rb +23 -28
  34. data/test/test_exp_setter.rb +732 -0
  35. data/test/test_float_conversions.rb +48 -30
  36. data/test/test_flt_conversions.rb +476 -80
  37. data/test/test_format.rb +124 -0
  38. data/test/test_format_input.rb +226 -0
  39. data/test/test_format_mode.rb +124 -0
  40. data/test/test_format_output.rb +789 -0
  41. data/test/test_integer_conversions.rb +22 -22
  42. data/test/test_numeral.rb +35 -0
  43. data/test/test_rational_conversions.rb +28 -28
  44. data/test/test_repeat_detector.rb +72 -0
  45. data/test/test_rounding.rb +158 -0
  46. data/test/test_symbols.rb +32 -0
  47. metadata +38 -5
  48. data/lib/numerals/formatting/digits_definition.rb +0 -75
@@ -0,0 +1,257 @@
1
+ module Numerals
2
+
3
+ # A Format object holds formatting options and performs
4
+ # formatted input/output operations on numbers.
5
+ #
6
+ # Formatting options are grouped into aspects:
7
+ #
8
+ # * Exact input
9
+ # * Rounding
10
+ # * Mode
11
+ # * Symbols
12
+ # * Input rounding
13
+ #
14
+ # Some aspects (Rounding, Mode & Symbols) are handled with aspect-definining
15
+ # classes Rounding, Format::Mode and Format::Symbols.
16
+ #
17
+ # Exact input applies only to numeric types that can hold limited
18
+ # precision values such as Float, Flt::Num or BigDecimal. It specifies
19
+ # that the numeric value is to be taken as an exact quantity. Otherwise,
20
+ # the numeric value is interpreted as a rounded approximation of some
21
+ # original exact value (so it represents a range of exact number which
22
+ # would all be rounded to the same approximation). Rational and Integer
23
+ # types are always exact and not affected by this option.
24
+ #
25
+ # Rounding defines how numbers are rounded into text form or how
26
+ # values represented in text round to numeric values. So it specifies
27
+ # the precision of the result and whether the result is an approximation
28
+ # or an exact quantity.
29
+ #
30
+ # Mode defines de formatting style.
31
+ #
32
+ # Symbols contains the details of how digits and other symbols are
33
+ # represented in text form and the final text notation used.
34
+ #
35
+ # The input-rounding property can set to either a Rounding object
36
+ # or just a rounding mode symbol (:half_even, etc.).
37
+ # It is used to define which rounding mode is implied when reading textual
38
+ # numeric expressions into approximate numeric values. It affects how
39
+ # approximate numbers are written to text because the text representation
40
+ # of approximate values should be read back into the original value.
41
+ # If a Rounding object is assigned only the mode is used, and it is ignored
42
+ # if the rounding is exact. #
43
+ #
44
+ class Format < FormattingAspect
45
+
46
+ def initialize(*args)
47
+ @exact_input = false
48
+ @rounding = Rounding[:short]
49
+ @mode = Mode[]
50
+ @symbols = Symbols[]
51
+ @notation = :text
52
+ @input_rounding = nil
53
+ set! *args
54
+ end
55
+
56
+ attr_reader :rounding, :exact_input, :mode, :symbols, :notation,
57
+ :input_rounding
58
+
59
+ def base
60
+ @rounding.base
61
+ end
62
+
63
+ # Presentation base for the significand
64
+ def significand_base
65
+ base**@mode.base_scale
66
+ end
67
+
68
+ include ModalSupport::StateEquivalent
69
+
70
+ def input_rounding?
71
+ !@input_rounding.nil?
72
+ end
73
+
74
+ def input_rounding_mode
75
+ input_rounding? ? @input_rounding.mode : nil
76
+ end
77
+
78
+ set do |*args|
79
+ options = extract_options(*args)
80
+ @exact_input = options[:exact_input] if options.has_key?(:exact_input)
81
+ @rounding.set! options[:rounding] if options[:rounding]
82
+ @mode.set! options[:mode] if options[:mode] # :format ?
83
+ @symbols.set! options[:symbols] if options[:symbols]
84
+ @notation = options[:notation] if options[:notation]
85
+ if options.has_key?(:input_rounding)
86
+ set_input_rounding! options[:input_rounding]
87
+ end
88
+
89
+ # shortcuts
90
+ @rounding.set! base: options[:base] if options[:base]
91
+ @symbols.set! digits: options[:digits] if options[:digits]
92
+ @rounding.set! mode: options[:rounding_mode] if options[:rounding_mode]
93
+ @rounding.set! precision: options[:precision] if options[:precision]
94
+ @rounding.set! places: options[:places] if options[:places]
95
+ @symbols.set! repeating: options[:repeating] if options.has_key?(:repeating)
96
+ @symbols.set! case_sensitive: options[:case_sensitive] if options.has_key?(:case_sensitive)
97
+ end
98
+
99
+ def parameters
100
+ {
101
+ rounding: @rounding,
102
+ exact_input: @exact_input,
103
+ mode: @mode,
104
+ symbols: @symbols,
105
+ notation: @notation,
106
+ input_rounding: input_rounding? ? @input_rounding : nil
107
+ }
108
+ end
109
+
110
+ def to_s
111
+ args = []
112
+ args << "exact_input: true" if @exact_input
113
+ args << "rounding: #{@rounding}"
114
+ args << "mode: #{@mode}"
115
+ args << "symbols: #{@symbols}"
116
+ args << "notation: #{@notation.inspect}" if @notation != :text
117
+ args << "input_rounding: #{input_rounding_mode.inspect}" if input_rounding?
118
+ "Format[#{args.join(', ')}]"
119
+ end
120
+
121
+ def inspect
122
+ to_s
123
+ end
124
+
125
+ def case_sensitive?
126
+ @symbols.case_sensitive?
127
+ end
128
+
129
+ aspect :rounding do |*args|
130
+ set! rounding: args
131
+ end
132
+
133
+ aspect :base do |base|
134
+ set! base: base
135
+ end
136
+
137
+ aspect :exact_input do |value|
138
+ @exact_input = value
139
+ end
140
+
141
+ aspect :mode do |*args|
142
+ set! mode: args
143
+ end
144
+
145
+ aspect :symbols do |*args|
146
+ set symbols: args
147
+ end
148
+
149
+ aspect :notation do |notation|
150
+ set! notation: notation
151
+ end
152
+
153
+ aspect :digits do |digits|
154
+ set! digits: digits
155
+ end
156
+
157
+ aspect :input_rounding do |input_roundig|
158
+ if input_roundig.nil?
159
+ @input_rounding = nil
160
+ else
161
+ if @input_rounding.nil?
162
+ @input_rounding = Rounding[input_roundig]
163
+ else
164
+ @input_rounding.set! input_roundig
165
+ end
166
+ end
167
+ end
168
+
169
+ def dup
170
+ # we need deep copy
171
+ Format[parameters]
172
+ end
173
+
174
+ include Output
175
+ include Input
176
+
177
+ # Shortcuts to Symbols sub-aspects
178
+
179
+ aspect :repeat do |*args|
180
+ @symbols.set_repeat!(*args)
181
+ end
182
+
183
+ aspect :grouping do |*args|
184
+ @symbols.set_grouping!(*args)
185
+ end
186
+
187
+ aspect :group_thousands do |sep = nil|
188
+ @symbols.set_grouping!(sep)
189
+ end
190
+
191
+ aspect :signs do |plus, minus|
192
+ @symbols.set_signs!(plus, minus)
193
+ end
194
+
195
+ aspect :plus do |plus, which = nil|
196
+ @symbols.set_plus!(plus, which)
197
+ end
198
+
199
+ aspect :minus do |minus|
200
+ @symbols.set_minus!(minus)
201
+ end
202
+
203
+ private
204
+
205
+ def extract_options(*args)
206
+ options = {}
207
+ args = args.first if args.size == 1 && args.first.kind_of?(Array)
208
+ args.each do |arg|
209
+ case arg
210
+ when Hash
211
+ options.merge! arg
212
+ when Rounding
213
+ options[:rounding] = arg
214
+ when Mode
215
+ options[:mode] = arg
216
+ when Symbols
217
+ options[:symbols] = arg
218
+ when Symbols::Digits
219
+ options[:digits] = arg
220
+ when Format
221
+ options.merge! arg.parameters
222
+ when :exact_input
223
+ options[:exact_input] = true
224
+ when :hexbin
225
+ options.merge!(
226
+ base: 2,
227
+ mode: {
228
+ base_scale: 4,
229
+ mode: :scientific,
230
+ sci_int_digits: 1
231
+ },
232
+ symbols: {
233
+ exponent: 'p'
234
+ }
235
+ )
236
+ when :gen, :general, :sci, :scientific, :fix; :fixed
237
+ options[:mode] = Mode[arg]
238
+ when :short, :free, :simplify, :preserve
239
+ options[:precision] = arg
240
+ when :half_even, :half_down, :half_up, :down, :up, :ceiling, :floor, :up05
241
+ options[:rounding_mode] = arg
242
+ when :case_sensitive
243
+ options[:case_sensitive] = true
244
+ when Symbol
245
+ options[:notation] = arg
246
+ when Integer
247
+ options[:base] = arg
248
+ else
249
+ raise "Invalid Format definition"
250
+ end
251
+ end
252
+ options
253
+ end
254
+
255
+ end
256
+
257
+ end
@@ -0,0 +1,140 @@
1
+ module Numerals
2
+
3
+ # Formatted input implementation
4
+ module Format::Input
5
+
6
+ def read(text, options={})
7
+ # 1. Obtain destination type
8
+ selector = options[:context] || options[:type]
9
+ conversion = Conversions[selector, options[:type_options]]
10
+ if conversion
11
+ type = conversion.type
12
+ else
13
+ type = options[:type]
14
+ if type != Numeral
15
+ raise Format::InvalidNumericType, "Invalid type #{selector.inspect}"
16
+ end
17
+ end
18
+
19
+ # if conversion.is_a?(ContextConversion)
20
+ # context = conversion.context
21
+ # end
22
+
23
+ # 2. dissassemble (parse notation): text notation => text parts
24
+ text_parts = Format.disassemble(@notation, self, text)
25
+
26
+ # 3. Convert text parts to values and generate a Numeral
27
+ numeral = partition_in(text_parts)
28
+
29
+ # 4. Convert to requested type:
30
+ if type == Numeral
31
+ return numeral
32
+ else
33
+ conversion_in(numeral, options)
34
+ end
35
+ end
36
+
37
+ def partition_in(text_parts)
38
+ if text_parts.special?
39
+ # Read special number
40
+ nan = /#{@symbols.regexp(:nan, case_sensitivity: true)}/
41
+ inf = /
42
+ #{@symbols.regexp(:plus, :minus, case_sensitivity: true)}?
43
+ \s*
44
+ #{@symbols.regexp(:infinity, case_sensitivity: true)}
45
+ /x
46
+ minus = /#{@symbols.regexp(:minus, case_sensitivity: true)}/
47
+ if nan.match(text_parts.special)
48
+ numeral = Numeral[:nan]
49
+ elsif match = inf.match(text_parts.special)
50
+ if match[1] && match[1] =~ minus
51
+ sign = -1
52
+ else
53
+ sign = +1
54
+ end
55
+ numeral = Numeral[:infinity, sign: sign]
56
+ else
57
+ raise Format::InvaludNumberFormat, "Invalid number"
58
+ end
59
+ else
60
+ # Parse and convert text parts to values
61
+ input_rounding = @input_rounding || @rounding
62
+ input_base = significand_base
63
+
64
+ if !@symbols.repeating && (text_parts.repeat || text_parts.detect_repeat)
65
+ raise Format::InvalidRepeatingNumeral, "Invalid format: unexpected repeating numeral"
66
+ end
67
+
68
+ minus = /#{@symbols.regexp(:minus, case_sensitivity: true)}/
69
+ if text_parts.sign? && text_parts.sign =~ minus
70
+ sign = -1
71
+ else
72
+ sign = +1
73
+ end
74
+
75
+ integer_digits = []
76
+ if text_parts.integer?
77
+ integer_digits = @symbols.digits_values(text_parts.integer, base: input_base)
78
+ end
79
+
80
+ fractional_digits = []
81
+ if text_parts.fractional?
82
+ fractional_digits = @symbols.digits_values(text_parts.fractional, base: input_base)
83
+ end
84
+
85
+ exponent_value = 0
86
+ if text_parts.exponent?
87
+ exponent_value = text_parts.exponent.to_i
88
+ end
89
+
90
+ point = integer_digits.size
91
+
92
+ if text_parts.detect_repeat? # repeat_suffix found
93
+ digits = integer_digits + fractional_digits
94
+ digits, repeat = RepeatDetector.detect(digits, @symbols.repeat_count - 1)
95
+ elsif text_parts.repeat?
96
+ repeat_digits = @symbols.digits_values(text_parts.repeat, base: input_base)
97
+ digits = integer_digits + fractional_digits
98
+ repeat = digits.size
99
+ digits += repeat_digits
100
+ else
101
+ digits = integer_digits + fractional_digits
102
+ repeat = nil
103
+ end
104
+
105
+ if @mode.base_scale > 1
106
+ # De-scale the significand base
107
+ digits = Format::BaseScaler.ugrouped_digits(digits, base, @mode.base_scale)
108
+ point *= @mode.base_scale
109
+ repeat *= @mode.base_scale if repeat
110
+ end
111
+
112
+ point += exponent_value
113
+
114
+ # Generate Numeral
115
+ if repeat || @exact_input
116
+ normalization = :exact
117
+ else
118
+ normalization = :approximate
119
+ end
120
+ numeral = Numeral[digits, sign: sign, point: point, repeat: repeat, base: base, normalize: normalization]
121
+ end
122
+ end
123
+
124
+ def conversion_in(numeral, options)
125
+ options = options.merge(
126
+ exact: numeral.exact?, # @exact_input, Also: if we want to force :fixed format
127
+ simplify: @rounding.simplifying?, # applies to approx input only => :short
128
+ )
129
+ type_options = { input_rounding: @input_rounding || @rounding }
130
+ if options[:type_options]
131
+ options[:type_options] = type_options.merge(options[:type_options])
132
+ else
133
+ options[:type_options] = type_options
134
+ end
135
+ Conversions.read(numeral, options)
136
+ end
137
+
138
+ end
139
+
140
+ end
@@ -0,0 +1,157 @@
1
+ module Numerals
2
+
3
+ # Formatting mode
4
+ #
5
+ # * :scientific use scientific notation.
6
+ # * :fixed used fixed notation.
7
+ # * :general (the default) uses :fixed notation unless
8
+ # it would produce trailing zeros in the integer part
9
+ # or too many leading zeros in the fractional part.
10
+ # The intent is to produced the simplest or more natural
11
+ # output and it's regulated by the :max_leading and
12
+ # :max_trailing parameters.
13
+ #
14
+ # The special value :engineering can be used as a shortcut
15
+ # for :scientific mode with :engineering :sci_int_digits.
16
+ #
17
+ # The modes can be abbreviated as :sci, :fix, :gen and :end.
18
+ #
19
+ # * :sci_int_digits numbe of digits to the left of the point
20
+ # in scientific notation. By default 1. The special value
21
+ # :engineering can be used for engineering notation, i.e.
22
+ # the number of digits will be between one and 3 so that the
23
+ # exponent is a multiple of 3.
24
+ # The special value :all is used to make the significand an
25
+ # integer value.
26
+ #
27
+ # * :max_leading (maximum number of leading zeros) determines
28
+ # when :scientific is chosen instead of :fixed when the mode is
29
+ # :general.
30
+ # The default value, 5 is that of the General Decimal Arithmetic
31
+ # 'to-scientific-string' number to text conversion, and also used
32
+ # by the .NET General ("G") format specifier. To reproduce the
33
+ # behaviour of the C %g format specifier the value should be 3.
34
+ # The special value :all can be used to reproduce the behaviour
35
+ # of some calculators, where scientific notation is used when
36
+ # more digits than the specified precision would be needed.
37
+ #
38
+ class Format::Mode < FormattingAspect
39
+
40
+ DEFAULTS = {
41
+ mode: :general,
42
+ sci_int_digits: 1,
43
+ max_leading: 5,
44
+ max_trailing: 0,
45
+ base_scale: 1
46
+ }
47
+
48
+ def initialize(*args)
49
+ DEFAULTS.each do |param, value|
50
+ instance_variable_set "@#{param}", value
51
+ end
52
+ set! *args
53
+ end
54
+
55
+ attr_reader :mode, :sci_int_digits, :max_leading, :max_trailing, :base_scale
56
+
57
+ include ModalSupport::StateEquivalent
58
+
59
+ set do |*args|
60
+ options = extract_options(*args)
61
+ options.each do |option, value|
62
+ send :"#{option}=", value
63
+ end
64
+ end
65
+
66
+ MODE_SHORTCUTS = {
67
+ gen: :general,
68
+ sci: :scientific,
69
+ fix: :fixed,
70
+ eng: :engineering
71
+ }
72
+
73
+ def mode=(mode)
74
+ @mode = MODE_SHORTCUTS[mode] || mode
75
+ if @mode == :engineering
76
+ @mode = :scientific
77
+ @sci_int_digits = :engineering
78
+ end
79
+ end
80
+
81
+ def sci_int_digits=(v)
82
+ @sci_int_digits = v
83
+ if @sci_int_digits == :eng
84
+ @sci_int_digits = :engineering
85
+ end
86
+ end
87
+
88
+ attr_writer :max_leading, :max_trailing, :base_scale
89
+
90
+ def engineering?
91
+ @mode == :scientific && @sci_int_digits == :engineering
92
+ end
93
+
94
+ def scientific?
95
+ @mode == :scientific
96
+ end
97
+
98
+ def fixed?
99
+ @mode == :fixed
100
+ end
101
+
102
+ def general?
103
+ @mode == :general
104
+ end
105
+
106
+ def parameters(abbreviated=false)
107
+ params = {}
108
+ DEFAULTS.each do |param, default|
109
+ value = instance_variable_get("@#{param}")
110
+ if !abbreviated || value != default
111
+ params[param] = value
112
+ end
113
+ end
114
+ if abbreviated && engineering?
115
+ params[:mode] = :engineering
116
+ params.delete :sci_int_digits
117
+ end
118
+ params
119
+ end
120
+
121
+ def to_s
122
+ "Mode[#{parameters(true).inspect.unwrap('{}')}]"
123
+ end
124
+
125
+ def inspect
126
+ "Format::#{self}"
127
+ end
128
+
129
+ # Note: since Mode has no mutable attributes, default dup is OK
130
+ # otherwise we'd need to redefine it:
131
+ # def dup
132
+ # Mode[parameters]
133
+ # end
134
+
135
+ private
136
+
137
+ def extract_options(*args)
138
+ options = {}
139
+ args = args.first if args.size == 1 && args.first.kind_of?(Array)
140
+ args.each do |arg|
141
+ case arg
142
+ when Hash
143
+ options.merge! arg
144
+ when Symbol
145
+ options[:mode] = arg
146
+ when Format::Mode
147
+ options.merge! arg.parameters
148
+ else
149
+ raise "Invalid Mode definition"
150
+ end
151
+ end
152
+ options
153
+ end
154
+
155
+ end
156
+
157
+ end
@@ -0,0 +1,51 @@
1
+ module Numerals
2
+
3
+ class Format
4
+
5
+ class Notation
6
+
7
+ def initialize(format)
8
+ @format = format
9
+ end
10
+
11
+ attr_reader :format
12
+
13
+ def assemble(output, text_parts)
14
+ raise "assemble must be implemented in Notation derived class #{self.class}"
15
+ end
16
+
17
+ def disassemble(text)
18
+ raise "disassemble must be implemented in Notation derived class #{self.class}"
19
+ end
20
+
21
+ end
22
+
23
+ @notations = {}
24
+
25
+ def self.define_notation(id, notation_class)
26
+ unless notation_class.class == Class && notation_class.superclass == Notation
27
+ raise "Notation class must be derived from Format::Notation"
28
+ end
29
+ @notations[id] = notation_class
30
+ end
31
+
32
+ def self.notation(id, format)
33
+ @notations[id].new(format) || raise("Unknown notation #{id.inspect}")
34
+ end
35
+
36
+
37
+ def self.assemble(id, output, format, text_parts)
38
+ notation(id, format).assemble(output, text_parts)
39
+ end
40
+
41
+ def self.disassemble(id, format, text)
42
+ notation(id, format).disassemble(text)
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ require 'numerals/format/notations/text'
50
+ require 'numerals/format/notations/latex'
51
+ require 'numerals/format/notations/html'
@@ -0,0 +1,53 @@
1
+ require 'cgi'
2
+
3
+ module Numerals
4
+
5
+ class Format
6
+
7
+ class HtmlNotation < Notation
8
+
9
+ def assemble(output, text_parts)
10
+ # 1.23<span style="text-decoration: overline">456</span> &times;10<sup>9</sup>
11
+ # Or alternative: use classes
12
+ # <span class=”numerals-num”>1.23<span class="numerals-rep">456</span> &times;10<span class="numerals-sup">9</span></span>
13
+ # .numerals-rep { text-decoration: overline; }
14
+ # .numerals-sup { vertical-align: super; }
15
+ if text_parts.special?
16
+ output << escape(text_parts.special)
17
+ else
18
+ output << escape(text_parts.sign)
19
+ output << escape(text_parts.integer) # or decide here if empty integer part is show as 0?
20
+ unless !text_parts.fractional? &&
21
+ !text_parts.repeat? &&
22
+ !format.symbols.show_point
23
+ output << escape(format.symbols.point)
24
+ end
25
+ output << escape(text_parts.fractional)
26
+ if text_parts.repeat
27
+ output << %(<span style="text-decoration: overline">#{escape(text_parts.repeat)}</span>)
28
+ end
29
+ if text_parts.exponent_value != 0 || format.mode.mode == :scientific
30
+ output << "&times;"
31
+ output << escape(text_parts.exponent_base)
32
+ output << "<sup>#{escape(text_parts.exponent)}</sup>"
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def escape(text)
40
+ CGI.escapeHTML(text)
41
+ end
42
+
43
+ def unescape(text)
44
+ CGI.unescapeHTML(text)
45
+ end
46
+
47
+ end
48
+
49
+ define_notation :html, HtmlNotation
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,48 @@
1
+ module Numerals
2
+
3
+ class Format
4
+
5
+ class LatexNotation < Notation
6
+
7
+ def assemble(output, text_parts)
8
+ # 1.23\overline{456}\times10^{9}
9
+ if text_parts.special?
10
+ output << text_parts.special
11
+ else
12
+ output << text_parts.sign
13
+ output << text_parts.integer # or decide here if empty integer part is shown as 0?
14
+ unless !text_parts.fractional? &&
15
+ !text_parts.repeat? &&
16
+ !format.symbols.show_point
17
+ output << format.symbols.point
18
+ end
19
+ output << text_parts.fractional
20
+ if text_parts.repeat?
21
+ output << "\\overline{#{text_parts.repeat}}"
22
+ end
23
+ if text_parts.exponent_value != 0 || format.mode.mode == :scientific
24
+ output << "\\times"
25
+ output << text_parts.exponent_base
26
+ output << "^"
27
+ output << "{#{text_parts.exponent}}"
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def escape(text)
35
+ text.gsub('\\', '\\\\')
36
+ end
37
+
38
+ def unescape(text)
39
+ text.gsub('\\\\', '\\')
40
+ end
41
+
42
+ end
43
+
44
+ define_notation :latex, LatexNotation
45
+
46
+ end
47
+
48
+ end