Avalie expressões com números significativos


10

Dada uma expressão, sua tarefa é avaliá-la. No entanto, sua resposta não pode mostrar mais dígitos do que o necessário, pois isso dá a impressão de ter medidas mais precisas do que a realidade.

O número de algarismos significativos que um número possui é quantos dígitos possui quando escritos em notação científica, incluindo zeros no final se houver um ponto decimal. Por exemplo, 1200possui 2 números significativos porque é, 1.2*10^3mas 1200.possui 4 números significativos e 1200.05 valores significativos.

Ao adicionar dois números, o resultado deve ser arredondado para o mesmo número de lugares que o número cujo dígito menos significativo está mais à esquerda. Por exemplo, 1200 + 3 = 1200(arredondado para a casa das centenas desde 1200 é arredondado para a casa das centenas),, 1200.01 + 3 = 1203e 4.59 + 2.3 = 6.9. Observe que 5arredondamentos para cima. Essa mesma regra se aplica à subtração. 0é arredondado para o local Observe que a adição e subtração não depende do número de dígitos significativos. Por exemplo,999 + 2.00 = 1001porque 999 é arredondado para o primeiro lugar e 2,00 é arredondado para o centésimo lugar; o que é arredondado para menos lugares é 999; portanto, o resultado, 1001,00, também deve ser arredondado para o local. Da mesma forma, 300 + 1 - 300 é exatamente igual a 1, mas 300 é arredondado para a casa das centenas, portanto o resultado final também deve ser arredondado para a casa das centenas, resultando em 0. 300. + 1 - 300. seria igual a 1 na outra mão.

Ao multiplicar ou dividir dois números, arredonde para o número de dígitos significativos do número com os dígitos menos significativos. Por exemplo, 3.839*4=20porque o valor exato,, 15.356arredonda para 20desde, 4tem apenas um número significativo. Da mesma forma, 100/4=30uma vez que ambos os números têm um número significativo, mas 100./4.00=25.0como ambos os números têm 3 números significativos. 0está definido para ter 1 figura significativa.

Expressões conterá somente *, /, +, e -, (e parênteses). A ordem das operações deve ser seguida e os resultados devem ser arredondados após cada operação. Se os parênteses forem deixados de fora em uma sequência de adições ou subtrações ou em uma sequência de multiplicações e divisões, arredonde após todas as operações serem concluídas. Por exemplo, 6*0.4*2 = 5(uma figura significativa), enquanto 0.4*(2*6)=0.4*10=4e (6*0.4)*2=2*2=4.

Entrada : uma sequência, com uma expressão contendo ()*/+-e dígitos. Para simplificar, -será usado apenas como operador de subtração, não para significar números negativos; as respostas, no entanto, ainda podem ser negativas e exigiriam -como prefixo.

Saída : o resultado da expressão, avaliado e arredondado para o número correto de dígitos. Observe que 25está incorreto para 25.0.

Casos de teste :

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

Caso de borda: considere o problema de 501*2.0. O valor exato é 1002. A impressão 1002fornece muitos números significativos (4, quando precisamos de 2), mas 1000fornece muito poucos (1, quando precisamos de 2). Nesse caso, seu programa deve imprimir de 1000qualquer maneira.

Esta fonte também explica dígitos significativos: http://www.purplemath.com/modules/rounding2.htm


O que você quer dizer com " o mesmo número de lugares "? É o mesmo que " o mesmo número de números significativos "? Se você deseja um case de borda para adição 999 + 2.00,.
Peter Taylor

Certamente 300 + 1 - 300é uma série de adições e subtrações, por isso não precisa ser arredondada até o final. (300 + 1) - 300seria zero.
Neil

Respostas:


9

Java 11, 1325 1379 1356 1336 1290 bytes

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 bytes para corrigir o caso de borda 501*2.0(deu o resultado 1002antes, mas agora está correto 1000).

Agora eu entendo por que este desafio foi respondida por quase dois anos ..>.> Este desafio tem casos mais especial do que a língua holandesa, que está dizendo algo ..
Java não é certamente a linguagem certa para este tipo de desafios (ou qualquer codegolf desafio nesse sentido ..; p), mas é a única linguagem que conheço o suficiente para tentar um desafio difícil como esse.

Formato de entrada como Stringsem espaços (se isso não for permitido, você pode adicionar s=s.replace(" ","")(+19 bytes) na parte superior do método).

Experimente online.

Explicação:

Desculpe pelo longo 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)));}

Esta parte é usada para entrada contendo parênteses. Ele obterá as partes separadas e usará chamadas recursivas.

  • 0.4*(2*6)torna-se 0.4*A, onde Aé uma chamada recursiva parac(2*6)
  • (8.3*0.02)+(1.*(9*4)+2.2)torna-se A+B, onde Aé uma chamada recursiva c(8.3*0.02)e Buma chamada recursiva para c(1.*(9*4)+2.2)→ que por sua vez se torna 1.*C+2.2, onde Cé uma chamada recursiva parac(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;

Esse primeiro loop é usado para preencher os valores Me k, onde Mé o maior comprimento inteiro em relação a números significativos e ko maior comprimento decimal.

  • 1200+3.0torna-se M=2, k=1( 12, .0)
  • 999+2.00torna-se M=3, k=2( 999, .00)
  • 300.+1-300.torna-se 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];

Esse segundo loop é usado para preencher as matrizes Ae btambém o valor q, onde Aé a quantidade de números significativos, bmantém os números inteiros com zeros à esquerda correspondentes Me qé o menor comprimento, independentemente dos pontos.

  • 1200+3.0torna-se A=[2, 5] (12, 00030), b=[1200, 0003.0]e q=2( 30)
  • 999+2.00torna-se A=[3, 5] (999, 00200), b=[999, 002.00]e q=3(ambos 999e 200)
  • 300.+1-300.torna-se A=[3, 3, 3] (300, 001, 300), b=[300., 001, 300.]e q=1( 1)
  • 501*2.0torna-se A=[3, 4] (501, 0020), b=[501, 002.0]e q=2( 20)

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

Usa um mecanismo JavaScript para avaliar a entrada, que será salva Rcomo o dobro.

  • 1200+3.0 torna-se R=1203.0
  • 999+2.00 torna-se R=1001.0
  • 300.+1-300. torna-se R=1.0

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

Isso define mo menor valor na matriz A.

  • A=[2, 5] torna-se m=2
  • A=[3, 5] torna-se m=3
  • A=[3, 3, 3] torna-se 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

Isso é modificado com mbase em vários fatores.

  • 999+2.00 = 1001.0& m=3,q=3torna - se m=4(porque m==M(ambos 3) → R%1==0( 1001.0não possui valores decimais) → (int)R/10%10<1( (int)1001.0/10torna-se 100100%10<1) → "1001".length()>m( 4>3) → "1001".length()-q<=1( 4-3<=1) → mtorna-se o comprimento da parte inteira "1001"( 4))
  • 3.839*4 = 15.356& m=1,q=1permanece m=1(porque m==M(ambos 1) → R%1!=0( 15.356tem valores decimais) → R<=99R%10!=0( 15.356%10==5.356) → m!=0mpermanece o mesmo ( 1))
  • 4*7*3 = 84.0& m=1,q=1permanece m=1(porque m==M(ambos 1) → R%1==0( 84.0não possui valores decimais) → (int)R/10%10>=1( (int)84/10torna-se 88%10>=1) → R<=99R%10!=0( 84%10==4) → m!=0mpermanece o mesmo ( 1))
  • 6.0+4.0 = 10.0& m=2,q=2torna - se m=3(porque m!=M( m=2, M=1) → R<=99R%10==0( 10%10==0) → mtorna-se o comprimento do total R(menos o ponto) "10.0".length()-1( 3))
  • 0-8.8 = -8.8& m=0,q=1torna - se m=1(porque m!=M( m=0, M=1) → R<=99R%10!=0( -8.8%10==-8.8) → m<1mtorna - se 1)
  • 501*2.0 = 1001.0& m=3,q=2torna - se m=2(porque m==M(ambos 3) → R%1==0( 1001.0não possui valores decimais) → (int)R/10%10<1( (int)1001.0/10torna-se 100100%10<1) → "1001".length()>m( 4>3) → "1001".length()-q>1( 4-2>1) → mtorna - se q( 2))

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

Agora Ré arredondado com base em m.

  • 1001.0e m=4se torna1001.0
  • 0.258& m=3torna - se 0.26(porque abs(R)<1, m-1( 2) em vez de m=3é usado dentro MathContext)
  • -8.8e m=1se torna-9.0
  • 1002.0e m=2se torna1000.0

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

Isso modifica a parte inteira, Rse necessário.

  • 300.+1-300. = 1.0& m=3,M=3permanece 1.0(porque m>=MRpermanece o mesmo ( 1.0))
  • 0.4*10 = 4.0& m=1,M=2permanece 4.0(porque m<M(10^(M-m))/10<=R( (10^1)/10<=4.010/10<=4.01<=4.0) → Rpermanece o mesmo ( 4.0))
  • 300+1-300 = 1.0& m=1,M=3torna - se 0.0(porque m<M(10^(M-m))/10>R( (10^2)/10>1.0100/10>1.010>1.0) → Rtorna - se 0.0por causa de 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`

Isso define Ra rcomo corda, e modifica-lo com base em vários fatores.

  • 1203.0& m=4,k=2torna - se 1203.(porque k>=1rtorna - se assim 1001.000; r.length()>=m( 8>=4) → r.contains(".")r.length()>=m( 8>=4) → substring do índice 0para m+1( 5))
  • 6.9& m=2,k=2permanece 6.9(porque k>=1rse torna 6.900; r.length()>=m( 5>=2) → r.contains(".")r.length()>=m( 5>=2) → subcadeia do índice 0para m+1( 3))
  • 1.0& m=3,k=0torna - se 1(porque k<1rtorna - se assim 1; r.length()<m( 1<3) → substring do índice 0para r.length()( 1))
  • 25.0& m=4,k=4torna - se 25.00(porque k>=1rtorna - se assim 25.00000; r.length()>=m( 8>=4) → r.contains(".")r.length()>+m( 8>=4) → substring do índice 0para m+1( 5))
  • 0& m=1,k=0permanece 0(porque k<1rpermanece assim 0; r.length()>=m( 1>=1) → !r.contains(".")→ substring do índice 0para m( 1))

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

Isso coloca os zeros à direita novamente na parte inteira, se necessário.

  • r="12"e R=1200.0se tornar="1200"
  • r="1"e R=10.0se tornar="10"
  • r="8"e R=80.0se tornar="80"

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

E finalmente retornamos o resultado, depois que removemos os pontos à direita.

  • 1203. torna-se 1203
  • 5. torna-se 5

Definitivamente pode ser jogado por algumas centenas de bytes, mas estou feliz que esteja funcionando agora. Já demorou um pouco para entender cada um dos casos e o que estava sendo solicitado no desafio. E então foram necessárias muitas tentativas e erros, testes e retestes para chegar ao resultado acima. E enquanto escrevia essa explicação acima, eu era capaz de remover outros ± 50 bytes de código não utilizado.


11
Votado. Mas a especificação parece exigir que 501*2.0a saída 1000(você deve produzir de 1000 qualquer maneira , que eu interpreto como "imóvel", não de qualquer maneira ). Magnífico trabalho de qualquer maneira.
Weijun Zhou

11
@ WeijunZhou Obrigado pelo feedback! Pensei novamente e consegui consertar o caso sem quebrar nenhum outro caso. :)
Kevin Cruijssen
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.