Re: Do you like bounded integrals?
On Tuesday, 30 August 2016 at 14:58:16 UTC, Chris Wright wrote: We can only track types, not values, and that strongly hampers our ability to reduce ranges. Playing with some examples, I think the real issue is that precise bounds tracking requires analyzing the formula as a whole, rather than just each operation individually: auto blend( Bound!(int, 0, 100) x, Bound!(int, 0, 100) y, Bound!(int, 0, 100) z) { enum Bound!(int, 100, 100) h = 100; return ((x * z) + (y * (h - z))) / h; } With propagation computed at the individual operation level, the above returns a `Bound!(int, 0, 200)`. But, using the expression as a whole it can easily be proven that the true maximum is `100`. (With just-plain-wrong "union of the input ranges" propagation, the above will generate many spurious exceptions.) Unfortunately, proving the true bounds statically in the general case would require something like a compile-time computer algebra system connected combined with AST macros. That's obviously overkill for what we're doing here.
Re: Do you like bounded integrals?
On Tuesday, 30 August 2016 at 14:58:16 UTC, Chris Wright wrote: On Tue, 30 Aug 2016 13:24:04 +, tsbockman wrote: Ranges don't always grow. Some operations will also cause them to shrink, if they're really being tracked correctly: We can only track types, not values, and that strongly hampers our ability to reduce ranges. We can still do it sometimes, but no operation always reduces ranges (and every operation sometimes extends ranges). Then don't try to propagate the ranges at all. Use regular `CheckedInt` as the return type for all of `BoundInt`'s non-assignment binary operations. Make the user explicitly label which variables should be checked against what ranges, instead of automatically inserting the usually-wrong guess that every intermediate value belongs in the same narrow range as the inputs. Otherwise, `BoundInt` will generate tons of spurious exceptions, particularly for multiplication. Expressions that are complicated enough that custom range-based sanity checks need to be done inside of it, on rvalues, should probably be broken up into smaller pieces, anyway.
Re: Do you like bounded integrals?
On Tue, 30 Aug 2016 13:24:04 +, tsbockman wrote: > On Tuesday, 30 August 2016 at 03:37:06 UTC, Chris Wright wrote: >> On Mon, 29 Aug 2016 18:20:05 +, tsbockman wrote: >>> They should. Generally speaking, if that doesn't produce reasonable >>> bounds (leaving aside rounding errors) at the end of the computation, >>> it means that the logic of the computation itself is wrong. >> >> The ranges expand very fast. Addition and subtraction double the range, >> multiplication squares it, exponentiation is n^^n. >> Larger bounds are usually not useful bounds. > > Ranges don't always grow. Some operations will also cause them to > shrink, if they're really being tracked correctly: We can only track types, not values, and that strongly hampers our ability to reduce ranges. We can still do it sometimes, but no operation always reduces ranges (and every operation sometimes extends ranges). > bitwise AND, It tends to extend the lower bound. For instance: Bounded!(int, -100, 5) a = -99, b = -32; auto c = a & b; Here, c has type Bounded!(int, -128, 5) and value -128. Similarly, Bounded!(int, 7, 55) & Bounded!(int, 12, 64) yields Bounded! (int, 0, 55). And for an odd one, Bounded!(int, 50, 55) & Bounded!(int, 60, 62) yields Bounded!(int, 48, 54). So bitwise AND *sometimes* reduces the range (in sometimes hard to predict ways), but it also sometimes extends it. > integer division, You can only reduce a range with integer division if the divisor's static range does not include the identity. For example (assuming the range is open on both ends): Bounded!(int, 0, 100) a = 1, b = 100; auto c = b / a; c has type Bounded!(0, 100) and value 100. On the other hand, it can sometimes reduce the range: Bounded!(int, 4, 8) a = 4, b = 8; auto c = b / a; Here, c has type Bounded!(int, 1, 2) and value 2. And sometimes it extends the range: Bounded!(int, -1, 1) a = -1; Bounded!(int, 0, 128) b = 128; auto c = a / b; Here, c has type Bounded!(int, -128, 128) and value -128. > and modulus all tend to do so. Modulus can result in a range larger than its operands': Bounded!(int, 1, 2) a = 1, b = 2; auto c = b % a; Here, c has type Bounded!(int, 0, 2) and value 0.
Re: Do you like bounded integrals?
On 08/29/2016 11:37 PM, Chris Wright wrote: On Mon, 29 Aug 2016 18:20:05 +, tsbockman wrote: On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: When composing, do the limits compose meaningfully? They should. Generally speaking, if that doesn't produce reasonable bounds (leaving aside rounding errors) at the end of the computation, it means that the logic of the computation itself is wrong. The ranges expand very fast. Addition and subtraction double the range, multiplication squares it, exponentiation is n^^n. Larger bounds are usually not useful bounds. Yah, was thinking of the same. So before long you run into the bounds, at which points it's all up to the checking policy. -- Andrei
Re: Do you like bounded integrals?
On Tuesday, 30 August 2016 at 03:37:06 UTC, Chris Wright wrote: On Mon, 29 Aug 2016 18:20:05 +, tsbockman wrote: They should. Generally speaking, if that doesn't produce reasonable bounds (leaving aside rounding errors) at the end of the computation, it means that the logic of the computation itself is wrong. The ranges expand very fast. Addition and subtraction double the range, multiplication squares it, exponentiation is n^^n. Larger bounds are usually not useful bounds. Ranges don't always grow. Some operations will also cause them to shrink, if they're really being tracked correctly: bitwise AND, integer division, and modulus all tend to do so. If the ranges are intrinsically significant (it's logically impossible for a creature to have -7 eyes) and the calculation is meaningful and correct, things should balance out at the end to produce a useful range for the final result. On the other hand, arbitrary sanity check ranges (it's merely extremely unlikely for a creature to have 2147483647 eyes) shouldn't be passed on to intermediate values at all; they must be assigned manually based on human intuition, and this is only really worth doing for inputs and outputs. In such cases, the best return value for binary operations would be normal (conceptually) unbounded CheckedInt. Give me mathematically correct bounds, or no bounds at all. Anything else will cause problems.
Re: Do you like bounded integrals?
On Mon, 29 Aug 2016 18:20:05 +, tsbockman wrote: > On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: >> When composing, do the limits compose meaningfully? > > They should. Generally speaking, if that doesn't produce reasonable > bounds (leaving aside rounding errors) at the end of the computation, it > means that the logic of the computation itself is wrong. The ranges expand very fast. Addition and subtraction double the range, multiplication squares it, exponentiation is n^^n. Larger bounds are usually not useful bounds.
Re: Do you like bounded integrals?
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: When composing, do the limits compose meaningfully? They should. Generally speaking, if that doesn't produce reasonable bounds (leaving aside rounding errors) at the end of the computation, it means that the logic of the computation itself is wrong. Ordinary abstract numbers don't really have "minimum" and "maximum" values; any variable that requires them has some sort of implied unit which determines its bounds. Therefore, the bounds should follow the rules of dimensional analysis (https://en.wikipedia.org/wiki/Dimensional_analysis).
Re: Do you like bounded integrals?
On 08/29/2016 11:33 AM, Marco Leise wrote: The following could make it more accessible, much like the two Exception constructors we have: alias CheckedInt(T, T min, T max, Hook = Abort) = CheckedIntImpl!(T, Hook, min, max); alias CheckedInt(T, Hook = Abort) = CheckedIntImpl!(T, Hook, T.min, T.max); struct CheckedIntImpl(T, Hook = Abort, T min, T max); Since then I've decided to confine static bounds computation to a separate type. Checked does naturally allow for hooks that impose dynamic bounds enforcement. One interesting tidbit - I was looking at a bounded integer library for C++ and it looks like it didn't implement the most interesting parts - computing bounds for logical operations: https://bitbucket.org/davidstone/bounded_integer/src/cd25b513c508498e882acb6543db5e08999243fb/include/bounded/detail/arithmetic/bitwise_and.hpp?at=default=file-view-default Andrei
Re: Do you like bounded integrals?
Am Tue, 23 Aug 2016 16:40:06 -0400 schrieb Andrei Alexandrescu: > Currently checkedint (https://github.com/dlang/phobos/pull/4613) stands > at 2432 lines and implements a variety of checking behaviors. At this > point I just figured I can very easily add custom bounds, e.g. an int > limited to 0 through 100 etc. It would take just a few lines because a > lot of support is there (bounds hooks, custom min/max) anyway. In Pascal/Delphi I used them often for day of the week, percentages and anything else that is naturally bounded. I guess as so often with "library features" it would depend on some pull and push factors. "What module was it? What do I have to set for 'Hook' again, so I can set 'min' and 'max'?" vs. "It's the proper type to use. It save me time here by catching bugs." > However, I fear it might complicate definition and just be a bit much. > Here's the design I'm thinking of. Current: > > struct Checkedint(T, Hook = Abort); > > Under consideration: > > struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max); > > It's easy to take the limits into account, but then there are a few > messes to mind: > > * When assigning a Checked to another, should the limits be matched > statically or checked dynamically? As for statically checking, "a = b" should work if b's range is included in a's range, just like we can assign a ubyte to a uint. Types with only a partial overlap should probably be dealt with at runtime the same way assignments of normal ints would be checked. As an upgrade to that one could also statically check if the ranges have no overlap at all. > * When composing, do the limits compose meaningfully? You mean like when one multiplies two Checkedints ? > * How to negotiate when both the user of Checked and the Hook need to > customize the limits? (e.g. if you look at WithNaN it needs to reserve a > special value, thus limiting the representable range). > > I think all of these questions have answers, but I wanted to gauge the > interest in bounded checked integrals. Would the need for them justify > additional complications in the definition? > > > Andrei I have no answer to that either. The following could make it more accessible, much like the two Exception constructors we have: alias CheckedInt(T, T min, T max, Hook = Abort) = CheckedIntImpl!(T, Hook, min, max); alias CheckedInt(T, Hook = Abort) = CheckedIntImpl!(T, Hook, T.min, T.max); struct CheckedIntImpl(T, Hook = Abort, T min, T max); -- Marco
Re: Do you like bounded integrals?
Update: I've taken a shot at adding bounds, see https://github.com/dlang/phobos/pull/4613/commits/bbdcea723e3dd98a979ae3f06a6786645647a778. Here are a few notes: * The Hook API works nicely with built-in or custom hooks, so no change there was necessary. * The constructor got quite a bit more complicated because it needs to evaluate statically the bounds, which in turn evaluate the constructor statically. My solution was to define minRep and maxRep as the built-in representations of the min and max, and use those inside the constructor. * There's trouble about choosing between a conservative and a statically precise approach to bounds computation. The simplest example is computing -x where x has type Checked!int. The precise result type is Checked!(int, int.min + 1, int.max). (If x == int.min, attempting to evaluate -x aborts the program.) The precise type is technically correct, but I assume in practice it'll just create a lot of annoyance due to the fact that x and -x have "slightly" different types. * It gets worse with binary operators. Implementing the precise limits is a mini-project of its own (akin to the VRP algorithms). Going conservative (as the current work does) is annoying in a different way - any binary operator loses the custom limits information that the user in all likelihood had carefully planted. My decision following this experiment is to drop support for custom limits in Checked. The complexity/power balance is untenable. However, the hooks should be reusable with a different artifact called e.g.: struct Bounded(T, T min, T max, Hook); That should do precise static bounds computation in all VRP glory and interact well with Checked so the two can be composed. Andrei
Re: Do you like bounded integrals?
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: Currently checkedint (https://github.com/dlang/phobos/pull/4613) stands at 2432 lines and implements a variety of checking behaviors. At this point I just figured I can very easily add custom bounds, e.g. an int limited to 0 through 100 etc. It would take just a few lines because a lot of support is there (bounds hooks, custom min/max) anyway. However, I fear it might complicate definition and just be a bit much. Here's the design I'm thinking of. Current: struct Checkedint(T, Hook = Abort); Under consideration: struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max); It's easy to take the limits into account, but then there are a few messes to mind: * When assigning a Checked to another, should the limits be matched statically or checked dynamically? * When composing, do the limits compose meaningfully? * How to negotiate when both the user of Checked and the Hook need to customize the limits? (e.g. if you look at WithNaN it needs to reserve a special value, thus limiting the representable range). I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition? Andrei It's just an abortion. There is a C++ bounded::integer library by David Stone that looks much better than what you have here. For a language that's claiming to be a better C++ and with better CT features, I would expect something more elegant and useful. Besides, this only works with integers. You've been bashing Go for lack of generics, so how about we see something that works with other types too, no? Perl 6: subset MyInt of Int where (4 < * < 123); or for string, subset MyStr of Str where (0 < *.chars < 100); Just beautiful, and what you would expect from a language that's trying to be better than the past.
Re: Do you like bounded integrals?
what about two types. alias BI = Bound!int; alias CBI = CheckedInt!BI; if Bound behaves as an integer people can choose.
Re: Do you like bounded integrals?
On 08/24/2016 05:05 AM, Lodovico Giaretta wrote: On Wednesday, 24 August 2016 at 00:40:15 UTC, Andrei Alexandrescu wrote: That wouldn't work for e.g. NaN. A NaN wants to "steal" a value but only if that's not available. It's complicated. Ok, I get your point. WithNaN should not waste the "official range" when there is a huge wasted representable range that can be exploited to choose the special NaN value. Yes, thanks for divining the meaning from the poor explanation. While this would be a very good thing, it poses a problem. If a hook is allowed to special-case values outside the official range, then it may happen that composing two hooks, they both special-case the same value, leading to wrong results. For example, in some statistical oriented environments, the special value NA (not available), very similar to NaN, is used to represent missing input data; a WithNA hook may decide to use the same policy used by WithNaN to reserve its NA value, causing corruption, as the same value has two meanings. Yes, that is indeed a potential problem. So, the first solution is that hooks should always reserve special values from the edges of the official range, and expose to the higher layer a correctly reduced official range. The second solution is having two ranges: the "official range" and the "usable range", where the usable range is the full representable range minus the special values already used. Hooks must take special values from the usable range and reduce it accordingly, while leaving the official range intact. The third (more complex) solution, which leaves the official range intact, is that hooks should take special values from wherever outside the official range and in some way communicate to the higher level which values are already taken. This is not impossible nor conceptually difficult, but it may not be worth. The fourth solution is to document hooks appropriately and acknowledge the fact (which is already true) that not all hooks can work together. Andrei
Re: Do you like bounded integrals?
On Wednesday, 24 August 2016 at 00:40:15 UTC, Andrei Alexandrescu wrote: That wouldn't work for e.g. NaN. A NaN wants to "steal" a value but only if that's not available. It's complicated. Ok, I get your point. WithNaN should not waste the "official range" when there is a huge wasted representable range that can be exploited to choose the special NaN value. While this would be a very good thing, it poses a problem. If a hook is allowed to special-case values outside the official range, then it may happen that composing two hooks, they both special-case the same value, leading to wrong results. For example, in some statistical oriented environments, the special value NA (not available), very similar to NaN, is used to represent missing input data; a WithNA hook may decide to use the same policy used by WithNaN to reserve its NA value, causing corruption, as the same value has two meanings. So, the first solution is that hooks should always reserve special values from the edges of the official range, and expose to the higher layer a correctly reduced official range. The second solution is having two ranges: the "official range" and the "usable range", where the usable range is the full representable range minus the special values already used. Hooks must take special values from the usable range and reduce it accordingly, while leaving the official range intact. The third (more complex) solution, which leaves the official range intact, is that hooks should take special values from wherever outside the official range and in some way communicate to the higher level which values are already taken. This is not impossible nor conceptually difficult, but it may not be worth.
Re: Do you like bounded integrals?
On Wednesday, 24 August 2016 at 08:39:28 UTC, Nordlöw wrote: Such an implementation is `IndexedBy` at https://github.com/nordlow/phobos-next/blob/master/src/typecons_ex.d#L167 Correction: Index type here is actually a Ada-style modulo type which does wrap-around instead of truncation.
Re: Do you like bounded integrals?
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: Currently checkedint (https://github.com/dlang/phobos/pull/4613) stands at 2432 lines and implements a variety of checking behaviors. At this point I just figured I can very easily add custom bounds, e.g. an int limited to 0 through 100 etc. It would take just a few lines because a lot of support is there (bounds hooks, custom min/max) anyway. struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max); For comparion take a look at my solution at: https://github.com/nordlow/phobos-next/blob/master/src/bound.d It may answer some of your questions. The CT-param `exceptional` should be related to your `Hook`. The solution is currently bloated as it is interleaved with an Optional implementation and packing logic. The CT-params `optional`, `exceptional`, `packed` and `signed` should probably be merged and stored in a Flags-type. * When assigning a Checked to another, should the limits be matched statically or checked dynamically? I'm not sure. I believe the answer lies in the semantic (real-life) interpretations of the boundeed types. If the ranges are related to physical boundaries it should probably be checked at compile-time like we do with units of measurement libraries. * When composing, do the limits compose meaningfully? What do you mean with composing? If you're talking about value-range algebra then take a look at the definitions of `opBinary`, `min`, `max` and `abs` in my `bound.d`. I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition? One other additional use is for Ada-style fixed-length-array index types. Such a type can be both bounds-checked and optionally tied to a specific subtype of a fixed-length array. Such an implementation is `IndexedBy` at https://github.com/nordlow/phobos-next/blob/master/src/typecons_ex.d#L167 which is currently in use in my trie-container https://github.com/nordlow/phobos-next/blob/master/src/trie.d
Re: Do you like bounded integrals?
On 08/23/2016 05:08 PM, Lodovico Giaretta wrote: On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: * When assigning a Checked to another, should the limits be matched statically or checked dynamically? The ideal would be implicit cast allowed when widening, explicit required when narrowing. As I think we will have to settle for always explicit, I would say dynamic check. This of course opens the way to many customizations: an user may want to customize what happens when the range check fails. Another user may even want a switch to statically disallow narrowing conversions. * When composing, do the limits compose meaningfully? Every layer should build on the limits exposed by the underlying layer, so I don't see what may go wrong. That wouldn't work for e.g. NaN. A NaN wants to "steal" a value but only if that's not available. It's complicated. * How to negotiate when both the user of Checked and the Hook need to customize the limits? (e.g. if you look at WithNaN it needs to reserve a special value, thus limiting the representable range). The idea is that WithNaN will not see the limits of the underlying types, but the limits set by the user. How to do this, see below. Nonono, NaN should be able to see the underlying type. I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition? From what I can see in my experience, saturation with custom min/max pops up once in a while in projects. So it's nice to have, even if it is a slight complication in the definition. Under consideration: struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max); Can I suggest a different approach? Different bounds implemented as a hook? alias MyBoundedInt = CheckedInt!(int, WithBounds!(0, 42)); The WithBounds hook would only define min, max and opCast. It may have other optional parameters, like whether to statically disallow narrowing casts, or what to do if a narrowing cast is found impossible at runtime. What do you think about this? It's the first thing I tried and it doesn't do well. The first thing ou want with a bounded type is to combine it with any other policy (abort, assert, saturate...). This kind of horizontal communication between hooks is tenuous and not supported well by the design. Andrei
Re: Do you like bounded integrals?
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: * When composing, do the limits compose meaningfully? Just to make sure I understand what you mean by "composing limits", do you mean this? alias NonNegativeInt = CheckedInt!(int, Abort, 0, int.max); alias Ubyte = CheckedInt!(NonNegativeInt, Abort, NonNegativeInt(0), NonNegativeInt(255)); Ubyte b; //b is guaranteed to be in [0, 255] And then we should expect this to fail: alias Byte = CheckedInt!(NonNegativeInt, Abort, NonNegativeInt(-128), NonNegativeInt(127));
Re: Do you like bounded integrals?
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: * When assigning a Checked to another, should the limits be matched statically or checked dynamically? The ideal would be implicit cast allowed when widening, explicit required when narrowing. As I think we will have to settle for always explicit, I would say dynamic check. This of course opens the way to many customizations: an user may want to customize what happens when the range check fails. Another user may even want a switch to statically disallow narrowing conversions. * When composing, do the limits compose meaningfully? Every layer should build on the limits exposed by the underlying layer, so I don't see what may go wrong. * How to negotiate when both the user of Checked and the Hook need to customize the limits? (e.g. if you look at WithNaN it needs to reserve a special value, thus limiting the representable range). The idea is that WithNaN will not see the limits of the underlying types, but the limits set by the user. How to do this, see below. I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition? From what I can see in my experience, saturation with custom min/max pops up once in a while in projects. So it's nice to have, even if it is a slight complication in the definition. Under consideration: struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max); Can I suggest a different approach? Different bounds implemented as a hook? alias MyBoundedInt = CheckedInt!(int, WithBounds!(0, 42)); The WithBounds hook would only define min, max and opCast. It may have other optional parameters, like whether to statically disallow narrowing casts, or what to do if a narrowing cast is found impossible at runtime. What do you think about this?
Re: Do you like bounded integrals?
On Tuesday, 23 August 2016 at 20:40:06 UTC, Andrei Alexandrescu wrote: I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition? Well, this occurs very frequently in my code: struct S { int foo; invariant { assert(ordered_(MIN, foo, MAX)); }; }; If bounded integers can handle this for me then you've got my support. Assuming the checks disappear in -release.
Do you like bounded integrals?
Currently checkedint (https://github.com/dlang/phobos/pull/4613) stands at 2432 lines and implements a variety of checking behaviors. At this point I just figured I can very easily add custom bounds, e.g. an int limited to 0 through 100 etc. It would take just a few lines because a lot of support is there (bounds hooks, custom min/max) anyway. However, I fear it might complicate definition and just be a bit much. Here's the design I'm thinking of. Current: struct Checkedint(T, Hook = Abort); Under consideration: struct Checkedint(T, Hook = Abort, T min = T.min, T max = T.max); It's easy to take the limits into account, but then there are a few messes to mind: * When assigning a Checked to another, should the limits be matched statically or checked dynamically? * When composing, do the limits compose meaningfully? * How to negotiate when both the user of Checked and the Hook need to customize the limits? (e.g. if you look at WithNaN it needs to reserve a special value, thus limiting the representable range). I think all of these questions have answers, but I wanted to gauge the interest in bounded checked integrals. Would the need for them justify additional complications in the definition? Andrei