Decimal places for multiple formats - excel

I am trying to figure out a way to increase/decrease decimal places for selected range (one or multiple cells with potentially multiple different formats).
So far my codes is as follows:
Dim myformat As String
Dim cell As Range
Dim val As Variant
For Each cell In Selection
val = cell.Value
myformat = cell.NumberFormat
If IsDate(val) = False Then
If InStr(1, myformat, ".") > 0 Then
myformat = myformat & "0"
Else
myformat = "0.0"
End If
End If
cell.NumberFormat = myformat
Next cell
Generally, it works, but not in its entirety. What I need it to do is to work for percentage, general, number formats as well as for formulas (not straight put cell values) and CUSTOM formats (this I cannot bypass no matter how hard I try to do it...).
The code should NOT work (should exit sub or do nothing) when it sees that cell contains time/date format (I managed to do that).
Below you will find types of formats I am developing my macro on:
As you can see my way of coding (I'm trying to learn) is to simply edit NumberFormat by reading the existing format and changing it as needed. This works for simple formats, but when combined with custom formats? It just does not work.
Does anyone have any ideas how can I proceed with my idea? Any help is much appreciated!
Thank you in advance for anyone willing to assist me in this. I am getting better in VBA with each day passing but not THAT good, yet ;)

For custom formats that already include a decimal point, instead of:
myformat = myformat & "0"
It would be more reliable to use:
myformat = replace(myformat,".",".0")
This will ensure an extra 0 is added after all decimal points, including in custom number formats with multiple sections, such as:
#,##0.00;[Red]-#,##0.00
To add a decimal point to a number where there isn't yet a decimal point, instead of:
myformat = "0.0"
use:
myformat = replace(myformat,"0","0.0")
Note that it still won't handle all possible custom number formats, which would require an enormous amount of parsing, but it should handle the standard types of formats as per your example.
To remove decimal points, do the reverse:
myformat = replace(myformat,".0",".")
This would leave behind a decimal point after all zeroes are removed, however this could be handled by checking if the number format contained decimal points that weren't followed by zeroes:
If InStr(myformat, ".") > 0 and InStr(myformat, ".0") = 0 then
myformat = replace(myformat,".","")
End if

Related

Is there a way to do 5 different number formats in one cell or write VBA script to produce a number format based on the value?

The data I produce has to have specific number formatting which depends on how large or how small the number is.
Numbers >=100 shouldn't have any decimal places, >=10 should have one decimal place (even if it is a zero), >=1 should have two decimal places (even if the last is a zero), 0.999-0.001 should have three decimal places (even if the last is a zero), and 0.0009-0.0001 should have four decimal places, again regardless of what the number in the last decimal place is.
So essentially I need a code, or way, to look at a number and give me those specific decimal places. I know I can format them individually, but we are talking thousands upon thousands of numbers that I would be formatting. I have tried using the round function but it won't give me trailing zeros.
This is currently what I am using as a function on the spreadsheet which works for everything but trailing zeros:
=IF(VLOOKUP($A$21,'Copied Data'!$A$15:$FL$140,23)>'Copied Data'!U$7,IF('Copied Data'!W16<0.001,ROUND('Copied Data'!W16,4),IF('Copied Data'!W16>=0.001,ROUND('Copied Data'!W16,3),IF('Copied Data'!W16>0.999,ROUND('Copied Data'!W16,2),IF('Copied Data'!W16>9.999,ROUND('Copied Data'!W16,1),IF('Copied Data'!W16>99.999,ROUND('Copied Data'!W16,0)," "))))),"<"&IF('Copied Data'!U$7<0.001,ROUND('Copied Data'!U$7,4),IF('Copied Data'!U$7>=0.0001,ROUND('Copied Data'!U$7,3),IF('Copied Data'!U$7>0.999,ROUND('Copied Data'!U$7,2),IF('Copied Data'!U$7>9.999,ROUND('Copied Data'!U$7,1),IF('Copied Data'!U$7>99.999,ROUND('Copied Data'!U$7,0)," "))))))
If there is a better way to do this, or a way to write a macro for it in VBA, that would be great! Thank you.
Something like this will deal with modifiers:
Dim rng As Range, c As Range, op, v
Set rng = ActiveSheet.Range("A1:A100") 'for example, or = Selection
For Each c In rng.Cells
v = Trim(c.Value)
If Len(v) > 0 Then
'if the value has a modifier, remove it
op = ""
If v Like ">*" Or v Like "<*" Then
op = Left(v, 1)
v = Trim(Right(v, Len(v) - 1))
End If
If IsNumeric(v) Then
v = CDbl(v)
If v > 0.0001 And v <= 0.00099 Then
c.Value = v
c.NumberFormat = op & "[format1]" 'include any modifier in the format
ElseIf v > 0.001 And v <= 0.999 Then
c.Value = v
c.NumberFormat = op & "[format2]"
Else
'you should probably include a general format so all
' cells which might have modifiers get the same treatment
End If
'add other cases as needed
End If
End If
Next c
Where [format1] etc are the formats you got from your macro recording
Note that any modifiers are no longer part of the value of the cell after this - they exist only in the cell format. That means you can work with the cells as numbers, but depending on what you need to do eventually it might or might not be appropriate.
If you just want to change the format of the numeric part of the cell value, then you could do something like:
c.Value = Application.Text(v, op & "0.0000")

Prevent Excel converting number range to Date format

I'm using Excel to translate data from one system which outputs .csv files to another which can read in .xls files. One column is sizes, which frequently includes terms like 4-6, 6-8, etc. When the csv is opened in Excel, these are automatically(?) converted to dates (6-Apr, 8-Jun, etc.) I can use
.NumberFormat = "m-d"
to get the data to *look like it should, but of course it's still a date value, and any operations I try to perform with it convert back to date. Is there any way to convert it to text in exactly that format? If I declare the .NumberFormat as "#", it just changes the value to a serial. I use the size, along with several other columns to create one long product ID code for inventory purposes.... when the size changes to a date value, I end up missing some of the data.
I found a solution to this problem that runs entirely from VBA - i.e., doesn't depend on special import conditions. I would appreciate any pointers or recommendations, especially if this has the potential to create unforeseen errors:
Dim Size as String
For i = 2 To LastRow
If IsDate(Range("H" & i).Value) Then
Size = Month(Range("H" & i)) & "-" & Day(Range("H" & i))
Range("H" & i).NumberFormat = "#"
Range("H" & i) = Size
End If
Next i
The column I'm testing includes size values that are string (XL, SM, 12T, etc), along with values that get converted to dates (4-6, 6-8). This seems to convert it back to a string value, and then properly treats it as a string value on all future uses (concatenations and logical tests)

Global VBA date format and decimal separator

Is there a way to change VBA settings globally on PC to accept dates and number on a specified format? (on my case dd/mm/yyyy and comma)
Changing Excel settings doesn't solve it for me.
As an small time VBA developer, I'm mostly creating userforms for data input and validation. Alongside with some basic access privileges, It keeps users (mostly an client's hired arms) from nosing on the database and corrupting it.
But, on form's submitting, the textbox values are saved temporally on spreadsheet's cells. Somehow on this step dates get scrambled and in some cases an 3 decimal places numeric gets multiplied by a thousand (e.g. 1/2/2000 turn to 2/1/2000 and 1,234 turn 1234). It defeats the whole purpose of those applications - data gets corrupted.
I've been able to workaround these using the Format(expression, format) function, but that must be applied every time an date or some precision number is added, or even used on some auxiliary task.
This is an recurrent problem for me because, as an Brazilian, dates are formatted as dd/mm/yyyy and decimal separator is ","(comma) on practically 100% of my local users.
Anybody had similar problems?
TIA
Excel doesn't have a default date format. Excel uses the Window System Date format settings. You can change you system setting by go to Control Panel -> Change date, time and number formats.
Change Date Format in Windows 7, 8.1 and Windows 10 to dd-mm-yyyy
After adjusting the Windows System Settings to dd-mm-yyyy, CDate will expect strings to be in the dd-mm-yyyy.
Range("A1").Value = CDate( "11/01/2016" )
Result: Monday, January 11, 2016
Summary of comments for those to lazy to read.
Alright, as Thomas Inzina pointed, the strait answer to my question is NO, you can't because there isn't such thing in VBA as this Global setting.
As Rory pointed out, the CDate function should solve (indeed it does) this issue, at least as to the date. Again, Thomas answer didn't include it but it points to the windows conf that would be used by the CDate function.
Datetimepicker, suggested by cyboashu, would solve this issue too, but it requires some tweaking on the user's PC to be available. Too much work for me. Although, this approach has the "pretty" advantage, adds value to your project.
Still looking for the comma/dot bad conversion problem. I'll keep editing this answer while none better exists.
Here I write a function called gdate that accepts a string with a date in a specific format. Then I parse the string, then call cdate based on the users date settings.
All I have to do is find/replace cdate with gdate throughout my code. Now I have a way to handle all date formats by always expecting an exact one gdate("mm/dd/yyyy"). Adjust the parsing if you want to expect different format. Building off built-in objects and functions is how we make things work.
Function gdate(ByVal dstring As String)
' 0 = month-day-year; 1 = day-month-year; 2 = year-month-day
d = Mid(dstring, InStr(1, dstring, "/") + 1, Len(dstring) - 5 - InStr(1, dstring, "/"))
m = Left(dstring, InStr(1, dstring, "/") - 1)
y = Right(dstring, 4)
dtype = Application.International(xlDateOrder)
Select Case dtype
Case 0: gdate = CDate(m & "/" & d & "/" & y)
Case 1: gdate = CDate(d & "/" & m & "/" & y)
Case 2: gdate = CDate(y & "/" & m & "/" & d)
End Select
End Function

VBA - Find date within text containing format 'DDMMYYYY'

I've scanned and found many similar answers to my question although none of them are quite ticking the boxes, so I'll explain what I'm after and hopefully it makes sense:
I have imported data into an excel spreadsheet (working with 2007) from a TXT, which contains various dates (all in the format DDMMYYYY).
What I'm trying to do is create a sub routine which finds any dates in this format and subsequently decreases the date by 1 year.
The dates are within a range when the TXT has been imported (in this instance, the first 2 dates appear under range A6) so ideally I would like to specify that range because I don't necessarily want to decrease all dates present in the TXT.
For example, here's the incorrect code which I am sure needs some serious tweaking!
Cells.Replace What:="Dates", Replacement:="Dates-1", LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=DDMMYYYY, _
ReplaceFormat:=False
I gather there is some prior definition that needs to be carried out before anything like the above code would work but if there is anyone who can help, I'd appreciate it!
Many thanks,
Robin.
I do not believe any tweak will allow your code to achieve the effect you seek. I cannot give a definitive answer because I do not quite believe your description. I hope the information below will allow you to investigate your data and develop an appropriate solution.
You are not, as far as I know, using SearchFormat correctly. You need something like this:
With Application.FindFormat
.Font.Bold = True
End With
Set Rng = .Find(. . . SearchFormat = True . . .)
The VBA Editor’s Help for Find states: “SearchFormat Optional Variant. The search format.” but gives no example to show what that means. I cannot find an example of using the find format facility that is not in the above style. That is: you use FindFormat to specify the format(s) of interest and set SearchFormat to True to indicate that those formats are to be searched for.
You could try:
With Application.FindFormat
.NumberFormat = "DDMMYYYY"
End With
I have not tried this and I cannot find any documentation that explains what format types can be searched for. If this works, it will almost certainly be faster and easier than anything based on the following information.
For Excel to import a string within a text file as a Date, it must:
Recognise the string as a date.
Convert that string to a number.
Store that number with a number format that indicates it is a date.
Excel stores dates as the number of days since 1/1/1900. For example, “21 August 2015” will be stored as 42238 with a number format of “dd mmmm yyyy”. There is nothing about the cell value that says it’s a date. You can enter the cell value as 42238 and later set the number format to “dd mmm yy” and the value will display as “21 Aug 15”.
Your description implies the dates are held in the text file as eight-digit values which Excel recognises as dates and therefore sets the number format to "DDMMYYYY". I have never managed to get Excel to recognise eight-digit values as dates. If you have succeeded, I would like to know how.
My best suggestion is:
For Each CellCrnt in RngImport
If CellCrnt.NumberFormat = "ddmmyyy" Then
' Add year to date
End If
Next
Extension
If you perform the actions I requested in the comment against your question, we should be able to identify the type of the values you wish to locate and modify. This extension explains how you might use that information.
In my earlier code I used RngImport to represent the Range of the imported data. One of your remarks makes me wonder: do you know how to initialise that Range?
Do we have to search the entire Range for these “dates”? That is, are there scattered across the data or are they restricted to a single column? The final code will be faster if we do not have to examine every cell.
My guess is we will need something like:
If IsDate(Mid(CellValue,1,2) & "/" & Mid(CellValue,3,2) & "/" & Mid(CellValue,5,4)) Then
to convert “ddmmyyyy” to “dd/mm/yyyy” and then test the new string to be a date.
Here is my suggestion:
Sub OneYearEarlier()
Dim c As Range
Dim dt As Date
For Each c In Selection
If c.NumberFormatLocal = "TT.MM.JJJJ" Then
dt = DateAdd("yyyy", -1, c.Value)
c.Value = dt
End If
Next c
End Sub
Firstly, I think you have to handle the cell value as a Date data type, not as a string. This will avoid overflow or underflow which might occur if not decreasing by one year but some other time interval, e.g. one month.
Secondly, as mentioned by others you cannot reliably detect that a number is a date in XL. My code uses
- only cells which you have selected beforehand (e.g. one column)
- only cells with a date format
I use NumberFormatLocal here so that the format string is identical to the one which you see in the format dialog/"custom format". But this is not really critical here, just a convenience.
I really dislike having to use both the 'internal' format string "yyyy" and the localized format string "JJJJ" but that's the way DateAdd wants it.
You can always create your own RegEx function to simplify:
Function RegEx(Target As String, RegExpression As String, _
Optional ReplaceString As String, Optional xIgnoreCase As Boolean, _
Optional xGlobal As Boolean, Optional xMultiLine As Boolean)
Dim regexOne As Objectv
Set regexOne = New RegExp
regexOne.Pattern = RegExpression
If xIgnoreCase Then regexOne.IgnoreCase = xIgnoreCase
If xGlobal Then regexOne.Global = xGlobal
If xMultiLine Then regexOne.MultiLine = xMultiLine
If regexOne.Test(Target) Then
If IsMissing(ReplaceString) Then
RegEx = regexOne.Execute(Target)
Else
RegEx = regexOne.Replace(Target, ReplaceString)
End If
End If
End Function
You could use this for your problem in this way:
Function fDateAdd(SearchTextas Date, DateUnit as String, ModAmount as Interger)
FoundText = RegEx(SearchText, "######")
Do
If IsDate(FoundText) And (Mid(FoundText, 5, 2)="19" Or Mid(FoundText, 5, 2)="20") Then
ReplaceWithText = DateAdd(DateUnit, ModAmount, CDate(FoundText))
fDateAdd = Replace(SearchText, FoundText, ReplaceWithText)
End If
FoundText = RegEx(SearchText, "######")
Loop While FoundText <> ""
End Function

How to get excel to display a certain number of significant figures?

I am using excel and i want to display a value to a certain number of significant figures.
I tried using the following equation
=ROUND(value,sigfigs-1-INT(LOG10(ABS(value))))
with value replaced by the number I am using and sigfigs replaced with the number of significant figures I want.
This formula works sometimes, but other times it doesn't.
For instance, the value 18.036, will change to 18, which has 2 significant figures. The way around this is to change the source formatting to retain 1 decimal place. But that can introduce an extra significant figure. For instance, if the result was 182 and then the decimal place made it change to 182.0, now I would have 4 sig figs instead of 3.
How do I get excel to set the number of sig figs for me so I don't have to figure it out manually?
The formula (A2 contains the value and B2 sigfigs)
=ROUND(A2/10^(INT(LOG10(A2))+1),B2)*10^(INT(LOG10(A2))+1)
may give you the number you want, say, in C2. But if the last digit is zero, then it will not be shown with a General format. You have then to apply a number format specific for that combination (value,sigfigs), and that is via VBA. The following should work. You have to pass three parameters (val,sigd,trg), trg is the target cell to format, where you already have the number you want.
Sub fmt(val As Range, sigd As Range, trg As Range)
Dim fmtstr As String, fmtstrfrac As String
Dim nint As Integer, nfrac As Integer
nint = Int(Log(val) / Log(10)) + 1
nfrac = sigd - nint
If (sigd - nint) > 0 Then
'fmtstrfrac = "." & WorksheetFunction.Rept("0", nfrac)
fmtstrfrac = "." & String(nfrac, "0")
Else
fmtstrfrac = ""
End If
'fmtstr = WorksheetFunction.Rept("0", nint) & fmtstrfrac
fmtstr = String(nint, "0") & fmtstrfrac
trg.NumberFormat = fmtstr
End Sub
If you don't mind having a string instead of a number, then you can get the format string (in, say, D2) as
=REPT("0",INT(LOG10(A2))+1)&IF(B2-(INT(LOG10(A2))+1)>0,"."&REPT("0",B2-(INT(LOG10(A2))+1)),"")
(this replicates the VBA code) and then use (in, say, E2)
=TEXT(C2,D2).
where cell C2 still has the formula above. You may use cell E2 for visualization purposes, and the number obtained in C2 for other math, if needed.
WARNING: crazy-long excel formula ahead
I was also looking to work with significant figures and I was unable to use VBA as the spreadsheets can't support them. I went to this question/answer and many other sites but all the answers don't seem to deal with all numbers all the time. I was interested in the accepted answer and it got close but as soon as my numbers were < 0.1 I got a #value! error. I'm sure I could have fixed it but I was already down a path and just pressed on.
Problem:
I needed to report a variable number of significant figures in positive and negative mode with numbers from 10^-5 to 10^5. Also, according to the client (and to purple math), if a value of 100 was supplied and was accurate to +/- 1 and we wish to present with 3 sig figs the answer should be '100.' so I included that as well.
Solution:
My solution is for an excel formula that returns the text value with required significant figures for positive and negative numbers.
It's long, but appears to generate the correct results according to my testing (outlined below) regardless of number and significant figures requested. I'm sure it can be simplified but that isn't currently in scope. If anyone wants to suggest a simplification, please leave me a comment!
=TEXT(IF(A1<0,"-","")&LEFT(TEXT(ABS(A1),"0."&REPT("0",sigfigs-1)&"E+00"),sigfigs+1)*10^FLOOR(LOG10(TEXT(ABS(A1),"0."&REPT("0",sigfigs-1)&"E+00")),1),(""&(IF(OR(AND(FLOOR(LOG10(TEXT(ABS(A1),"0."&REPT("0",sigfigs-1)&"E+00")),1)+1=sigfigs,RIGHT(LEFT(TEXT(ABS(A1),"0."&REPT("0",sigfigs-1)&"E+00"),sigfigs+1)*10^FLOOR(LOG10(TEXT(ABS(A1),"0."&REPT("0",sigfigs-1)&"E+00")),1),1)="0"),LOG10(TEXT(ABS(A1),"0."&REPT("0",sigfigs-1)&"E+00"))<=sigfigs-1),"0.","#")&REPT("0",IF(sigfigs-1-(FLOOR(LOG10(TEXT(ABS(A1),"0."&REPT("0",sigfigs-1)&"E+00")),1))>0,sigfigs-1-(FLOOR(LOG10(TEXT(ABS(A1),"0."&REPT("0",sigfigs-1)&"E+00")),1)),0)))))
Note: I have a named range called "sigfigs" and my numbers start in cell A1
Test Results:
I've tested it against the wikipedia list of examples and my own examples so far in positive and negative. I've also tested with a few values that gave me issues early on and all seem to produce the correct results.
I've also tested with a few values that gave me issues early on and all seem to produce the correct results now.
3 Sig Figs Test
99.99 -> 100.
99.9 -> 99.9
100 -> 100.
101 -> 101
Notes:
Treating Negative Numbers
To Treat Negative Numbers, I have included a concatenation with a negative sign if less than 0 and use the absolute value for all other work.
Method of construction:
It was initially divided into about 6 columns in excel that performed the various steps and at the end I merged all of the steps into one formula above.
Use scientific notation, say if you have 180000 and you need 4 sigfigs the only way is to type as 1.800x10^5
I added to your formula so it also automatically displays the correct number of decimal places. In the formula below, replace the digit "2" with the number of decimal places that you want, which means you would need to make four replacements. Here is the updated formula:
=TEXT(ROUND(A1,2-1-INT(LOG10(ABS(A1)))),"0"&IF(INT(LOG10(ABS(ROUND(A1,2-1-INT(LOG10(ABS(A1)))))))<1,"."&REPT("0",2-1-INT(LOG10(ABS(ROUND(A1,2-1-INT(LOG10(ABS(A1)))))))),""))
For example, if cell A1 had the value =1/3000, which is 0.000333333.., the above formula as-written outputs 0.00033.
This is an old question, but I've modified sancho.s' VBA code so that it's a function that takes two arguments: 1) the number you want to display with appropriate sig figs (val), and 2) the number of sig figs (sigd). You can save this as an add-in function in excel for use as a normal function:
Public Function sigFig(val As Range, sigd As Range)
Dim nint As Integer
Dim nfrac As Integer
Dim raisedPower As Double
Dim roundVal As Double
Dim fmtstr As String
Dim fmtstrfrac As String
nint = Int(Log(val) / Log(10)) + 1
nfrac = sigd - nint
raisedPower = 10 ^ (nint)
roundVal = Round(val / raisedPower, sigd) * raisedPower
If (sigd - nint) > 0 Then
fmtstrfrac = "." & String(nfrac, "0")
Else
fmtstrfrac = ""
End If
If nint <= 0 Then
fmtstr = String(1, "0") & fmtstrfrac
Else
fmtstr = String(nint, "0") & fmtstrfrac
End If
sigFig = Format(roundVal, fmtstr)
End Function
It seems to work in all the use cases I've tried so far.
Rounding to significant digits is one thing... addressed above. Formatting to a specific number of digits is another... and I'll post it here for those of you trying to do what I was and ended up here (as I will likely do again in the future)...
Example to display four digits:
.
Use Home > Styles > Conditional Formatting
New Rule > Format only cells that contain
Cell Value > between > -10 > 10 > Format Number 3 decimal places
New Rule > Format only cells that contain
Cell Value > between > -100 > 100 > Format Number 2 decimal places
New Rule > Format only cells that contain
Cell Value > between > -1000 > 1000 > Format Number 1 decimal place
New Rule > Format only cells that contain
Cell Value > not between > -1000 > 1000 > Format Number 0 decimal places
.
Be sure these are in this order and check all of the "Stop If True" boxes.
The formula below works fine. The number of significant figures is set in the first text formula. 0.00 and 4 for 3sf, 0.0 and 3 for 2sf, 0.0000 and 6 for 5sf, etc.
=(LEFT((TEXT(A1,"0.00E+000")),4))*POWER(10,
(RIGHT((TEXT(A1,"0.00E+000")),4)))
The formula is valid for E+/-999, if you have a number beyond this increase the number of the last three zeros, and change the second 4 to the number of zeros +1.
Note that the values displayed are rounded to the significant figures, and should by used for display/output only. If you are doing further calcs, use the original value in A1 to avoid propagating minor errors.
As a very simple display measure, without having to use the rounding function, you can simply change the format of the number and remove 3 significant figures by adding a decimal point after the number.
I.e. #,###. would show the numbers in thousands. #,###.. shows the numbers in millions.
Hope this helps
You could try custom formatting instead.
Here's a crash course: https://support.office.com/en-nz/article/Create-a-custom-number-format-78f2a361-936b-4c03-8772-09fab54be7f4?ui=en-US&rs=en-NZ&ad=NZ.
For three significant figures, I type this in the custom type box:
[>100]##.0;[<=100]#,##0
You could try
=ROUND(value,sigfigs-(1+INT(LOG10(ABS(value)))))
value :: The number you wish to round.
sigfigs :: The number of significant figures you want to round to.

Resources