Skip to content

Commit 838e138

Browse files
committed
Update comments and move tests around
1 parent 2d0c28b commit 838e138

File tree

3 files changed

+278
-145
lines changed

3 files changed

+278
-145
lines changed

src/parse.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ const RoundThrows = RoundingMode{:Throw}()
1212
struct FixedDecimalConf{T<:Integer} <: AbstractConf{T}
1313
f::Int
1414
end
15-
# this overload says that when parsing a FixedDecimal type, use our new custom FixedDecimalConf type
15+
# This overload says that when parsing a FixedDecimal type, use our new custom FixedDecimalConf type
1616
Parsers.conf(::Type{FixedDecimal{T,f}}, opts::Parsers.Options, kw...) where {T<:Integer,f} = FixedDecimalConf{T}(f)
1717
# Because the value returned from our `typeparser` isn't a FixedDecimal, we overload here to show we're returning an integer type
1818
Parsers.returntype(::Type{FixedDecimal{T,f}}) where {T,f} = T
19-
# this overload allows us to take the Result{IntegerType} returned from typeparser and turn it into a FixedDecimal Result
19+
# This overload allows us to take the Result{IntegerType} returned from typeparser and turn it into a FixedDecimal Result
2020
function Parsers.result(FD::Type{FixedDecimal{T,f}}, res::Parsers.Result{T}) where {T,f}
2121
return Parsers.invalid(res.code) ? Result{FD}(res.code, res.tlen) :
2222
Result{FD}(res.code, res.tlen, reinterpret(FD, res.val))
2323
end
24+
# Tell Parsers that we can use our custom typeparser and not rely on Base.tryparse
25+
Parsers.supportedtype(::Type{<:FixedDecimal}) = true
2426

2527
const OPTIONS_ROUND_NEAREST = Parsers.Options(rounding=RoundNearest)
2628
const OPTIONS_ROUND_TO_ZERO = Parsers.Options(rounding=RoundToZero)
@@ -117,9 +119,9 @@ end
117119
# all digits are zero
118120
i = zero(T)
119121
# The backing_integer_digits == 0 case is handled in the `else` (it means
120-
# that all the digits are passed the precision but we might get `1` from rounding)
122+
# that all the digits are passed the precision but we might get `1` from rounding)
121123
elseif backing_integer_digits < 0
122-
# All digits are past our precision, no overflow possible
124+
# All digits are past our precision, no overflow possible, but we might get an inexact
123125
i = zero(T)
124126
(rounding === RoundThrows) && (code |= Parsers.INEXACT)
125127
elseif neg && (T <: Unsigned)
@@ -165,6 +167,10 @@ end
165167
return Parsers.scale(conf, FT, digits, exp, neg, code, ndigits, f, options)
166168
end
167169

170+
# This hooks into the floating point parsing machinery from Parsers.jl, where we also accumulate
171+
# all the digits and note the effective exponent before we do "scaling" -- for FixedDecimals,
172+
# the scaling means padding the backing integer with zeros or rounding them as necessary.
173+
# We overloaded the "scale" and "noscale" methods to produce backing integers for FixedDecimals.
168174
# We return a value of T -- i.e. the _integer_ backing the FixedDecimal, the reintrpret needs to happen later
169175
@inline function Parsers.typeparser(conf::FixedDecimalConf{T}, source, pos, len, b, code, pl, options) where {T<:Integer}
170176
if !(options.rounding in (nothing, RoundNearest, RoundToZero, RoundThrows))
@@ -202,8 +208,6 @@ end
202208
return pos, code, Parsers.PosLen(pl.pos, pos - pl.pos), x
203209
end
204210

205-
Parsers.supportedtype(::Type{<:FixedDecimal}) = true
206-
207211
function _base_parse(::Type{FD{T, f}}, source::AbstractString, mode::RoundingMode=RoundNearest) where {T, f}
208212
if !(mode in (RoundThrows, RoundNearest, RoundToZero))
209213
throw(ArgumentError("Unhandled rounding mode $mode"))

test/parse_tests.jl

Lines changed: 258 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ end
9595

9696
T = Int64
9797
f = 8
98-
@testset "parse" begin
98+
@testset "xparse" begin
9999
@testset "1.0" begin
100100
@testset "$T" for T in IntegerTypes
101101
@testset "$f" for f in 0:(_maxintdigits(T) - 1)
@@ -425,4 +425,260 @@ end
425425
end
426426
end
427427
end
428-
end # @testset "parse"
428+
end # @testset "xparse"
429+
430+
431+
@testset "parse" begin
432+
# Note: the underscore used in the reinterpreted integer is used to indicate the decimal
433+
# place.
434+
@testset "decimal position" begin
435+
@test parse(FD2, "123") == reinterpret(FD2, 123_00)
436+
@test parse(FD2, "0.123") == reinterpret(FD2, 0_12)
437+
@test parse(FD2, ".123") == reinterpret(FD2, 0_12)
438+
@test parse(FD2, "1.23") == reinterpret(FD2, 1_23)
439+
@test parse(FD2, "12.3") == reinterpret(FD2, 12_30)
440+
@test parse(FD2, "123.") == reinterpret(FD2, 123_00)
441+
@test parse(FD2, "123.0") == reinterpret(FD2, 123_00)
442+
443+
@test parse(FD2, "-123") == reinterpret(FD2, -123_00)
444+
@test parse(FD2, "-0.123") == reinterpret(FD2, -0_12)
445+
@test parse(FD2, "-.123") == reinterpret(FD2, -0_12)
446+
@test parse(FD2, "-1.23") == reinterpret(FD2, -1_23)
447+
@test parse(FD2, "-12.3") == reinterpret(FD2, -12_30)
448+
@test parse(FD2, "-123.") == reinterpret(FD2, -123_00)
449+
@test parse(FD2, "-123.0") == reinterpret(FD2, -123_00)
450+
end
451+
452+
@testset "scientific notation" begin
453+
@test parse(FD4, "12e0") == reinterpret(FD4, 00012_0000)
454+
@test parse(FD4, "12e3") == reinterpret(FD4, 12000_0000)
455+
@test parse(FD4, "12e-3") == reinterpret(FD4, 00000_0120)
456+
@test parse(FD4, "1.2e0") == reinterpret(FD4, 00001_2000)
457+
@test parse(FD4, "1.2e3") == reinterpret(FD4, 01200_0000)
458+
@test parse(FD4, "1.2e-3") == reinterpret(FD4, 00000_0012)
459+
@test parse(FD4, "1.2e-4") == reinterpret(FD4, 00000_0001)
460+
461+
@test parse(FD4, "-12e0") == reinterpret(FD4, -00012_0000)
462+
@test parse(FD4, "-12e3") == reinterpret(FD4, -12000_0000)
463+
@test parse(FD4, "-12e-3") == reinterpret(FD4, -00000_0120)
464+
@test parse(FD4, "-1.2e0") == reinterpret(FD4, -00001_2000)
465+
@test parse(FD4, "-1.2e3") == reinterpret(FD4, -01200_0000)
466+
@test parse(FD4, "-1.2e-3") == reinterpret(FD4, -00000_0012)
467+
468+
@test parse(FD2, "999e-1") == reinterpret(FD2, 99_90)
469+
@test parse(FD2, "999e-2") == reinterpret(FD2, 09_99)
470+
@test parse(FD2, "999e-3") == reinterpret(FD2, 01_00)
471+
@test parse(FD2, "999e-4") == reinterpret(FD2, 00_10)
472+
@test parse(FD2, "999e-5") == reinterpret(FD2, 00_01)
473+
@test parse(FD2, "999e-6") == reinterpret(FD2, 00_00)
474+
475+
@test parse(FD2, "-999e-1") == reinterpret(FD2, -99_90)
476+
@test parse(FD2, "-999e-2") == reinterpret(FD2, -09_99)
477+
@test parse(FD2, "-999e-3") == reinterpret(FD2, -01_00)
478+
@test parse(FD2, "-999e-4") == reinterpret(FD2, -00_10)
479+
@test parse(FD2, "-999e-5") == reinterpret(FD2, -00_01)
480+
@test parse(FD2, "-999e-6") == reinterpret(FD2, -00_00)
481+
482+
@test parse(FD4, "9"^96 * "e-100") == reinterpret(FD4, 0_001)
483+
end
484+
485+
@testset "round to nearest" begin
486+
@test parse(FD2, "0.444") == reinterpret(FD2, 0_44)
487+
@test parse(FD2, "0.445") == reinterpret(FD2, 0_44)
488+
@test parse(FD2, "0.446") == reinterpret(FD2, 0_45)
489+
@test parse(FD2, "0.454") == reinterpret(FD2, 0_45)
490+
@test parse(FD2, "0.455") == reinterpret(FD2, 0_46)
491+
@test parse(FD2, "0.456") == reinterpret(FD2, 0_46)
492+
493+
@test parse(FD2, "-0.444") == reinterpret(FD2, -0_44)
494+
@test parse(FD2, "-0.445") == reinterpret(FD2, -0_44)
495+
@test parse(FD2, "-0.446") == reinterpret(FD2, -0_45)
496+
@test parse(FD2, "-0.454") == reinterpret(FD2, -0_45)
497+
@test parse(FD2, "-0.455") == reinterpret(FD2, -0_46)
498+
@test parse(FD2, "-0.456") == reinterpret(FD2, -0_46)
499+
500+
@test parse(FD2, "0.009") == reinterpret(FD2, 0_01)
501+
@test parse(FD2, "-0.009") == reinterpret(FD2, -0_01)
502+
503+
@test parse(FD4, "1.5e-4") == reinterpret(FD4, 0_0002)
504+
end
505+
506+
@testset "round to zero" begin
507+
@test parse(FD2, "0.444", RoundToZero) == reinterpret(FD2, 0_44)
508+
@test parse(FD2, "0.445", RoundToZero) == reinterpret(FD2, 0_44)
509+
@test parse(FD2, "0.446", RoundToZero) == reinterpret(FD2, 0_44)
510+
@test parse(FD2, "0.454", RoundToZero) == reinterpret(FD2, 0_45)
511+
@test parse(FD2, "0.455", RoundToZero) == reinterpret(FD2, 0_45)
512+
@test parse(FD2, "0.456", RoundToZero) == reinterpret(FD2, 0_45)
513+
514+
@test parse(FD2, "-0.444", RoundToZero) == reinterpret(FD2, -0_44)
515+
@test parse(FD2, "-0.445", RoundToZero) == reinterpret(FD2, -0_44)
516+
@test parse(FD2, "-0.446", RoundToZero) == reinterpret(FD2, -0_44)
517+
@test parse(FD2, "-0.454", RoundToZero) == reinterpret(FD2, -0_45)
518+
@test parse(FD2, "-0.455", RoundToZero) == reinterpret(FD2, -0_45)
519+
@test parse(FD2, "-0.456", RoundToZero) == reinterpret(FD2, -0_45)
520+
521+
@test parse(FD2, "0.009", RoundToZero) == reinterpret(FD2, 0_00)
522+
@test parse(FD2, "-0.009", RoundToZero) == reinterpret(FD2, 0_00)
523+
524+
@test parse(FD4, "1.5e-4", RoundToZero) == reinterpret(FD4, 0_0001)
525+
end
526+
527+
@testset "round throws" begin
528+
@test parse(FD2, "0.44", RoundThrows) == reinterpret(FD2, 0_44)
529+
@test parse(FD2, "0.440", RoundThrows) == reinterpret(FD2, 0_44)
530+
531+
@test_throws InexactError parse(FD2, "0.444", RoundThrows)
532+
@test_throws InexactError parse(FD2, "0.445", RoundThrows)
533+
@test_throws InexactError parse(FD2, "0.446", RoundThrows)
534+
@test_throws InexactError parse(FD2, "0.454", RoundThrows)
535+
@test_throws InexactError parse(FD2, "0.455", RoundThrows)
536+
@test_throws InexactError parse(FD2, "0.456", RoundThrows)
537+
538+
@test_throws InexactError parse(FD2, "-0.444", RoundThrows)
539+
@test_throws InexactError parse(FD2, "-0.445", RoundThrows)
540+
@test_throws InexactError parse(FD2, "-0.446", RoundThrows)
541+
@test_throws InexactError parse(FD2, "-0.454", RoundThrows)
542+
@test_throws InexactError parse(FD2, "-0.455", RoundThrows)
543+
@test_throws InexactError parse(FD2, "-0.456", RoundThrows)
544+
545+
@test_throws InexactError parse(FD2, "0.009", RoundThrows)
546+
@test_throws InexactError parse(FD2, "-0.009", RoundThrows)
547+
@test_throws InexactError parse(FD4, "1.5e-4", RoundThrows)
548+
end
549+
550+
@testset "invalid" begin
551+
@test_throws OverflowError parse(FD4, "1.2e100")
552+
@test_throws ArgumentError parse(FD4, "foo")
553+
@test_throws ArgumentError parse(FD4, "1.2.3")
554+
@test_throws ArgumentError parse(FD4, "1.2", RoundUp)
555+
end
556+
end
557+
558+
559+
@testset "tryparse" begin
560+
# Note: the underscore used in the reinterpreted integer is used to indicate the decimal
561+
# place.
562+
@testset "decimal position" begin
563+
@test tryparse(FD2, "123") == reinterpret(FD2, 123_00)
564+
@test tryparse(FD2, "0.123") == reinterpret(FD2, 0_12)
565+
@test tryparse(FD2, ".123") == reinterpret(FD2, 0_12)
566+
@test tryparse(FD2, "1.23") == reinterpret(FD2, 1_23)
567+
@test tryparse(FD2, "12.3") == reinterpret(FD2, 12_30)
568+
@test tryparse(FD2, "123.") == reinterpret(FD2, 123_00)
569+
@test tryparse(FD2, "123.0") == reinterpret(FD2, 123_00)
570+
571+
@test tryparse(FD2, "-123") == reinterpret(FD2, -123_00)
572+
@test tryparse(FD2, "-0.123") == reinterpret(FD2, -0_12)
573+
@test tryparse(FD2, "-.123") == reinterpret(FD2, -0_12)
574+
@test tryparse(FD2, "-1.23") == reinterpret(FD2, -1_23)
575+
@test tryparse(FD2, "-12.3") == reinterpret(FD2, -12_30)
576+
@test tryparse(FD2, "-123.") == reinterpret(FD2, -123_00)
577+
@test tryparse(FD2, "-123.0") == reinterpret(FD2, -123_00)
578+
end
579+
580+
@testset "scientific notation" begin
581+
@test tryparse(FD4, "12e0") == reinterpret(FD4, 00012_0000)
582+
@test tryparse(FD4, "12e3") == reinterpret(FD4, 12000_0000)
583+
@test tryparse(FD4, "12e-3") == reinterpret(FD4, 00000_0120)
584+
@test tryparse(FD4, "1.2e0") == reinterpret(FD4, 00001_2000)
585+
@test tryparse(FD4, "1.2e3") == reinterpret(FD4, 01200_0000)
586+
@test tryparse(FD4, "1.2e-3") == reinterpret(FD4, 00000_0012)
587+
@test tryparse(FD4, "1.2e-4") == reinterpret(FD4, 00000_0001)
588+
589+
@test tryparse(FD4, "-12e0") == reinterpret(FD4, -00012_0000)
590+
@test tryparse(FD4, "-12e3") == reinterpret(FD4, -12000_0000)
591+
@test tryparse(FD4, "-12e-3") == reinterpret(FD4, -00000_0120)
592+
@test tryparse(FD4, "-1.2e0") == reinterpret(FD4, -00001_2000)
593+
@test tryparse(FD4, "-1.2e3") == reinterpret(FD4, -01200_0000)
594+
@test tryparse(FD4, "-1.2e-3") == reinterpret(FD4, -00000_0012)
595+
596+
@test tryparse(FD2, "999e-1") == reinterpret(FD2, 99_90)
597+
@test tryparse(FD2, "999e-2") == reinterpret(FD2, 09_99)
598+
@test tryparse(FD2, "999e-3") == reinterpret(FD2, 01_00)
599+
@test tryparse(FD2, "999e-4") == reinterpret(FD2, 00_10)
600+
@test tryparse(FD2, "999e-5") == reinterpret(FD2, 00_01)
601+
@test tryparse(FD2, "999e-6") == reinterpret(FD2, 00_00)
602+
603+
@test tryparse(FD2, "-999e-1") == reinterpret(FD2, -99_90)
604+
@test tryparse(FD2, "-999e-2") == reinterpret(FD2, -09_99)
605+
@test tryparse(FD2, "-999e-3") == reinterpret(FD2, -01_00)
606+
@test tryparse(FD2, "-999e-4") == reinterpret(FD2, -00_10)
607+
@test tryparse(FD2, "-999e-5") == reinterpret(FD2, -00_01)
608+
@test tryparse(FD2, "-999e-6") == reinterpret(FD2, -00_00)
609+
610+
@test tryparse(FD4, "9"^96 * "e-100") == reinterpret(FD4, 0_001)
611+
end
612+
613+
@testset "round to nearest" begin
614+
@test tryparse(FD2, "0.444") == reinterpret(FD2, 0_44)
615+
@test tryparse(FD2, "0.445") == reinterpret(FD2, 0_44)
616+
@test tryparse(FD2, "0.446") == reinterpret(FD2, 0_45)
617+
@test tryparse(FD2, "0.454") == reinterpret(FD2, 0_45)
618+
@test tryparse(FD2, "0.455") == reinterpret(FD2, 0_46)
619+
@test tryparse(FD2, "0.456") == reinterpret(FD2, 0_46)
620+
621+
@test tryparse(FD2, "-0.444") == reinterpret(FD2, -0_44)
622+
@test tryparse(FD2, "-0.445") == reinterpret(FD2, -0_44)
623+
@test tryparse(FD2, "-0.446") == reinterpret(FD2, -0_45)
624+
@test tryparse(FD2, "-0.454") == reinterpret(FD2, -0_45)
625+
@test tryparse(FD2, "-0.455") == reinterpret(FD2, -0_46)
626+
@test tryparse(FD2, "-0.456") == reinterpret(FD2, -0_46)
627+
628+
@test tryparse(FD2, "0.009") == reinterpret(FD2, 0_01)
629+
@test tryparse(FD2, "-0.009") == reinterpret(FD2, -0_01)
630+
631+
@test tryparse(FD4, "1.5e-4") == reinterpret(FD4, 0_0002)
632+
end
633+
634+
@testset "round to zero" begin
635+
@test tryparse(FD2, "0.444", RoundToZero) == reinterpret(FD2, 0_44)
636+
@test tryparse(FD2, "0.445", RoundToZero) == reinterpret(FD2, 0_44)
637+
@test tryparse(FD2, "0.446", RoundToZero) == reinterpret(FD2, 0_44)
638+
@test tryparse(FD2, "0.454", RoundToZero) == reinterpret(FD2, 0_45)
639+
@test tryparse(FD2, "0.455", RoundToZero) == reinterpret(FD2, 0_45)
640+
@test tryparse(FD2, "0.456", RoundToZero) == reinterpret(FD2, 0_45)
641+
642+
@test tryparse(FD2, "-0.444", RoundToZero) == reinterpret(FD2, -0_44)
643+
@test tryparse(FD2, "-0.445", RoundToZero) == reinterpret(FD2, -0_44)
644+
@test tryparse(FD2, "-0.446", RoundToZero) == reinterpret(FD2, -0_44)
645+
@test tryparse(FD2, "-0.454", RoundToZero) == reinterpret(FD2, -0_45)
646+
@test tryparse(FD2, "-0.455", RoundToZero) == reinterpret(FD2, -0_45)
647+
@test tryparse(FD2, "-0.456", RoundToZero) == reinterpret(FD2, -0_45)
648+
649+
@test tryparse(FD2, "0.009", RoundToZero) == reinterpret(FD2, 0_00)
650+
@test tryparse(FD2, "-0.009", RoundToZero) == reinterpret(FD2, 0_00)
651+
652+
@test tryparse(FD4, "1.5e-4", RoundToZero) == reinterpret(FD4, 0_0001)
653+
end
654+
655+
@testset "round throws" begin
656+
@test tryparse(FD2, "0.44", RoundThrows) == reinterpret(FD2, 0_44)
657+
@test tryparse(FD2, "0.440", RoundThrows) == reinterpret(FD2, 0_44)
658+
659+
@test isnothing(tryparse(FD2, "0.444", RoundThrows))
660+
@test isnothing(tryparse(FD2, "0.445", RoundThrows))
661+
@test isnothing(tryparse(FD2, "0.446", RoundThrows))
662+
@test isnothing(tryparse(FD2, "0.454", RoundThrows))
663+
@test isnothing(tryparse(FD2, "0.455", RoundThrows))
664+
@test isnothing(tryparse(FD2, "0.456", RoundThrows))
665+
666+
@test isnothing(tryparse(FD2, "-0.444", RoundThrows))
667+
@test isnothing(tryparse(FD2, "-0.445", RoundThrows))
668+
@test isnothing(tryparse(FD2, "-0.446", RoundThrows))
669+
@test isnothing(tryparse(FD2, "-0.454", RoundThrows))
670+
@test isnothing(tryparse(FD2, "-0.455", RoundThrows))
671+
@test isnothing(tryparse(FD2, "-0.456", RoundThrows))
672+
673+
@test isnothing(tryparse(FD2, "0.009", RoundThrows))
674+
@test isnothing(tryparse(FD2, "-0.009", RoundThrows))
675+
@test isnothing(tryparse(FD4, "1.5e-4", RoundThrows))
676+
end
677+
678+
@testset "invalid" begin
679+
@test isnothing(tryparse(FD4, "1.2e100"))
680+
@test isnothing(tryparse(FD4, "foo"))
681+
@test isnothing(tryparse(FD4, "1.2.3"))
682+
@test isnothing(tryparse(FD4, "1.2", RoundUp))
683+
end
684+
end

0 commit comments

Comments
 (0)