Oceń wyrażenia za pomocą znaczących cyfr

10

Biorąc pod uwagę wyrażenie, Twoim zadaniem jest jego ocena. Jednak twoja odpowiedź nie może zawierać więcej cyfr niż to konieczne, ponieważ daje to wrażenie posiadania dokładniejszych pomiarów niż rzeczywistość.

Liczba znaczących cyfr, które ma liczba, to liczba cyfr, które zapisano w notacji naukowej, w tym zera na końcu, jeśli występuje przecinek dziesiętny. Na przykład 1200ma 2 znaczące liczby, ponieważ ma, 1.2*10^3ale 1200.ma 4 znaczące liczby i 1200.0ma 5 znaczących liczb.

Dodając dwie liczby, wynik należy zaokrąglić do tej samej liczby miejsc, co liczba, której najmniej znacząca cyfra znajduje się najdalej w lewo. Na przykład 1200 + 3 = 1200(zaokrąglone do setek miejsca, ponieważ 1200 jest zaokrąglone do setek miejsc) 1200.01 + 3 = 1203, i 4.59 + 2.3 = 6.9. Zauważ, że 5zaokrągla w górę. Ta sama zasada dotyczy odejmowania. 0jest zaokrąglony do jednego miejsca. Pamiętaj, że dodawanie i odejmowanie nie zależy od liczby cyfr znaczących. Na przykład,999 + 2.00 = 1001ponieważ 999 jest zaokrąglone do jednego miejsca, a 2,00 jest zaokrąglone do setnego miejsca; jeden zaokrąglony do mniejszej liczby miejsc to 999, więc wynik 1001,00 również powinien zostać zaokrąglony do jednego miejsca. Podobnie, 300 + 1 - 300 jest dokładnie równe 1, ale 300 jest zaokrąglane do setek miejsc, więc wynik końcowy powinien być również zaokrąglany do setek miejsc, dając 0,300. + 1 - 300. równałoby się 1 na druga ręka.

Mnożąc lub dzieląc dwie liczby, zaokrąglij do liczby cyfr znaczących liczby z cyframi najmniej znaczącymi. Na przykład, 3.839*4=20ponieważ dokładna wartość 15.356, zaokrągla do 20od, 4ma tylko jedną znaczącą liczbę. Podobnie, 100/4=30ponieważ obie liczby mają jedną znaczącą liczbę, ale 100./4.00=25.0ponieważ obie liczby mają 3 znaczące liczby. 0jest zdefiniowany jako mający 1 znaczącą liczbę.

Ekspresja może zawierać tylko *, /, +i -(i nawiasów). Kolejność operacji powinna być przestrzegana, a wyniki powinny być zaokrąglane po każdej operacji. Jeśli nawiasy są pominięte w ciągu dodawania lub odejmowania lub ciągu mnożenia i dzielenia, to należy zaokrąglić po zakończeniu wszystkich operacji. Na przykład 6*0.4*2 = 5(jedna znacząca liczba), podczas gdy 0.4*(2*6)=0.4*10=4i (6*0.4)*2=2*2=4.

Dane wejściowe : ciąg znaków z wyrażeniem zawierającym ()*/+-cyfry. Aby uprościć rzeczy, -będzie używany tylko jako operator odejmowania, a nie jako liczby ujemne; odpowiedzi mogą jednak być przeczące i wymagać będą -przedrostka.

Wyjście : Wynikiem wyrażenia, oceniane i zaokrąglona do poprawnej liczby cyfr. Zauważ, że 25jest niepoprawny dla 25.0.

Przypadki testowe :

3 + 0.5 --> 4
25.01 - 0.01 --> 25.00
4*7*3 --> 80
(4*7)*3 --> 90
(8.0 + 0.5)/(2.36 - 0.8 - 0.02) --> 5.7
6.0 + 4.0 --> 10.0
5.0 * 2.0 --> 10.0
1/(2.0 * (3.0 + 5.0)) --> 0.06
0.0020 * 129 --> 0.26
300 + 1 - 300 --> 0
0 - 8.8 --> -9
3*5/2*2 --> 20

Edge case: Rozważ problem 501*2.0. Dokładna wartość to 1002. Drukowanie 1002daje zbyt wiele znaczących liczb (4, gdy potrzebujemy 2), ale 1000daje zbyt mało (1, gdy potrzebujemy 2). W takim przypadku program powinien 1000mimo to wydrukować .

To źródło wyjaśnia również cyfry znaczące: http://www.purplemath.com/modules/rounding2.htm

soktinpk
źródło
Co rozumiesz przez „ tę samą liczbę miejsc ”? Czy to to samo, co „ ta sama liczba znaczących liczb ”? Jeśli chcesz przypadek krawędzi dla Dodatkowo, 999 + 2.00.
Peter Taylor,
Z pewnością 300 + 1 - 300jest ciągiem dodawania i odejmowania, więc nie trzeba go zaokrąglać do końca. (300 + 1) - 300byłoby zero.
Neil,

Odpowiedzi:

9

Java 11, 1325 1379 1356 1336 1290 bajtów

import java.math.*;String c(String s)throws Exception{String r="",T=r,a[],b[],z="\\.";int i=0,l,A[],M=0,m=s.length(),j,f=0,q=m;if(s.contains("(")){for(;i<m;){var c=s.charAt(i++);if(f<1){if(c==40){f=1;continue;}r+=c;}else{if(c==41&T.replaceAll("[^(]","").length()==T.replaceAll("[^)]","").length()){r+="x"+s.substring(i);break;}T+=c;}}return c(r.replace("x",c(T)));}else{for(a=s.split("[\\+\\-\\*/]"),A=new int[l=a.length];i<l;f=b.length>1&&(j=b[1].length())>f?j:f)M=(j=(b=a[i++].split(z))[0].length())>M?j:M;for(b=a.clone(),i=0;i<l;A[i]=b[i].contains(".")?j=b[i].length()-1:b[i].replaceAll("0*$","").length(),i++)for(q=(j=b[i].replace(".","").length())<q?j:q,j=a[i].split(z)[0].length();j++<M;)b[i]=0+b[i];double R=new Double(new javax.script.ScriptEngineManager().getEngineByName("JS").eval(s)+""),p;for(int x:A)m=x<m?x:m;m=m==M&R%1==0&(int)R/10%10<1&(j=(r=R+"").split(z)[0].length())>m?j-q>1?q:j:R>99?m:R%10==0?r.length()-1:m<1?1:m;R=new BigDecimal(R).round(new MathContext((R<0?-R:R)<1?m-1:m)).doubleValue();r=(m<M&(p=Math.pow(10,M-m))/10>R?(int)(R/p)*p:R)+"";l=r.length()-2;r=(r=f<1?r.replaceAll(z+"0$",""):r+"0".repeat(f)).substring(0,(j=r.length())<m?j:r.contains(".")?(j=r.replaceAll("^0\\.0+","").length())<m?m-~j:m+1:m);for(i=r.length();i++<l;)r+=0;return r.replaceAll(z+"$","");}}

+54 bajtów, aby naprawić wielkość liter 501*2.0(dawał wynik 1002wcześniej, ale teraz jest poprawny 1000).

Rozumiem teraz, dlaczego na to wyzwanie nie udzielono odpowiedzi przez prawie dwa lata ..>.> To wyzwanie ma więcej specjalnych przypadków niż język holenderski, który mówi coś ..
Java z pewnością nie jest odpowiednim językiem dla tego rodzaju wyzwań (lub jakiegokolwiek dorosłego golfa wyzwanie w tej sprawie ...; p), ale jest to jedyny język, który znam wystarczająco dobrze, aby nawet podjąć takie trudne wyzwanie.

Format wejściowy Stringbez spacji (jeśli nie jest to dozwolone, możesz dodać s=s.replace(" ","")(+19 bajtów) na górze metody).

Wypróbuj online.

Wyjaśnienie:

Przepraszam za długi post.

if(s.contains("(")){
  for(;i<m;){
    var c=s.charAt(i++);
    if(f<1){
      if(c==40){
        f=1;
        continue;}
      r+=c;}
    else{
      if(c==41&T.replaceAll("[^(]","").length()==T.replaceAll("[^)]","").length()){
        r+="x"+s.substring(i);
        break;}
      T+=c;}}
  return c(r.replace("x",c(T)));}

Ta część służy do wprowadzania danych zawierających nawiasy. Otrzyma oddzielne części i użyje wywołań rekurencyjnych.

  • 0.4*(2*6)staje się 0.4*A, gdzie Ajest rekurencyjne połączenie zc(2*6)
  • (8.3*0.02)+(1.*(9*4)+2.2)staje się A+B, gdzie Ajest rekurencyjne połączenie z c(8.3*0.02)i Brekurencyjne połączenie z c(1.*(9*4)+2.2)→ które z kolei staje się 1.*C+2.2, gdzie Cjest połączenie rekurencyjne zc(9*4)

for(a=s.split("[\\+\\-\\*/]"),A=new int[l=a.length];
    i<l;
    f=b.length>1&&(j=b[1].length())>f?j:f)
  M=(j=(b=a[i++].split(z))[0].length())>M?j:M;

Ta pierwsza pętla służy do wypełnienia wartości Moraz k, gdzie Mjest największą długością całkowitą w odniesieniu do liczb znaczących i knajwiększą długością dziesiętną.

  • 1200+3.0staje się M=2, k=1( 12, .0)
  • 999+2.00staje się M=3, k=2( 999, .00)
  • 300.+1-300.staje się M=3, k=0( 300, .)

for(b=a.clone(),i=0;
    i<l;
    A[i]=b[i].contains(".")?j=b[i].length()-1:b[i].replaceAll("0*$","").length(),i++)
  for(q=(j=b[i].replace(".","").length())<q?j:q,
      j=a[i].split(z)[0].length();
      j++<M;)
    b[i]=0+b[i];

Ta druga pętla służy do wypełniania tablic, Aa btakże wartości q, gdzie Ajest liczba znaczących cyfr, zawiera bliczby całkowite z zerami wiodącymi, aby je dopasować M, i qjest najmniejszą kropką bez uwzględnienia kropek.

  • 1200+3.0staje się A=[2, 5] (12, 00030), b=[1200, 0003.0]i q=2( 30)
  • 999+2.00staje się A=[3, 5] (999, 00200), b=[999, 002.00]i q=3(zarówno 999i 200)
  • 300.+1-300.staje się A=[3, 3, 3] (300, 001, 300), b=[300., 001, 300.]i q=1( 1)
  • 501*2.0staje się A=[3, 4] (501, 0020), b=[501, 002.0]i q=2( 20)

double R=new Double(new javax.script.ScriptEngineManager().getEngineByName("JS").eval(s)+"")

Używa silnika JavaScript do oceny danych wejściowych, które zostaną zapisane Rjako podwójne.

  • 1200+3.0 staje się R=1203.0
  • 999+2.00 staje się R=1001.0
  • 300.+1-300. staje się R=1.0

for(int x:A)
  m=x<m?x:m;

Ustawia msię na najmniejszą wartość w tablicy A.

  • A=[2, 5] staje się m=2
  • A=[3, 5] staje się m=3
  • A=[3, 3, 3] staje się m=3

 m=m==M                // If `m` equals `M`
   &R%1==0             // and `R` has no decimal values (apart from 0)
   &(int)R/10%10<1     // and floor(int(R)/10) modulo-10 is 0
   &(j=(r=R+"").split(z)[0].length())>m?
                       // and the integer-length of R is larger than `m`:
    j-q>1?             //  If this integer-length of `R` minus `q` is 2 or larger:
     q                 //   Set `m` to `q` instead
    :                  //  Else:
     j                 //  Set `m` to this integer-length of `R`
   :R>99?              // Else-if `R` is 100 or larger:
    m                  //  Leave `m` the same
   :R%10==0?           // Else-if `R` modulo-10 is exactly 0:
    r.length()-1       //  Set `m` to the total length of `R` (minus the dot)
   :m<1?               // Else-if `m` is 0:
    1                  //  Set `m` to 1
   :                   // Else:
    m;                 //  Leave `m` the same

To zmienia się w moparciu o wiele czynników.

  • 999+2.00 = 1001.0& m=3,q=3staje się m=4(ponieważ m==M(oba 3) → R%1==0( 1001.0nie ma wartości dziesiętnych) → (int)R/10%10<1( (int)1001.0/10staje się 100100%10<1) → "1001".length()>m( 4>3) → "1001".length()-q<=1( 4-3<=1) → więc mstaje się długością części całkowitej "1001"( 4))
  • 3.839*4 = 15.356& m=1,q=1pozostaje m=1(ponieważ m==M(oba 1) → R%1!=0( 15.356ma wartości dziesiętne) → R<=99R%10!=0( 15.356%10==5.356) → m!=0→ więc mpozostaje ten sam ( 1))
  • 4*7*3 = 84.0& m=1,q=1pozostaje m=1(ponieważ m==M(oba 1) → R%1==0( 84.0nie ma wartości dziesiętnych) → (int)R/10%10>=1( (int)84/10staje się 88%10>=1) → R<=99R%10!=0( 84%10==4) → m!=0→ więc mpozostaje taki sam ( 1))
  • 6.0+4.0 = 10.0& m=2,q=2staje się m=3(ponieważ m!=M( m=2, M=1) → R<=99R%10==0( 10%10==0) → więc mstaje się długością sumy R(minus kropka) "10.0".length()-1( 3))
  • 0-8.8 = -8.8i m=0,q=1staje się m=1(ponieważ m!=M( m=0, M=1) → R<=99R%10!=0( -8.8%10==-8.8) → m<1mstaje się 1)
  • 501*2.0 = 1001.0& m=3,q=2staje się m=2(ponieważ m==M(oba 3) → R%1==0( 1001.0nie ma wartości dziesiętnych) → (int)R/10%10<1( (int)1001.0/10staje się 100100%10<1) → "1001".length()>m( 4>3) → "1001".length()-q>1( 4-2>1) → więc mstaje się q( 2))

R=new BigDecimal(R).round(new MathContext((R<0?-R:R)<1?m-1:m)).doubleValue();

Teraz Rjest zaokrąglany na podstawie m.

  • 1001.0i m=4staje się1001.0
  • 0.258I m=3staje się 0.26(ze względu abs(R)<1, m-1( 2), a nie m=3są stosowane wewnątrz MathContext)
  • -8.8i m=1staje się-9.0
  • 1002.0i m=2staje się1000.0

m<M&(p=Math.pow(10,M-m))/10>R?(int)(R/p)*p:R;

W Rrazie potrzeby modyfikuje to część całkowitą .

  • 300.+1-300. = 1.0i m=3,M=3zostaje 1.0(ponieważ m>=M→ więc Rpozostaje taki sam ( 1.0))
  • 0.4*10 = 4.0i m=1,M=2zostaje 4.0(ponieważ m<M(10^(M-m))/10<=R( (10^1)/10<=4.010/10<=4.01<=4.0) → więc Rpozostaje ten sam ( 4.0))
  • 300+1-300 = 1.0& m=1,M=3staje się 0.0(ponieważ m<M(10^(M-m))/10>R( (10^2)/10>1.0100/10>1.010>1.0) → Rstaje się z 0.0powodu int(R/(10^(M-m)))*(10^(M-m))( int(1.0/(10^2))*(10^2)int(1.0/100)*1000*1000)

r=(...)+"";                  // Set `R` to `r` as String (... is the part explained above)
l=r.length()-2;              // Set `l` to the length of `R` minus 2
r=(r=k<1?                    // If `k` is 0 (no decimal values in any of the input-numbers)
      r.replaceAll(z+"0$","")
                             //  Remove the `.0` at the end
     :                       // Else:
      r+"0".repeat(f)
                             //  Append `k` zeroes after the current `r`
  ).substring(0,             // Then take the substring from index `0` to:
     (j=r.length())<m?       //  If the total length of `r` is below `m`:
       j                     //   Leave `r` the same
     :r.contains(".")?       //  Else-if `r` contains a dot
       (j=r.replaceAll("^0\\.0+","").length())<m?
                             //   And `R` is a decimal below 1,
                             //   and its rightmost decimal length is smaller than `m`
        m-~j                 //    Take the substring from index 0 to `m+j+1`
                             //    where `j` is this rightmost decimal length
       :                     //   Else:
        m+1                  //    Take the substring from index 0 to `m+1`
     :                       //  Else:
      m);                    //   Take the substring from index 0 to `m`

Ustawia Rsię rna String i modyfikuje go na podstawie wielu czynników.

  • 1203.0& m=4,k=2staje się 1203.(ponieważ k>=1→ tak rstaje się 1001.000; r.length()>=m( 8>=4) → r.contains(".")r.length()>=m( 8>=4) → podciąg z indeksu 0do m+1( 5))
  • 6.9& m=2,k=2pozostaje 6.9(ponieważ k>=1→ tak rstaje się 6.900; r.length()>=m( 5>=2) → r.contains(".")r.length()>=m( 5>=2) → podciąg z indeksu 0do m+1( 3))
  • 1.0& m=3,k=0staje się 1(ponieważ k<1→ tak rstaje się 1; r.length()<m( 1<3) → podciąg od indeksu 0do r.length()( 1))
  • 25.0& m=4,k=4staje się 25.00(ponieważ k>=1→ tak rstaje się 25.00000; r.length()>=m( 8>=4) → r.contains(".")r.length()>+m( 8>=4) → podciąg z indeksu 0do m+1( 5))
  • 0& m=1,k=0pozostaje 0(ponieważ k<1→ tak rzostaje 0; r.length()>=m( 1>=1) → !r.contains(".")→ podciąg z indeksu 0do m( 1))

for(i=r.length();i++<l;)
  r+=0;

Powoduje to, że w razie potrzeby końcowe zera są ponownie częścią liczby całkowitej.

  • r="12"i R=1200.0staje sięr="1200"
  • r="1"i R=10.0staje sięr="10"
  • r="8"i R=80.0staje sięr="80"

return r.replaceAll(z+"$","");

I w końcu zwracamy wynik, po usunięciu kropek końcowych.

  • 1203. staje się 1203
  • 5. staje się 5

Zdecydowanie można go pograć w golfa o kilkaset bajtów, ale cieszę się, że teraz działa. Zrozumienie każdego z przypadków i tego, o co pytano w wyzwaniu, zajęło już trochę czasu. A potem zajęło dużo prób i błędów, testów i ponownych testów, aby uzyskać powyższy wynik. Podczas pisania powyższego wyjaśnienia udało mi się usunąć kolejne ± 50 bajtów nieużywanego kodu.

Kevin Cruijssen
źródło
1
Pozytywne. Ale wydaje się, że specyfikacja wymaga tego 501*2.0do wyjścia 1000(i tak powinieneś 1000 to zrobić, co interpretuję jako „nadal”, a nie w żadnym wypadku ). I tak wspaniała praca.
Weijun Zhou
1
@WeijunZhou Dzięki za opinie! Zastanowiłem się nad tym ponownie i udało mi się naprawić obudowę bez rozbijania innych skrzynek. :)
Kevin Cruijssen