-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
number.multiple() enhancement - Issue #916 #918
number.multiple() enhancement - Issue #916 #918
Conversation
‘number.multiple()’ enhanced for: 1. Accept any Number (integer or float) as argument. 2. Accept references as argument. 3. For any non-number or zero multiple base, validate now returns error instead of throwing it.
‘number.multiple()’ enhanced for: 1. Accept any Number (integer or float) as argument. 2. Accept references as argument. 3. For any non-number or zero multiple base, validate now returns error instead of throwing it. 4. Required tests added & checked.
…nhancement-#916 # Conflicts: # lib/number.js
Rebase done |
You need to check error messages as well, false is not enough (eg. https://github.com/hapijs/joi/blob/master/test/number.js#L548). That's a new policy I intend to apply to all the tests but didn't have time to do. |
|
||
return this._test('multiple', base, function (value, state, options) { | ||
const mult = Math.pow(10, Math.max(internals.precision(value), internals.precision(divisor))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which case it this trying to prevent ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Marsup
It is to allow number.multiple() to allow float numbers to be checked. Currently number.multiple() cannot handle floats. However % operator cannot handle decimal numbers correctly. So the idea is to change the decimal numbers to integers before using % operator.
Example:
- To check if 4.523 is a multiple of 1.3, it checks if 4523 (4.523 * 10^3) is a multiple of 1300 (1.3 * 10^3).
- To check if 4.2 is a multiple of 1.05, it checks if 420 (4.2 * 10^2) is a multiple of 105 (1.05 * 10^2)
Added internals.precision(num) to find the number of decimal places in a any number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a real world problem for existing use cases though ? We don't want accurate modulo, we just want 0 when it's a multiple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes @Marsup it is a real life problem for existing use cases in financial application involving exchange rates that can have 2 to 5 decimal accuracy.
number.multiple() fails to validate entered currency value as exact multiple of an amount in another currency because of modulo operator's inability to handle decimal/floats correctly.
Example:
6711 % 67.11 results in 5.684341886080802e-14
3230 % 32.3 results in 2.8421709430404007e-13
Although in both the above cases it should have been 0 but modulo operator is broken for decimal/float values. (Check JSFiddle)
Similar real world use cases also exist in stock trading application.
@Marsup |
I'm now wondering whether it should be an extension or not, it's still unsafe and we'd be better off using something like big.js/decimal.js or whatever module works. |
@Marsup Thats why I felt that for now doing it in number.js will be better and then upgrade by adding float.js to clearly distinguish between integer and float validation. |
You misunderstood me, I was talking about modules, not files. I don't see any point in having another type since javascript doesn't make this difference either. |
@Marsup |
Sorry not unsafe, unreliable. However unlikely, given enough digits, you'll overflow the maximum capacity of javascript numbers with the multiplication. I'm still looking whether we can achieve the same thing without increasing original values but I can't find anything. |
Marsup I understand your point now.
|
@Marsup (A lighter version of it is also available at decimal.js-light). I tested the mod function with fairly large numbers (See JSFiddle) and it works perfectly alright. If you agree, it can be used. |
I already mentioned that library a few comments ago, it's also why I said I might have it as an extension, since this is exactly why it was made. |
# Conflicts: # test/number.js
@Marsup |
When I said an extension, I meant a separate module (see Joi.extend()), sorry if that was unclear. I'm not adding any dependencies to the core unless they serve a common interest. |
Removed big.js and reverted back to regular modulo operator. Also added notes to the documentation about the limitations of number.multiple() with decimals/floats.
@Marsup Okay... I understand. Removed big.js but kept float acceptance and refs. |
@@ -53,12 +53,29 @@ internals.Number = class extends Any { | |||
|
|||
multiple(base) { | |||
|
|||
Hoek.assert(Hoek.isInteger(base), 'multiple must be an integer'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't remove those, just allow floats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't remove this then it throws error instead of gracefully validating (thats how it happens even now). It starts throwing exception for non-integer number, so what is the point in allowing float numbers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And that's intended, this is to prevent developers mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that means:
- The developer should first validate if the multiple is zero (the developer is using Joi for validation and there is no way to validate that in Joi).
- Allowing floats has no meaning, because it will throw exception anyways.
Am I right?
So in effect we are just making the changes for allowing refs.?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The developer is using joi to validate external inputs, not to validate its own inputs, there are synchronous throws for that yes.
How would allowing floats crash anything ?
|
||
return this._test('multiple', base, function (value, state, options) { | ||
return this._test('multiple', base, (value, state, options) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not use => here.
Restored errors to be thrown for non-integer and zero value of multiple.
const isRef = Ref.isRef(base); | ||
|
||
if (!isRef) { | ||
Hoek.assert(Hoek.isInteger(base), 'multiple must be an integer'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So why aren't you allowing floats here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Marsup Allowed floats now.
@Marsup Have allowed all numbers (Integers & floats). Is there anything else that I have missed? |
I don't think so but I'll review more in depth tonight. |
Merged as 3a32b92. Cleaned it up a bit before merging, thanks for your contribution. |
This thread has been automatically locked due to inactivity. Please open a new issue for related bugs or questions following the new issue template instructions. |
‘number.multiple()’ enhanced for:
instead of throwing it.