Definitive Way to Constrain Response to X Decimal Places

4318
6
11-10-2020 05:28 PM
DerekKonofalski
Occasional Contributor

I'm at my wit's end and need some help coming up with a definitive solution to this problem that I've seen pop up on these discussion forums multiple times.

I have a Survey123 Connect form where one of the entries is a number that needs to be inputted as a decimal value with a precision of 2 decimal places. After searching on these discussion forums, I have found multiple potential responses and none of them seem to work. Here's a breakdown of what I've tried. If someone can poke some holes and/or confirm these, I would be grateful.

1. Format for decimal numbers 

Using this method, recommended by James Tedrick, to input the constraint as

. * 100 = int(. * 100)

seems like it should work. It's a simple check to see if the current value, multiplied by 100 matches the integer value of the same thing. If it's not 2 digits, the first part will still have a decimal point and, therefore, won't match. Unfortunately, there's either a bug in this or I'm not reading through this correctly because *most* numbers to 2 decimal places work except for 1.09, 1.10, and on up to 1.16 don't work. Typing in 1.08 or 1.17 is accepted fine but not anything in between. Even 2.09, 10.09, and 101.09 work, just not 1.09. I have no other constraints set (the quote is copy/pasted from James' post into my sheet and then into this comment) and no other fields on the XLSForm filled other than type (decimal), name, and label with a constraint and constraint message. There may be more very specific numbers that don't work but I haven't been able to find any others in my random testing.

2. survey 123 decimal places  

This post advises the use of an input mask which is mildly helpful with the big caveat that numbers have to match the mask exactly so a mask of '9999.99' will only allow 4 digits before and 2 digits after the decimal. That means that numbers need to be padded if they're not large enough (e.g., 0001.15 using the example number from above) and they cannot be larger than the number of digits in the mask (e.g., 10,015.15 is not possible as an entry for the mask '9999.99'). Another user recommends a regex expression but I'll cover that below. Based on the link provided by Ismael Chivite‌, there is only the option to use the input mask for *required* digits, not optional. Our specific use case could be as little as 0.05 and as much as 10,000.00 (possibly more but it's never happened).

3. Creating a constraint for 3 digits after the decimal in Survey123 

This post suggests a regex expression (which was also suggested in the previous thread, although with a different expression) so I attempted again with this expression:

regex(., '^[0-9]+\.[0-9]{2}$')

Based on that expression, it should be looking for one or more numbers at the start of the input followed by a decimal and then exactly 2 digits. It works for the example case of 1.15, 1.09, and also 101.15 but it fails with 1.10 and 1.20 or any other input that ends in 0, despite the fact that this should be allowed by the regex expression. Modifying the end of the expression from {2} to {1,2} allows for 1.10 and 1.20 but then it won't allow 1.00 or 2.00. It seems like the regex filter can't deal with 0's at the ends of strings properly. The post linked above also mentions this non-zero issue but there's not really a solution for it in any comments that I could find.

There is a suggestion mentioned at the bottom, by James again, that gives some arithmetic checks that should give the format desired:

Another way to multiply the number by the number of digits you are looking for and

- check to see if there is any decimal component left (${q}*1000 - int(${q})*1000) > 0

- check that the last digit is non-zero (${q}*1000) mod 10 > 0

but I wasn't able to convert or translate those into any reasonable constraints that worked for the conditions I tried. I tried using an 'and' operator to combine those 2 statements (.*100-int(.)*100)>0 and ((.*100) mod 10>0) but to no success. It still accepted 1.1 and 1.152 and anything else I threw at it.

------

All of this to ask: "What's the 'proper' way to enforce precision for decimal numbers?"

I've seen so many different responses on this and it seems like it should be something that's somewhat easy to implement and, most importantly, consistent. Unfortunately, I can't seem to find what's the "right" way to enforce this that lets me specify any number so long as it's precise to 2 decimal places.

HALP!

6 Replies
AndyMcClary
New Contributor III

As far as I can tell the decimal data type doesn't actually store trailing zeros so any arithmetic based check won't be able to tell whether the user entered exactly two decimal places. Regex based constraints will also fail with trailing zeros for the same reason. The best solution I've found is to create a text field with the numbers appearance and then use the following constraint:

regex(., '^[0-9]+\.\d{2}$')

If you want to store that value as a number, you could make a decimal field with the calculation set equal to that text field and appearance type hidden. You could also set the bind::esri:fieldType property of the text field to null so that you don't store the same value twice.  

It's a little convoluted and I'm not sure it's the "proper" solution but hopefully it helps,

Andy

DerekKonofalski
Occasional Contributor

Thanks for the reply. Yes, I was hoping for a "non-convoluted" way to address these issues but it seems like there really isn't one from what I can tell.  😞

0 Kudos
DerekKonofalski
Occasional Contributor

Just realized that I'd have to change the feature service and lose all the currently entered data in order to implement this "convoluted" method. Back to the drawing board for me. 😞

0 Kudos
JoshConrad1
New Contributor III
0 Kudos
abureaux
MVP Regular Contributor

I know this is rather old, but in my quick internet search to see if anyone else had a solution to this issue, I came across this post.

I recently posted a solution to this here. The formula I created essentially tests for both a decimal and counts the number of decimal places in an input number. Assuming the input number has both a decimal AND equals a specific number of decimal places (e.g., 3), it will return 'true'. I then have a constraint added to the input of ${my_formula}='true'

Seems to work quite well.

0 Kudos
SMH-Rio
New Contributor III

For me, this was the best solution I found:

regex(.,'^\d+(\.\d{1,2})?$')

Source: adapted from ChatGPT 😅

Allows integers and decimals with up to 2 decimal places (optional). I defined the regex in a text field and put bind::esri:fieldType=decimal.

The problem is that the decimal separator must be a period [.] (in Brazil, we use a comma [,]). Even changing the regex to allow the comma, the value is accepted by Survey123 but is not stored in the layer.

Apparently, the 'decimal' type question understands what the separator is for each location and performs a conversion so that the value is stored correctly in the database, which does not happen with the regex.

In countries that use the comma as a separator, it is a choice between forcing the user to use the period or having "weird values" in the layer.

0 Kudos