diff --git a/src/formatting.typ b/src/formatting.typ index db2e34b..e7da552 100644 --- a/src/formatting.typ +++ b/src/formatting.typ @@ -210,7 +210,7 @@ sign: none, int: int, frac: frac, - digits: it.digits, + digits: auto, group: false, positive-sign: false, decimal-separator: it.decimal-separator, diff --git a/src/num.typ b/src/num.typ index 963338d..b4fa3bd 100644 --- a/src/num.typ +++ b/src/num.typ @@ -117,27 +117,30 @@ } /// Round number and uncertainty - if it.round.precision != none { - (info.int, info.frac, info.pm) = round( + if info.pm != none and it.round.follow-uncertainty { + (info.int, info.frac, info.pm) = round-to-uncertainty( info.int, info.frac, - pm: info.pm, + info.pm, + sign: info.sign, ..it.round, + precision: it.round.uncertainty-precision, ) + } else { + if it.round.precision != none { + (info.int, info.frac) = round( + info.int, + info.frac, + sign: info.sign, + ..it.round, + ) + } } let digits = if it.digits == auto { info.frac.len() } else { it.digits } if digits < 0 { assert(false, message: "`digits` needs to be positive, got " + str(digits)) } - - if info.pm != none { - let pm = info.pm - if type(pm.first()) != array { pm = (pm,) } - digits = calc.max(digits, ..pm.map(array.last).map(str.len)) - } - - // info.digits = digits it.digits = digits // Format number diff --git a/src/rounding.typ b/src/rounding.typ index ec97f52..015322d 100644 --- a/src/rounding.typ +++ b/src/rounding.typ @@ -197,7 +197,7 @@ /// Rounds (and/or pads) a number given by an integer part and a fractional -/// part. Several rounding modes are supported. +/// part. #let round( /// Integer part. @@ -216,15 +216,11 @@ /// - `"places"`: The number is rounded to the number of places after the /// decimal point given by the `precision` parameter. /// - `"figures"`: The number is rounded to a number of significant figures. - /// - `"uncertainty"`: Requires giving the uncertainty. The uncertainty is - /// rounded to significant figures given by the `precision` argument and - /// then the number is rounded to the same number of places as the - /// uncertainty. mode: "places", /// The precision to round to. See parameter `mode` for the different modes. - /// If set to `none`, no rounding is performed. - /// -> int | none + /// If set to `auto` or `none`, no rounding is performed. + /// -> int | auto | none precision: 2, /// Rounding direction. @@ -242,19 +238,15 @@ /// -> bool | int pad: true, - /// Uncertainty - pm: none, + .. ) = { - if precision == none { - return (int, frac, pm) - } - if mode == "uncertainty" and pm == none { - return (int, frac, pm) + if precision in (auto, none) { + return (int, frac) } - assert-option(mode, "round-mode", ("places", "figures", "uncertainty")) + assert-option(mode, "round-mode", ("places", "figures")) // Removal hint if direction == "down" { @@ -277,55 +269,107 @@ )) - if mode == "uncertainty" { - // Round uncertainties to `precision` figures. + pad-decimal( + ..round-decimal( + int, frac, + precision: precision, + mode: mode, + dir: direction, + sign: sign, + ties: ties, + ), + pad, mode, precision + ) +} - let is-symmetric = type(pm.first()) != array - if is-symmetric { - pm = (pm,) - } - - /// Find the (fractional) place of the smallest uncertainty - let places = precision + calc.max( - ..pm.map(((i, f)) => count-leading-zeros(i + f) - i.len()) - ) - pm = pm - .map(((i, f)) => round-decimal( - i, f, - dir: direction, - precision: places, - mode: "places" - )) - .map(((i, f)) => pad-decimal( - i, f, - pad, - "places", - places - )) - - if is-symmetric { - pm = pm.first() - } - - // Then, round number to the same number of places - mode = "places" - precision = places +/// Rounds a number to the same decimal place as its uncertainty. +#let round-to-uncertainty( + + /// Integer part. + /// -> str + int, + + /// Fractional part. + /// -> str + frac, + + /// Uncertainty, given as a tuple of integer and fractional part. Can also be a tuple of two tuples, in the case of asymmetric uncertainty. + /// -> (str, str) | ((str, str), (str, str)) + uncertainty, + + /// The sign of the number. + /// -> "+" | "-" + sign: "+", + + /// The precision to round the uncertainty to. If `auto`, the uncertainty left as is. + /// -> auto | int + precision: auto, + + /// Rounding direction. + /// -> str + direction: "nearest", + + /// How to round ties. + /// -> "away-from-zero" | "towards-zero" | "to-odd" | "to-even" | "towards-infinity" | "towards-negative-infinity" + ties: "away-from-zero", + + /// Determines whether the number should be padded with zeros if the number + /// has less digits than the rounding precision. If an integer is given, + /// determines the minimum number of decimal digits (`mode: "places"`) or + /// significant figures (`mode: "figures"`) to display. + /// -> bool | int + pad: true, + .. + +) = { + let is-symmetric = type(uncertainty.first()) != array + if is-symmetric { + uncertainty = (uncertainty,) + } + + /// Find the (fractional) place of the smallest uncertainty + let places = if precision == auto { + calc.max( + ..uncertainty.map(((i, f)) => f.len()) + ) + } else { + precision + calc.max( + ..uncertainty.map(((i, f)) => count-leading-zeros(i + f) - i.len()) + ) } + uncertainty = uncertainty + .map(((i, f)) => round-decimal( + i, f, + dir: direction, + precision: places, + mode: "places" + )) + .map(((i, f)) => pad-decimal( + i, f, + pad, + "places", + places + )) + + if is-symmetric { + uncertainty = uncertainty.first() + } + + // Then, round number to the same number of places as the uncertainty. ( ..pad-decimal( ..round-decimal( int, frac, - precision: precision, - mode: mode, + precision: places, + mode: "places", dir: direction, sign: sign, ties: ties, ), - pad, mode, precision + pad, "places", places ), - pm + uncertainty ) } - diff --git a/src/state.typ b/src/state.typ index d3edcea..f9dd00f 100644 --- a/src/state.typ +++ b/src/state.typ @@ -23,7 +23,9 @@ ), round: ( mode: "places", - precision: none, + precision: auto, + follow-uncertainty: true, + uncertainty-precision: auto, pad: true, direction: "nearest", ties: "away-from-zero", diff --git a/tests/num/rounding/ref.typ b/tests/num/rounding/ref.typ index e60e111..4d52bec 100644 --- a/tests/num/rounding/ref.typ +++ b/tests/num/rounding/ref.typ @@ -18,9 +18,20 @@ $10#sym.space.thin 000$ \ #pagebreak() -$1.23plus.minus 0.03$ \ -$1.2342plus.minus 0.0003$ \ -$9plus.minus 2$ +$12.4plus.minus 0.2$ \ +$1.234plus.minus 0.009$ \ +$1.234#sym.space.thin 22plus.minus 0.00310$ \ +$9plus.minus 2$ \ +$8.00plus.minus 0.02$ \ +$-9plus.minus 2$ \ +$8.8plus.minus 0.02$ \ +$0.30plus.minus 0.12$ \ +$0.3plus.minus 0.12$ \ + +$12.38plus.minus 0.2$ \ +$12plus.minus 0.2$ \ +$1.2plus.minus 0.00034$ \ +$8.8plus.minus 2$ \ #pagebreak() diff --git a/tests/num/rounding/test.typ b/tests/num/rounding/test.typ index aa0b86b..27e85fa 100644 --- a/tests/num/rounding/test.typ +++ b/tests/num/rounding/test.typ @@ -120,142 +120,138 @@ #let round-places = round.with(mode: "places") #let round-figures = round.with(mode: "figures") -#assert.eq(round("23", "5", precision: none), ("23", "5", none)) - -#assert.eq(round-places("1", "234", precision: 3), ("1", "234", none)) -#assert.eq(round-places("1", "234", precision: 2), ("1", "23", none)) -#assert.eq(round-places("1", "234", precision: 1), ("1", "2", none)) -#assert.eq(round-places("1", "234", precision: 0), ("1", "", none)) -#assert.eq(round-places("23", "534", precision: -1), ("20", "", none)) -#assert.eq(round-places("12345", "534", precision: -3), ("12000", "", none)) -#assert.eq(round-places("70", "", precision: -2), ("100", "", none)) -#assert.eq(round-places("70", "", precision: -3), ("", "", none)) -#assert.eq(round-places("70", "", precision: -4), ("", "", none)) -#assert.eq(round-places("", "0022", precision: 3), ("", "002", none)) - -#assert.eq(round-places("1", "1", precision: 0), ("1", "", none)) -#assert.eq(round-places("1", "1", precision: 3), ("1", "100", none)) -#assert.eq(round-places("1", "1", precision: 5), ("1", "10000", none)) -#assert.eq(round-places("1", "1", precision: 5), ("1", "10000", none)) - -#assert.eq(round-places("1", "1", precision: 5, pad: false), ("1", "1", none)) -#assert.eq(round-places("1", "1", precision: 5, pad: true), ("1", "10000", none)) -#assert.eq(round-places("1", "1", precision: 5, pad: -1), ("1", "1", none)) -#assert.eq(round-places("1", "1", precision: 5, pad: 0), ("1", "1", none)) -#assert.eq(round-places("1", "1", precision: 5, pad: 3), ("1", "100", none)) -#assert.eq(round-places("1", "1", precision: 5, pad: 6), ("1", "10000", none)) - -#assert.eq(round-figures("1", "234", precision: 4), ("1", "234", none)) -#assert.eq(round-figures("1", "234", precision: 3), ("1", "23", none)) -#assert.eq(round-figures("1", "234", precision: 2), ("1", "2", none)) -#assert.eq(round-figures("1", "234", precision: 1), ("1", "", none)) -#assert.eq(round-figures("1", "234", precision: 0), ("", "", none)) -#assert.eq(round-figures("1", "234", precision: -1), ("", "", none)) -#assert.eq(round-figures("8", "234", precision: 0), ("10", "", none)) -#assert.eq(round-figures("8", "234", precision: -1), ("", "", none)) -#assert.eq(round-figures("8", "234", precision: -2), ("", "", none)) -#assert.eq(round-figures("11", "", precision: -3), ("", "", none)) - -#assert.eq(round-figures("1", "2", precision: 4), ("1", "200", none)) - -#assert.eq(round-figures("1", "2", precision: 4, pad: false), ("1", "2", none)) -#assert.eq(round-figures("1", "2", precision: 4, pad: true), ("1", "200", none)) -#assert.eq(round-figures("1", "2", precision: 4, pad: -1), ("1", "2", none)) -#assert.eq(round-figures("1", "2", precision: 4, pad: 0), ("1", "2", none)) -#assert.eq(round-figures("1", "2", precision: 4, pad: 3), ("1", "20", none)) -#assert.eq(round-figures("1", "2", precision: 4, pad: 6), ("1", "200", none)) - -#assert.eq(round-figures("0", "00126", precision: 2), ("", "0013", none)) -#assert.eq(round-figures("0", "000126", precision: 3), ("", "000126", none)) - -#assert.eq(round-figures("0", "0016", precision: 4, pad: false), ("", "0016", none)) -#assert.eq(round-figures("0", "0016", precision: 4, pad: true), ("", "001600", none)) -#assert.eq(round-figures("0", "0016", precision: 4, pad: -1), ("", "0016", none)) -#assert.eq(round-figures("0", "0016", precision: 4, pad: 0), ("", "0016", none)) -#assert.eq(round-figures("0", "0016", precision: 4, pad: 3), ("", "00160", none)) -#assert.eq(round-figures("0", "0016", precision: 4, pad: 6), ("", "001600", none)) - - -#assert.eq(round-places("99", "92", precision: 2), ("99", "92", none)) -#assert.eq(round-places("99", "92", precision: 0), ("100", "", none)) -#assert.eq(round-places("99", "99", precision: 1), ("100", "0", none)) -#assert.eq(round-places("99", "99", precision: -1), ("100", "", none)) -#assert.eq(round-places("1", "299995", precision: 5), ("1", "30000", none)) -#assert.eq(round-places("1", "299994", precision: 5), ("1", "29999", none)) -#assert.eq(round-places("523", "", precision: -2), ("500", "", none)) +#assert.eq(round("23", "5", precision: none), ("23", "5")) + +#assert.eq(round-places("1", "234", precision: 3), ("1", "234")) +#assert.eq(round-places("1", "234", precision: 2), ("1", "23")) +#assert.eq(round-places("1", "234", precision: 1), ("1", "2")) +#assert.eq(round-places("1", "234", precision: 0), ("1", "")) +#assert.eq(round-places("23", "534", precision: -1), ("20", "")) +#assert.eq(round-places("12345", "534", precision: -3), ("12000", "")) +#assert.eq(round-places("70", "", precision: -2), ("100", "")) +#assert.eq(round-places("70", "", precision: -3), ("", "")) +#assert.eq(round-places("70", "", precision: -4), ("", "")) +#assert.eq(round-places("", "0022", precision: 3), ("", "002")) + +#assert.eq(round-places("1", "1", precision: 0), ("1", "")) +#assert.eq(round-places("1", "1", precision: 3), ("1", "100")) +#assert.eq(round-places("1", "1", precision: 5), ("1", "10000")) +#assert.eq(round-places("1", "1", precision: 5), ("1", "10000")) + +#assert.eq(round-places("1", "1", precision: 5, pad: false), ("1", "1")) +#assert.eq(round-places("1", "1", precision: 5, pad: true), ("1", "10000")) +#assert.eq(round-places("1", "1", precision: 5, pad: -1), ("1", "1")) +#assert.eq(round-places("1", "1", precision: 5, pad: 0), ("1", "1")) +#assert.eq(round-places("1", "1", precision: 5, pad: 3), ("1", "100")) +#assert.eq(round-places("1", "1", precision: 5, pad: 6), ("1", "10000")) + +#assert.eq(round-figures("1", "234", precision: 4), ("1", "234")) +#assert.eq(round-figures("1", "234", precision: 3), ("1", "23")) +#assert.eq(round-figures("1", "234", precision: 2), ("1", "2")) +#assert.eq(round-figures("1", "234", precision: 1), ("1", "")) +#assert.eq(round-figures("1", "234", precision: 0), ("", "")) +#assert.eq(round-figures("1", "234", precision: -1), ("", "")) +#assert.eq(round-figures("8", "234", precision: 0), ("10", "")) +#assert.eq(round-figures("8", "234", precision: -1), ("", "")) +#assert.eq(round-figures("8", "234", precision: -2), ("", "")) +#assert.eq(round-figures("11", "", precision: -3), ("", "")) + +#assert.eq(round-figures("1", "2", precision: 4), ("1", "200")) + +#assert.eq(round-figures("1", "2", precision: 4, pad: false), ("1", "2")) +#assert.eq(round-figures("1", "2", precision: 4, pad: true), ("1", "200")) +#assert.eq(round-figures("1", "2", precision: 4, pad: -1), ("1", "2")) +#assert.eq(round-figures("1", "2", precision: 4, pad: 0), ("1", "2")) +#assert.eq(round-figures("1", "2", precision: 4, pad: 3), ("1", "20")) +#assert.eq(round-figures("1", "2", precision: 4, pad: 6), ("1", "200")) + +#assert.eq(round-figures("0", "00126", precision: 2), ("", "0013")) +#assert.eq(round-figures("0", "000126", precision: 3), ("", "000126")) + +#assert.eq(round-figures("0", "0016", precision: 4, pad: false), ("", "0016")) +#assert.eq(round-figures("0", "0016", precision: 4, pad: true), ("", "001600")) +#assert.eq(round-figures("0", "0016", precision: 4, pad: -1), ("", "0016")) +#assert.eq(round-figures("0", "0016", precision: 4, pad: 0), ("", "0016")) +#assert.eq(round-figures("0", "0016", precision: 4, pad: 3), ("", "00160")) +#assert.eq(round-figures("0", "0016", precision: 4, pad: 6), ("", "001600")) + + +#assert.eq(round-places("99", "92", precision: 2), ("99", "92")) +#assert.eq(round-places("99", "92", precision: 0), ("100", "")) +#assert.eq(round-places("99", "99", precision: 1), ("100", "0")) +#assert.eq(round-places("99", "99", precision: -1), ("100", "")) +#assert.eq(round-places("1", "299995", precision: 5), ("1", "30000")) +#assert.eq(round-places("1", "299994", precision: 5), ("1", "29999")) +#assert.eq(round-places("523", "", precision: -2), ("500", "")) #assert.eq( - round("42", "3734", pm: ("", "0025"), precision: 2, mode: "uncertainty"), + round-to-uncertainty("42", "3734", ("", "0025"), precision: 2), ("42", "3734", ("", "0025")), ) #assert.eq( - round("42", "3734", pm: ("", "0025"), precision: 1, mode: "uncertainty"), + round-to-uncertainty("42", "3734", ("", "0025"), precision: 1), ("42", "373", ("", "003")), ) #assert.eq( - round("42", "3734", pm: ("2", "2"), precision: 1, mode: "uncertainty"), + round-to-uncertainty("42", "3734", ("2", "2"), precision: 1), ("42", "", ("2", "")), ) #assert.eq( - round("42", "3734", pm: ("2", "2"), precision: 2, mode: "uncertainty"), + round-to-uncertainty("42", "3734", ("2", "2"), precision: 2), ("42", "4", ("2", "2")), ) #assert.eq( - round("42", "3734", pm: ("2", "2"), precision: 3, mode: "uncertainty"), + round-to-uncertainty("42", "3734", ("2", "2"), precision: 3), ("42", "37", ("2", "20")), ) #assert.eq( - round("4211", "3734", pm: ("230", "2"), precision: 1, mode: "uncertainty"), + round-to-uncertainty("4211", "3734", ("230", "2"), precision: 1), ("4200", "", ("200", "")), ) #assert.eq( - round("1", "23", pm: ("0", "2"), precision: 1, mode: "uncertainty"), + round-to-uncertainty("1", "23", ("0", "2"), precision: 1), ("1", "2", ("", "2")), ) #assert.eq( - round("123", "9", pm: ("20", ""), precision: 1, mode: "uncertainty"), + round-to-uncertainty("123", "9", ("20", ""), precision: 1), ("120", "", ("20", "")), ) #assert.eq( - round( + round-to-uncertainty( "1", "23", - pm: (("0", "2"), ("0", "3")), + (("0", "2"), ("0", "3")), precision: 1, - mode: "uncertainty", ), ("1", "2", (("", "2"), ("", "3"))), ) #assert.eq( - round( + round-to-uncertainty( "123", "9", - pm: (("020", ""), ("30", "")), + (("020", ""), ("30", "")), precision: 1, - mode: "uncertainty", ), ("120", "", (("20", ""), ("30", ""))), ) #assert.eq( - round( + round-to-uncertainty( "1", "23", - pm: (("0", "24"), ("0", "3")), + (("0", "24"), ("0", "3")), precision: 1, - mode: "uncertainty", ), ("1", "2", (("", "2"), ("", "3"))), ) #assert.eq( - round( + round-to-uncertainty( "1", "23", - pm: (("0", "04"), ("0", "3")), + (("0", "04"), ("0", "3")), precision: 1, - mode: "uncertainty", ), ("1", "23", (("", "04"), ("", "30"))), ) @@ -266,7 +262,7 @@ precision: 2, mode: "places", ), - ("", "00", none), + ("", "00"), ) #assert.eq( @@ -276,7 +272,7 @@ precision: 2, mode: "figures", ), - ("", "", none), + ("", ""), ) @@ -316,8 +312,26 @@ // Uncertainty -#set-round(mode: "uncertainty", precision: 1) -#num("1.234(34)") \ + +#num("12.378+-0.2") \ +#num("1.234(9)") \ +#num("1.23422(310)") \ +// num is rounded +#num("8.8+-2") \ +// num is padded +#num("8+-0.02") \ +// signs are respected +#num("-8.8+-2", round: (direction: "towards-negative-infinity")) \ +// num is not padded +#num("8.8+-0.02", round: (pad: false)) \ +// num is rounded and padded +#num(round: (pad: true))[0.299+-.12] \ +// num is rounded and not padded +#num(round: (pad: false))[0.299+-.12] \ + +#set-round(follow-uncertainty: false) +#num("12.378+-0.2", round: (precision: 2, mode: "places")) \ +#num("12.378+-0.2", round: (precision: 2, mode: "figures")) \ #num("1.23422(34)") \ #num("8.8+-2") \ diff --git a/tests/num/uncertainty/ref.typ b/tests/num/uncertainty/ref.typ index 32d5591..c4e0ad9 100644 --- a/tests/num/uncertainty/ref.typ +++ b/tests/num/uncertainty/ref.typ @@ -18,9 +18,9 @@ $1.7±2.8$, $1.7(28)$, $1.7(2.8)$, $2.2^(+0.2)_(-0.5)$, $2.2^(+2)_(-5)$, $2.2^(+2)_(-5)$, - $2.2^(+2.0)_(-5.0)$, $2.2^(+20)_(-50)$, $2.2^(+2.0)_(-5.0)$, + $2^(+2)_(-5)$, $2^(+2)_(-5)$, $2^(+2)_(-5)$, $2.2^(+2.0)_(-0.5)$, $2.2^(+20)_(-5)$, $2.2^(+2.0)_(-0.5)$, - $9.99^(+2.00)_(-0.50)$, $9.99^(+200)_(-50)$, $9.99^(+2.00)_(-0.50)$, + $10.0^(+2.0)_(-0.5)$, $10.0^(+20)_(-5)$, $10.0^(+2.0)_(-0.5)$, ) @@ -47,15 +47,15 @@ non-math-attach([2.2], t: [+0.2], b: [-0.5]), non-math-attach([2.2], t: [+2], b: [-5]), non-math-attach([2.2], t: [+2], b: [-5]), - non-math-attach([2.2], t: [+2.0], b: [-5.0]), - non-math-attach([2.2], t: [+20], b: [-50]), - non-math-attach([2.2], t: [+2.0], b: [-5.0]), + non-math-attach([2], t: [+2], b: [-5]), + non-math-attach([2], t: [+2], b: [-5]), + non-math-attach([2], t: [+2], b: [-5]), non-math-attach([2.2], t: [+2.0], b: [-0.5]), non-math-attach([2.2], t: [+20], b: [-5]), non-math-attach([2.2], t: [+2.0], b: [-0.5]), - non-math-attach([9.99], t: [+2.00], b: [-0.50]), - non-math-attach([9.99], t: [+200], b: [-50]), - non-math-attach([9.99], t: [+2.00], b: [-0.50]), + non-math-attach([10.0], t: [+2.0], b: [-0.5]), + non-math-attach([10.0], t: [+20], b: [-5]), + non-math-attach([10.0], t: [+2.0], b: [-0.5]), ) #pagebreak() diff --git a/tests/table/format/ref/2.png b/tests/table/format/ref/2.png index d07c40b..3194e94 100644 Binary files a/tests/table/format/ref/2.png and b/tests/table/format/ref/2.png differ diff --git a/tests/table/format/test.typ b/tests/table/format/test.typ index 0baae5b..7d1c700 100644 --- a/tests/table/format/test.typ +++ b/tests/table/format/test.typ @@ -84,8 +84,8 @@ columns: 2, align: center, format: (none, auto), - [4], [1.2+-3], - [4], [1.2+-5], + [4], [1.2+-3.0], + [4], [1.2+-5.0], ) diff --git a/tests/table/nonum/test.typ b/tests/table/nonum/test.typ index 5762696..e2ef857 100644 --- a/tests/table/nonum/test.typ +++ b/tests/table/nonum/test.typ @@ -4,7 +4,7 @@ #ztable( format: (auto,), [#nonum[€]123.0#nonum(footnote[A special number])], - [222.23+-2], + [222.23+-2.00], [12], nonum[non-numerical], )