.Net Float error 4 - float(4) = 4.44089209850063E-16 - sql

I have a sql database, it has a column of floats. In it, the value is "4" and it is displayed a "4" while other values are 2.5, 3.6, all have decimals and those seem to function fine for my issue. But the number without a decimal is different.
I have a basic function
Private Function getScore(scoreValue As Double) As String
If (scoreValue = 5) Then
Return "Platinum"
ElseIf scoreValue >= 4 Then
Return "Gold"
ElseIf scoreValue >= 3 Then
Return "Silver"
ElseIf scoreValue >= 2 Then
Return "Bronze"
Else
Return "Unsatisfactory"
End If
End Function
When i pass this "4" that's a float, it returns the "silver" score. How am i to do this function correctly, i can't round the score, so i can't cast it as an integer. I did various debug statements to figure out what was going on and when i output scoreValue - 4 it yielded 4.44089209850063E-16

If you are not going to change the whole application and are focused on this method, you can use Math.Round:
scoreValue = Math.Round(scoreValue, 2, MidpointRounding.AwayFromZero)
This will round the number, but not to an integer - it should keep some meaningful scale.
Either way, you should also consider the scoreValue = 5 condition: it might also fail. See also: Comparing double values in C#

Related

Sorting List(Of T) stopping early

I have a custom class called SaveFile. One property it has is SaveNumber and I'm trying to sort the list by that property. I read a bunch of articles here and got it to sort properly but it's stopping early? The case in which I noticed this is with set that has 79 saves. It would sort: 1, 2, 3, 4, 5, 6, 7, 10, 11, ... 30, 8, 31, ... 70, 9, 71, .. The code I use is
saveList.Sort(Function(x, y) x.CompareTo(y))
But if I use the code:
For i = 0 To 3
saveList.Sort(Function(x, y) x.CompareTo(y))
Next
then it sorts right but it takes a very long time and one set has over 700 SaveFiles in it so it takes almost 5 minutes for my program to load. Any ideas?
This is the code that I have for my CompareTo function:
Public Function CompareTo(y As SaveFile) As Integer Implements IComparable(Of SaveFile).CompareTo
'If neither are an autosave then compare save number
If Not Me.Text.StartsWith("autosave") And Not y.Text.StartsWith("autosave") Then
If Me.SaveNumber.ToString.Length > y.SaveNumber.ToString.Length Then
Return False
ElseIf Me.SaveNumber.ToString.Length < y.SaveNumber.ToString.Length Then
Return True
End If
Return Me.SaveNumber < y.SaveNumber
'if either is an autosave
Else
'Create to comparable integers with
'a value of 4. We set the value to
'4 because it is higher than the 3
'available autosaves, making it sort
'after any autosaves if only one is
'an autosave.
Dim xComp As Integer = 4
Dim yComp As Integer = 4
'If x is an autosave then figure out
'which autosave number it is.
If Me.Text.StartsWith("autosave") Then
Select Case True
Case Me.Text.EndsWith("1")
xComp = 1
Case Me.Text.EndsWith("2")
xComp = 2
Case Me.Text.EndsWith("3")
xComp = 3
End Select
End If
'if y is an autosave then figure out
'which autosave number it Is.
If y.Text.StartsWith("autosave") Then
Select Case True
Case y.Text.EndsWith("1")
yComp = 1
Case y.Text.EndsWith("2")
yComp = 2
Case y.Text.EndsWith("3")
yComp = 3
End Select
End If
Return xComp < yComp
End If
End Function
First, asking 2 questions in one post is not a good idea because it reduces the chances someone will know the answer to both. For example, without seeing the code that loads these things or even what they are, makes it just a guess why it takes so long to load.
For the sorting, your CompareTo method is flawed. From MSDN:
Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
That is, it should return -1 (precedes), 1 (follows) or 0 (same). Yours just returns Boolean:
If Me.SaveNumber.ToString.Length > y.SaveNumber.ToString.Length Then
Return False
ElseIf Me.SaveNumber.ToString.Length < y.SaveNumber.ToString.Length Then
Return True
End If
...
Return xComp < yComp
We have no idea what type SaveNumber is, but if it is am integer, comparing the length of the string form if it is not the same as comparing the value:
' Me vs Other
Return "9".Length < "7".Length
That will return False (0) when it by value, it should return 1. Turning on Option Strict would have flagged the incorrect return type, which might have led you to the answer. It should be something like this, ignoring the "auto" logic:
Public Function CompareTo(other As SaveItem) As Integer _
Implements IComparable(Of SaveItem).CompareTo
... special handling for "auto"
If Me.SaveNumber = other.SaveNumber Then
Return 0
ElseIf Me.SaveNumber < other.SaveNumber Then
Return -1
Else
Return 1
End If
End Function
But, you may not even need that (assuming again that SaveNumber is an int):
saveItems = saveItems.OrderBy(Function(q) q.SaveNumber).ToList()
IComparable.CompareTo() might be needed in order to handle the "autosave" logic but it might be able to be handled in the OrderBy() depending on the information the class exposes.

Return a value from a class into a form

I am calling a function from a class where it will return on a label different colors depending on the value that shows on the label. Below is the method:
Function GetLabelColor(value As Integer) As Color
If value <= 3.9 Then
Return Color.Green
ElseIf (value >= 4) And (value <= 6.9) Then
Return Color.Orange
ElseIf value >= 7 Then
Return Color.Red
End If
End Function
And then in the form I am using this code to call it:
Dim uicommon = New CommonUI()
Dim labelColor = uicommon.GetLabelColor(Integer.Parse(lblResultadoTotal.Text))
lblResultadoTotal.ForeColor = labelColor
lblGB.ForeColor = labelColor
But for some reason it will return me only the Green color even if the value is greater then 3.9. Do you have any solution who might help me?
The function below does the same thing as the one you provided but accepts a double that will allow for decimals such as 3.9 to be passed to it
Function GetLabelColor(value As Double) As Color
If (value >= 4) And (value < 7) Then
Return Color.Orange
ElseIf value >= 7 Then
Return Color.Red
End If
Return Color.Green
End Function
If you only want integers (whole numbers) passed to it then you should not do comparisons like value <= 3.9 because an integer will never equal 3.9. Instead you could use this version;
Function GetLabelColor(value As Integer) As Color
If (value >= 4) And (value <= 6) Then
Return Color.Orange
ElseIf value > 6 Then
Return Color.Red
End If
Return Color.Green
End Function
If you use a Double version of the method then you will need to parse your string value using this;
Double.Parse(lblResultadoTotal.Text)
If you are not getting the value you expect it is not a problem with your method but the value being passed to it. Try hard-coding a value to test this; i.e. replace Integer.Parse(lblResultadoTotal.Text) with an actual number e.g. 5 and see if you get the color you expect.
This shows a simple console application that can demonstrate it working for the value 10.4
Imports System.Drawing
Module Module1
Sub Main()
Console.WriteLine(GetLabelColor(Double.Parse("10.4")))
Console.ReadLine()
End Sub
Function GetLabelColor(value As Double) As Color
If (value >= 4) And (value <= 6) Then
Return Color.Orange
ElseIf value > 6 Then
Return Color.Red
End If
Return Color.Green
End Function
End Module
I guess you must use Double instead of Integer.
But for some reason it will return me only the Green color even if the value is greater then 3.9. Do you have any solution who might help me?
"Greater than 3.9" is not the same thing as ">= 4". In number theory, there are an infinite number of values between 3.9 and 4.0. The realities of binary floating-point representation changes that a little so that there are not actually infinitely many, but there are still a damned lot. Under no circumstances will the value 3.9 compare as >= 4.0.
If you want 3.9 to act like 4.0, then you should be rounding the values to whole numbers (integers). There is a built-in method to do that, named Math.Round. There is an overload that allows you to specify a value from the MidpointRounding enumeration, which controls how a number that ends in .5 should be treated. For example, AwayFromZero would round 2.5 to 4.0, while ToEven would round it to 3.0. For 3.5, the results would be the same in both cases.
Function GetLabelColor(value As Double) As Color
Dim roundedValue As Integer = Math.Round(value, MidpointRounding.AwayFromZero)
If (roundedValue >= 4) AndAlso (roundedValue < 7) Then
Return Color.Orange
ElseIf roundedValue >= 7 Then
Return Color.Red
Else
Return Color.Green
End If
End Function
Note: You probably want to use AndAlso in your condition, rather than And. The results will be the same in both cases, but AndAlso and OrElse are the preferred logical operators. The And and Or operators are reserved for bitwise operations, which is not what you're doing here. And hardly ever what you will be doing.

VB.NET - DataGridView substring cell for Number(10,2)

I have a DataGridView with a column where the user can insert double. I have to control the cell value before i insert in my database because the table have a number(10,2) datatype for this field.
My current code :
Dim length As Integer = Nothing
Dim row As Integer = DTG.CurrentCell.RowIndex
Dim column As Integer = DTG.CurrentCell.ColumnIndex()
With DTG(row).Cells(column)
length = Len(.Value)
If Not IsNothing(.Value) Then
If Not IsNumeric(.Value) Then
.Value = 0
End If
If length > 10 Then
.Value = .Value.SubString(0, 10)
If .Value.Contains(".") Then
.Value = .Value.SubString(0, 9)
End If
End If
End If
End With
The length method is not appropriate here, because if my cell contains ".", the length increases.
Examples :
1234567891 => length = 10 => insert : 1234567891
123456789.1 => length = 11 => insert : 123456789
In the 2nd case, i need to insert 123456789.1
Can someone advise me ? Thank you
You can use .Value.IndexOf(".") to get the number of digits before the decimal separator (<= 10).
If I'm not mistaken, a database field with a datatype of number(10,2) means that it can carry a maximum value of 99999999.99, using this you might change your code to look like
Dim dbMaxNum As Decimal = 99999999.99
With DTG(row).Cells(column)
If Not IsNothing(.Value) Then
If Not IsNumeric(.Value) Then
.Value = 0
Else
.Value = Math.Min( _
dbMaxNum, _
Math.Round(CDec(.Value), 2, MidpointRounding.AwayFromZero))
End If
End If
End With
Where we first round .Value to two decimal places and then we choose the minimum between the result and the maximum your database field can hold (99999999.99)
The option MidpointRounding.AwayFromZero is to prevent unexpected rounding results, for example 32.625 rounding to 32.62 (an issue that was brought up in this question).
I finally decided to work with the cell value.
If .Value > 99999999.99 Then
.Value = Convert.ToDouble(.Value.SubString(0, 8))
End If
I changed the style format to "N2", so the user can't write more than 2 decimals :
.ValueType = GetType(Double)
.Style.Format = "N2"
I also found another way to do this, i could format my column like a masked textbox. I'll try this solution later.
EDIT :
The previous answer is pretty bad but it had helped me for a while. I found a better way to handle my problem. I created a function that check the number of digits before the decimal, and if the length is higher than 8, its return False :
Public Function numberDigitsBeforeDecimal(ByVal montant As Double) As Boolean
Dim beforeDigit As String = montant.ToString.Substring(0, montant.ToString.IndexOf("."))
If beforeDigit.Length > 8 Then
Return False
Else
Return True
End If
End Function

Mod with Doubles

Am I doing something wrong or does the VBA Mod operator actually not work with floating point values like Doubles?
So I've always sort of assumed that the VBA Mod operator would work with Doubles based on the VB documentation, but in trying to figure out why my rounding function doesn't work, I found some unexpected Mod behavior.
Here is my code:
Public Function RoundUp(num As Double, Optional nearest As Double = 1)
RoundUp = ((num \ nearest) - ((num Mod nearest) > 0)) * nearest
End Function
RoundUp(12.34) returns 12 instead of 13 so I dug a little deeper and found that:
12.5 Mod 1 returns 0 with the return type of Long, whereas I had expected 0.5 with a type of Double.
Conclusion
As #ckuhn203 points out in his answer, according to the VBA specification,
The modulus, or remainder, operator divides number1 by number2
(rounding floating-point numbers to integers) and returns only the
remainder as result.
And
Usually, the data type of result is a Byte, Byte variant, Integer,
Integer variant, Long, or Variant containing a Long, regardless of
whether or not result is a whole number. Any fractional portion is
truncated.
For my purposes, I need a floating point modulo and so I have decided to use the following:
Public Function FMod(a As Double, b As Double) As Double
FMod = a - Fix(a / b) * b
'http://en.wikipedia.org/wiki/Machine_epsilon
'Unfortunately, this function can only be accurate when `a / b` is outside [-2.22E-16,+2.22E-16]
'Without this correction, FMod(.66, .06) = 5.55111512312578E-17 when it should be 0
If FMod >= -2 ^ -52 And FMod <= 2 ^ -52 Then '+/- 2.22E-16
FMod = 0
End If
End Function
Here are some examples:
FMod(12.5, 1) = 0.5
FMod(5.3, 2) = 1.3
FMod(18.5, 4.2) = 1.7
Using this in my rounding function solves my particular issue.
According to the VB6/VBA documentation
The modulus, or remainder, operator divides number1 by number2
(rounding floating-point numbers to integers) and returns only the
remainder as result. For example, in the following expression, A
(result) equals 5. A = 19 Mod 6.7 Usually, the data type of result is
a Byte, Byte variant, Integer, Integer variant, Long, or Variant
containing a Long, regardless of whether or not result is a whole
number. Any fractional portion is truncated. However, if any
expression is Null, result is Null. Any expression that is Empty is
treated as 0.
Remember, mod returns the remainder of the division. Any integer mod 1 = 0.
debug.print 12 mod 1
'12/1 = 12 r 0
The real culprit here though is that vba truncates (rounds down) the double to an integer before performing the modulo.
?13 mod 10
'==>3
?12.5 mod 10
'==>2
debug.print 12.5 mod 1
'vba truncates 12.5 to 12
debug.print 12 mod 1
'==> 0
I believe that the Mod operator calculates with long type only. The link that you provided is for VB.Net, which is not the same as the VBA you use in MSAccess.
The operator in VBA appears to accept a double type, but simply converts it to a long internally.
This test yielded a result of 1.
9 Mod 4.5
This test yielded a result of 0.
8 Mod 4.5
As a work around your can do some simple math on the values. To get two decimal of precision just multiply the input values by 100 and then divide the result by 100.
result = (123.45*100 Mod 1*100)/100
result = (12345 Mod 100)/100
result = 0.45
I'm late to the party, but just incase this answer is still helpful to someone.
Try This in VBS:
Option Explicit
Call Main()
Sub Main()
WScript.Echo CStr(Is_Rest_Of_Divide_Equal_To_Zero(506.25, 1.5625))
End Sub
Function Is_Rest_Of_Divide_Equal_To_Zero(Divident, Divisor)
Dim Result
Dim DivideResult
If Divident > Divisor Then
DivideResult = Round(Divident/Divisor, 0)
If (DivideResult * Divisor) > Divident Then
Result = False
ElseIf (DivideResult * Divisor) = Divident Then
Result = True
ElseIf (DivideResult * Divisor) < Divident Then
Result = False
End If
ElseIf Divident = Divisor Then
Result = True
ElseIf Divident < Divisor Then
Result = False
End If
Is_Rest_Of_Divide_Equal_To_Zero = Result
End Function
Public Function Modi(d as double) as double
Modi = d - Int(d)
End Function
Dim myDoule as Double
myDoule = 1.99999
Debug.Print Modi(myDoule)
0.99999

Round up to the nearest multiple of a number

This question has already been asked for the C++ language but I need a function for VBA. I tried converting the C++ function to VBA, but it doesn't return the right values.
I need a function that does the following:
RoundUp(23.90, 5)
'return 25
RoundUp(23.90, 10)
'return 30
RoundUp(23.90, 20)
'return 40
RoundUp(23.90, 50)
'return 50
RoundUp(102.50, 5)
'return 105
RoundUp(102.50, 20)
'return 120
Here's what I have so far. It works most of the time, but returns incorrect values for numbers that are less than .5 less than the multiple. So the problem seems to be a rounding problem with how I'm calculating the remainder value.
Public Function RoundUp(dblNumToRound As Double, lMultiple As Long) As Double
Dim rmndr As Long
rmndr = dblNumToRound Mod lMultiple
If rmndr = 0 Then
RoundUp = dblNumToRound
Else
RoundUp = Round(dblNumToRound) + lMultiple - rmndr
End If
End Function
For Example:
RoundUp(49.50, 50)
'Returns 49.50 because rmndr = 0
I'd simply divide by the lMultiple, round up and multiply again.
Assuming you indeed always want to round up (also for negative numbers):
Public Function RoundUp(dblNumToRound As Double, lMultiple As Long) As Double
Dim asDec as Variant
Dim rounded as Variant
asDec = CDec(dblNumToRound)/lMultiple
rounded = Int(asDec)
If rounded <> asDec Then
rounded = rounded + 1
End If
RoundUp = rounded * lMultiple
End Function
I'm not actually a VBA programmer, so this might need a tweaked comma or two. However the important thing is:
Use Decimal (variant subtype) for precision
Let VB do the math for you
Worth trying WorksheetFunction.Ceiling method (Excel)
WorksheetFunction.Ceiling(27.4,5)
Above example will return 30. Here is Link:
https://learn.microsoft.com/en-us/office/vba/api/excel.worksheetfunction.ceiling
A far simpler solution is to add .5 to the number before rounding:
1.1 -> Round(1.1+.5, 0) -> 2